Skip to main content

ktrs_cli/ktlint/
logger.rs

1//! ktlint's logging: kotlin-logging over logback with its default `TTLLLayout` console appender, which
2//! writes `HH:mm:ss.SSS [thread] LEVEL logger -- message` to *stdout* (also with `--stdin`).
3
4use ktrs_lint::editorconfig::KtlintVersion;
5
6use crate::ktlint::console::{Console, LINE_SEPARATOR};
7use crate::ktlint::parallel::WORKER_THREAD_PREFIX;
8use crate::ktlint::version;
9
10pub const KTLINT_COMMAND_LINE: &str = "io.github.ktlint.core.cli.internal.KtlintCommandLine";
11pub const FILE_UTILS: &str = "io.github.ktlint.core.cli.internal.FileUtils";
12pub const REPORTER_AGGREGATOR: &str = "io.github.ktlint.core.cli.internal.ReporterAggregator";
13pub const KTLINT_SERVICE_LOADER: &str = "io.github.ktlint.core.cli.internal.KtlintServiceLoader";
14pub const LOAD_RULE_PROVIDERS: &str = "io.github.ktlint.core.cli.internal.LoadRuleProviders";
15pub const BASELINE: &str = "io.github.ktlint.core.cli.reporter.baseline.Baseline";
16pub const EDITOR_CONFIG_DEFAULTS_LOADER: &str = "io.github.ktlint.core.rule.engine.internal.EditorConfigDefaultsLoader";
17pub const GENERATE_EDITOR_CONFIG_SUB_COMMAND: &str = "io.github.ktlint.core.cli.internal.GenerateEditorConfigSubCommand";
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
20pub enum Level {
21    Trace,
22    Debug,
23    Info,
24    Warn,
25    Error,
26    /// `--log-level=none`.
27    Off,
28}
29
30impl Level {
31    /// `--log-level`'s converter; `Err` is its `error("Invalid log level '$it'")`.
32    pub fn parse(value: &str) -> Result<Level, String> {
33        match value.to_uppercase().as_str() {
34            "TRACE" => Ok(Level::Trace),
35            "DEBUG" => Ok(Level::Debug),
36            "INFO" => Ok(Level::Info),
37            "WARN" => Ok(Level::Warn),
38            "ERROR" => Ok(Level::Error),
39            "NONE" => Ok(Level::Off),
40            _ => Err(format!("Invalid log level '{value}'")),
41        }
42    }
43
44    fn name(self) -> &'static str {
45        match self {
46            Level::Trace => "TRACE",
47            Level::Debug => "DEBUG",
48            Level::Info => "INFO",
49            Level::Warn => "WARN",
50            Level::Error => "ERROR",
51            Level::Off => "OFF",
52        }
53    }
54}
55
56#[derive(Clone)]
57pub struct Logger {
58    console: Console,
59    min_level: Level,
60    ktlint_version: KtlintVersion,
61}
62
63impl Logger {
64    pub fn new(console: Console, min_level: Level, ktlint_version: KtlintVersion) -> Logger {
65        Logger { console, min_level, ktlint_version }
66    }
67
68    pub fn ktlint_version(&self) -> KtlintVersion {
69        self.ktlint_version
70    }
71
72    pub fn is_enabled(&self, level: Level) -> bool {
73        level != Level::Off && level >= self.min_level
74    }
75
76    /// Logs `message()` from `logger` (a 2.0 class name, renamed to the run's package), if `level` is enabled.
77    pub fn log(&self, level: Level, logger: &str, message: impl FnOnce() -> String) {
78        if self.is_enabled(level) {
79            let logger = match logger.strip_prefix(version::package(KtlintVersion::V2_0)) {
80                Some(class) => format!("{}{class}", version::package(self.ktlint_version)),
81                None => logger.to_owned(),
82            };
83            let line = format!("{} [{}] {} {logger} -- {}{LINE_SEPARATOR}", local_time(), thread_name(), level.name(), message());
84            self.console.out(&line);
85        }
86    }
87
88    pub fn trace(&self, logger: &str, message: impl FnOnce() -> String) {
89        self.log(Level::Trace, logger, message);
90    }
91
92    pub fn debug(&self, logger: &str, message: impl FnOnce() -> String) {
93        self.log(Level::Debug, logger, message);
94    }
95
96    pub fn info(&self, logger: &str, message: impl FnOnce() -> String) {
97        self.log(Level::Info, logger, message);
98    }
99
100    pub fn warn(&self, logger: &str, message: impl FnOnce() -> String) {
101        self.log(Level::Warn, logger, message);
102    }
103
104    pub fn error(&self, logger: &str, message: impl FnOnce() -> String) {
105        self.log(Level::Error, logger, message);
106    }
107}
108
109/// The JVM thread: a file worker's name (see `parallel.rs`), else `main` (test threads carry test names).
110fn thread_name() -> String {
111    let current = std::thread::current();
112    match current.name() {
113        Some(name) if name.starts_with(WORKER_THREAD_PREFIX) => name.to_owned(),
114        _ => "main".to_owned(),
115    }
116}
117
118/// `%d{HH:mm:ss.SSS}` in the local time zone.
119fn local_time() -> String {
120    let (hour, minute, second, millis) = local_clock();
121    format!("{hour:02}:{minute:02}:{second:02}.{millis:03}")
122}
123
124#[cfg(windows)]
125fn local_clock() -> (u16, u16, u16, u16) {
126    #[link(name = "kernel32")]
127    unsafe extern "system" {
128        fn GetLocalTime(system_time: *mut [u16; 8]);
129    }
130    let mut st = [0u16; 8];
131    // SAFETY: SYSTEMTIME is eight WORDs; GetLocalTime only writes it.
132    unsafe { GetLocalTime(&mut st) };
133    (st[4], st[5], st[6], st[7])
134}
135
136#[cfg(all(unix, target_pointer_width = "64"))]
137fn local_clock() -> (u16, u16, u16, u16) {
138    unsafe extern "C" {
139        fn localtime_r(time: *const i64, tm: *mut [i32; 16]) -> *mut [i32; 16];
140    }
141    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
142    let secs = now.as_secs() as i64;
143    // `struct tm` starts with tm_sec, tm_min, tm_hour (ints); 64 bytes covers glibc/musl/macOS layouts.
144    let mut tm = [0i32; 16];
145    // SAFETY: time_t is i64 on 64-bit unix; localtime_r writes at most sizeof(struct tm) <= 64 bytes.
146    let ok = unsafe { !localtime_r(&secs, &mut tm).is_null() };
147    if ok {
148        (tm[2] as u16, tm[1] as u16, tm[0] as u16, now.subsec_millis() as u16)
149    } else {
150        utc_clock(now)
151    }
152}
153
154#[cfg(not(any(windows, all(unix, target_pointer_width = "64"))))]
155fn local_clock() -> (u16, u16, u16, u16) {
156    utc_clock(std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default())
157}
158
159#[cfg_attr(windows, allow(dead_code))]
160fn utc_clock(now: std::time::Duration) -> (u16, u16, u16, u16) {
161    let day_secs = now.as_secs() % 86_400;
162    ((day_secs / 3600) as u16, (day_secs / 60 % 60) as u16, (day_secs % 60) as u16, now.subsec_millis() as u16)
163}