Skip to main content

nautilus_common/logging/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Logging configuration types and parsing.
17//!
18//! This module provides configuration for the Nautilus logging subsystem via
19//! the `LoggerConfig` and `FileWriterConfig` types.
20//!
21//! # Spec String Format
22//!
23//! The `NAUTILUS_LOG` environment variable uses a semicolon-separated format:
24//!
25//! ```text
26//! stdout=Info;fileout=Debug;RiskEngine=Error;my_crate::module=Debug;is_colored
27//! ```
28//!
29//! ## Supported Keys
30//!
31//! | Key                     | Type      | Description                                  |
32//! |-------------------------|-----------|----------------------------------------------|
33//! | `stdout`                | Log level | Maximum level for stdout output.             |
34//! | `fileout`               | Log level | Maximum level for file output.               |
35//! | `is_colored`            | Boolean   | Enable ANSI colors (default: true).          |
36//! | `print_config`          | Boolean   | Print config to stdout at startup.           |
37//! | `log_components_only`   | Boolean   | Only log components with explicit filters.   |
38//! | `use_tracing`           | Boolean   | Enable tracing subscriber for external libs. |
39//! | `fileout_sync_on_flush` | Boolean   | Sync file logs on every flush (default: true). |
40//! | `buffered_stdout`       | Boolean   | Buffer stdout output (default: false).       |
41//! | `<component>`           | Log level | Component-specific log level (exact match).  |
42//! | `<module::path>`        | Log level | Module-specific log level (prefix match).    |
43//!
44//! ## Log Levels
45//!
46//! All log levels are case-insensitive.
47//!
48//! - `Off`
49//! - `Error`
50//! - `Warn`
51//! - `Info`
52//! - `Debug`
53//! - `Trace`
54//!
55//! ## Boolean Values
56//!
57//! - Bare flag: `is_colored` → true
58//! - Explicit: `is_colored=true`, `is_colored=false`, `is_colored=0`, `is_colored=no`
59
60use std::{env, str::FromStr};
61
62use ahash::AHashMap;
63use log::LevelFilter;
64use serde::{Deserialize, Serialize};
65use ustr::Ustr;
66
67use super::writer::FileWriterConfig;
68use crate::config::ConfigResult;
69
70/// Configuration for the Nautilus logger.
71#[cfg_attr(
72    feature = "python",
73    pyo3::pyclass(module = "nautilus_trader.common", from_py_object)
74)]
75#[cfg_attr(
76    feature = "python",
77    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")
78)]
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
80#[builder(finish_fn(name = build_inner, vis = ""))]
81#[serde(default, deny_unknown_fields)]
82pub struct LoggerConfig {
83    /// Maximum log level for stdout output.
84    #[builder(default = LevelFilter::Info)]
85    pub stdout_level: LevelFilter,
86    /// Maximum log level for file output (`Off` disables file logging).
87    #[builder(default = LevelFilter::Off)]
88    pub fileout_level: LevelFilter,
89    /// Per-component log level overrides (exact match).
90    #[builder(default)]
91    pub component_level: AHashMap<Ustr, LevelFilter>,
92    /// Per-module path log level overrides (prefix match).
93    #[builder(default)]
94    pub module_level: AHashMap<Ustr, LevelFilter>,
95    /// Log only components with explicit level filters.
96    #[builder(default)]
97    pub log_components_only: bool,
98    /// Use ANSI color codes in output.
99    #[builder(default = true)]
100    pub is_colored: bool,
101    /// Print configuration to stdout at startup.
102    #[builder(default)]
103    pub print_config: bool,
104    /// Initialize the tracing subscriber for external Rust crate logs.
105    #[builder(default)]
106    pub use_tracing: bool,
107    /// If all logging should be bypassed.
108    #[builder(default)]
109    pub bypass_logging: bool,
110    /// File writer configuration for log file output.
111    pub file_config: Option<FileWriterConfig>,
112    /// If the log file should be cleared before use.
113    #[builder(default)]
114    pub clear_log_file: bool,
115    /// If file log flushes should also sync data to disk.
116    #[builder(default = true)]
117    pub fileout_sync_on_flush: bool,
118    /// If stdout writes should be buffered until flush or buffer capacity.
119    #[builder(default)]
120    pub buffered_stdout: bool,
121}
122
123impl<S: logger_config_builder::IsComplete> LoggerConfigBuilder<S> {
124    /// Validates and builds the [`LoggerConfig`].
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if any field fails validation (see [`LoggerConfig::validate`]).
129    pub fn build(self) -> ConfigResult<LoggerConfig> {
130        let config = self.build_inner();
131        config.validate()?;
132        Ok(config)
133    }
134}
135
136impl Default for LoggerConfig {
137    fn default() -> Self {
138        Self::builder()
139            .build()
140            .expect("default `LoggerConfig` should be valid")
141    }
142}
143
144impl LoggerConfig {
145    /// Creates a new [`LoggerConfig`] instance.
146    #[must_use]
147    #[expect(clippy::too_many_arguments)]
148    pub fn new(
149        stdout_level: LevelFilter,
150        fileout_level: LevelFilter,
151        component_level: AHashMap<Ustr, LevelFilter>,
152        module_level: AHashMap<Ustr, LevelFilter>,
153        log_components_only: bool,
154        is_colored: bool,
155        print_config: bool,
156        use_tracing: bool,
157        bypass_logging: bool,
158        file_config: Option<FileWriterConfig>,
159        clear_log_file: bool,
160    ) -> Self {
161        Self {
162            stdout_level,
163            fileout_level,
164            component_level,
165            module_level,
166            log_components_only,
167            is_colored,
168            print_config,
169            use_tracing,
170            bypass_logging,
171            file_config,
172            clear_log_file,
173            fileout_sync_on_flush: true,
174            buffered_stdout: false,
175        }
176    }
177
178    /// Validates the logger configuration.
179    ///
180    /// # Errors
181    ///
182    /// Returns an error if the file writer configuration is invalid (see
183    /// [`FileWriterConfig::validate`]).
184    pub fn validate(&self) -> ConfigResult<()> {
185        if let Some(file_config) = &self.file_config {
186            file_config.validate()?;
187        }
188
189        Ok(())
190    }
191
192    /// Parses a configuration from a spec string.
193    ///
194    /// # Format
195    ///
196    /// Semicolon-separated key-value pairs or bare flags:
197    /// ```text
198    /// stdout=Info;fileout=Debug;RiskEngine=Error;my_crate::module=Debug;is_colored
199    /// ```
200    ///
201    /// # Errors
202    ///
203    /// Returns an error if the spec string contains invalid syntax or log levels.
204    pub fn from_spec(spec: &str) -> anyhow::Result<Self> {
205        let mut config = Self::default();
206
207        for kv in spec.split(';') {
208            let kv = kv.trim();
209            if kv.is_empty() {
210                continue;
211            }
212
213            let Some((k, v)) = kv.split_once('=') else {
214                // Handle bare flags (without =)
215                match kv.to_lowercase().as_str() {
216                    "log_components_only" => config.log_components_only = true,
217                    "is_colored" => config.is_colored = true,
218                    "print_config" => config.print_config = true,
219                    "use_tracing" => config.use_tracing = true,
220                    "bypass_logging" => config.bypass_logging = true,
221                    "fileout_sync_on_flush" => config.fileout_sync_on_flush = true,
222                    "buffered_stdout" => config.buffered_stdout = true,
223                    _ => anyhow::bail!("Invalid spec pair: {kv}"),
224                }
225                continue;
226            };
227
228            let k = k.trim();
229            let v = v.trim();
230            let k_lower = k.to_lowercase();
231
232            match k_lower.as_str() {
233                "is_colored" => {
234                    config.is_colored = parse_bool_value(v);
235                }
236                "log_components_only" => {
237                    config.log_components_only = parse_bool_value(v);
238                }
239                "print_config" => {
240                    config.print_config = parse_bool_value(v);
241                }
242                "use_tracing" => {
243                    config.use_tracing = parse_bool_value(v);
244                }
245                "bypass_logging" => {
246                    config.bypass_logging = parse_bool_value(v);
247                }
248                "fileout_sync_on_flush" => {
249                    config.fileout_sync_on_flush = parse_bool_value(v);
250                }
251                "buffered_stdout" => {
252                    config.buffered_stdout = parse_bool_value(v);
253                }
254                "stdout" => {
255                    config.stdout_level = parse_level(v)?;
256                }
257                "fileout" => {
258                    config.fileout_level = parse_level(v)?;
259                }
260                _ => {
261                    let lvl = parse_level(v)?;
262
263                    if k.contains("::") {
264                        config.module_level.insert(Ustr::from(k), lvl);
265                    } else {
266                        config.component_level.insert(Ustr::from(k), lvl);
267                    }
268                }
269            }
270        }
271
272        Ok(config)
273    }
274
275    /// Parses configuration from the `NAUTILUS_LOG` environment variable.
276    ///
277    /// # Errors
278    ///
279    /// Returns an error if the variable is unset or contains invalid syntax.
280    pub fn from_env() -> anyhow::Result<Self> {
281        let spec = env::var("NAUTILUS_LOG")?;
282        Self::from_spec(&spec)
283    }
284}
285
286/// Parses a boolean value from a string.
287///
288/// Returns `true` unless the value is explicitly "false", "0", or "no" (case-insensitive).
289fn parse_bool_value(v: &str) -> bool {
290    !matches!(v.to_lowercase().as_str(), "false" | "0" | "no")
291}
292
293/// Parses a log level from a string.
294fn parse_level(v: &str) -> anyhow::Result<LevelFilter> {
295    LevelFilter::from_str(v).map_err(|_| anyhow::anyhow!("Invalid log level: {v}"))
296}
297
298#[cfg(test)]
299mod tests {
300    use rstest::rstest;
301
302    use super::*;
303    use crate::config::ConfigError;
304
305    #[rstest]
306    fn test_zero_rotation_max_file_size_rejected() {
307        let file_config = FileWriterConfig::new(None, None, None, Some((0, 5)));
308        let result = LoggerConfig::builder().file_config(file_config).build();
309        assert!(
310            matches!(result, Err(ConfigError::Range { field, .. }) if field == "file_config.file_rotate.max_file_size")
311        );
312    }
313
314    #[rstest]
315    fn test_positive_rotation_max_file_size_accepted() {
316        let file_config = FileWriterConfig::new(None, None, None, Some((1_048_576, 5)));
317        let result = LoggerConfig::builder().file_config(file_config).build();
318        assert!(result.is_ok());
319    }
320
321    #[rstest]
322    fn test_default_config() {
323        let config = LoggerConfig::default();
324        assert_eq!(config.stdout_level, LevelFilter::Info);
325        assert_eq!(config.fileout_level, LevelFilter::Off);
326        assert!(config.component_level.is_empty());
327        assert!(!config.log_components_only);
328        assert!(config.is_colored);
329        assert!(!config.print_config);
330        assert!(!config.bypass_logging);
331        assert!(config.file_config.is_none());
332        assert!(!config.clear_log_file);
333        assert!(config.fileout_sync_on_flush);
334        assert!(!config.buffered_stdout);
335    }
336
337    #[rstest]
338    fn test_from_spec_stdout_and_fileout() {
339        let config = LoggerConfig::from_spec("stdout=Debug;fileout=Error").unwrap();
340        assert_eq!(config.stdout_level, LevelFilter::Debug);
341        assert_eq!(config.fileout_level, LevelFilter::Error);
342    }
343
344    #[rstest]
345    fn test_from_spec_case_insensitive_levels() {
346        let config = LoggerConfig::from_spec("stdout=debug;fileout=ERROR").unwrap();
347        assert_eq!(config.stdout_level, LevelFilter::Debug);
348        assert_eq!(config.fileout_level, LevelFilter::Error);
349    }
350
351    #[rstest]
352    fn test_from_spec_case_insensitive_keys() {
353        let config = LoggerConfig::from_spec("STDOUT=Info;FILEOUT=Debug").unwrap();
354        assert_eq!(config.stdout_level, LevelFilter::Info);
355        assert_eq!(config.fileout_level, LevelFilter::Debug);
356    }
357
358    #[rstest]
359    fn test_from_spec_empty_string() {
360        let config = LoggerConfig::from_spec("").unwrap();
361        assert_eq!(config, LoggerConfig::default());
362    }
363
364    #[rstest]
365    fn test_from_spec_with_whitespace() {
366        let config = LoggerConfig::from_spec("  stdout = Info ; fileout = Debug  ").unwrap();
367        assert_eq!(config.stdout_level, LevelFilter::Info);
368        assert_eq!(config.fileout_level, LevelFilter::Debug);
369    }
370
371    #[rstest]
372    fn test_from_spec_trailing_semicolon() {
373        let config = LoggerConfig::from_spec("stdout=Warn;").unwrap();
374        assert_eq!(config.stdout_level, LevelFilter::Warn);
375    }
376
377    #[rstest]
378    fn test_from_spec_bare_is_colored() {
379        let config = LoggerConfig::from_spec("is_colored").unwrap();
380        assert!(config.is_colored);
381    }
382
383    #[rstest]
384    fn test_from_spec_is_colored_true() {
385        let config = LoggerConfig::from_spec("is_colored=true").unwrap();
386        assert!(config.is_colored);
387    }
388
389    #[rstest]
390    fn test_from_spec_is_colored_false() {
391        let config = LoggerConfig::from_spec("is_colored=false").unwrap();
392        assert!(!config.is_colored);
393    }
394
395    #[rstest]
396    fn test_from_spec_is_colored_zero() {
397        let config = LoggerConfig::from_spec("is_colored=0").unwrap();
398        assert!(!config.is_colored);
399    }
400
401    #[rstest]
402    fn test_from_spec_is_colored_no() {
403        let config = LoggerConfig::from_spec("is_colored=no").unwrap();
404        assert!(!config.is_colored);
405    }
406
407    #[rstest]
408    fn test_from_spec_is_colored_case_insensitive() {
409        let config = LoggerConfig::from_spec("IS_COLORED=FALSE").unwrap();
410        assert!(!config.is_colored);
411    }
412
413    #[rstest]
414    fn test_from_spec_print_config() {
415        let config = LoggerConfig::from_spec("print_config").unwrap();
416        assert!(config.print_config);
417    }
418
419    #[rstest]
420    fn test_from_spec_print_config_false() {
421        let config = LoggerConfig::from_spec("print_config=false").unwrap();
422        assert!(!config.print_config);
423    }
424
425    #[rstest]
426    fn test_from_spec_log_components_only() {
427        let config = LoggerConfig::from_spec("log_components_only").unwrap();
428        assert!(config.log_components_only);
429    }
430
431    #[rstest]
432    fn test_from_spec_log_components_only_false() {
433        let config = LoggerConfig::from_spec("log_components_only=false").unwrap();
434        assert!(!config.log_components_only);
435    }
436
437    #[rstest]
438    fn test_from_spec_fileout_sync_on_flush_false() {
439        let config = LoggerConfig::from_spec("fileout_sync_on_flush=false").unwrap();
440        assert!(!config.fileout_sync_on_flush);
441    }
442
443    #[rstest]
444    fn test_from_spec_buffered_stdout() {
445        let config = LoggerConfig::from_spec("buffered_stdout").unwrap();
446        assert!(config.buffered_stdout);
447    }
448
449    #[rstest]
450    fn test_from_spec_component_level() {
451        let config = LoggerConfig::from_spec("RiskEngine=Error;DataEngine=Debug").unwrap();
452        assert_eq!(
453            config.component_level[&Ustr::from("RiskEngine")],
454            LevelFilter::Error
455        );
456        assert_eq!(
457            config.component_level[&Ustr::from("DataEngine")],
458            LevelFilter::Debug
459        );
460    }
461
462    #[rstest]
463    fn test_from_spec_component_preserves_case() {
464        // Component names should preserve their original case
465        let config = LoggerConfig::from_spec("MyComponent=Info").unwrap();
466        assert!(
467            config
468                .component_level
469                .contains_key(&Ustr::from("MyComponent"))
470        );
471        assert!(
472            !config
473                .component_level
474                .contains_key(&Ustr::from("mycomponent"))
475        );
476    }
477
478    #[rstest]
479    fn test_from_spec_full_example() {
480        let config = LoggerConfig::from_spec(
481            "stdout=Info;fileout=Debug;RiskEngine=Error;is_colored;print_config",
482        )
483        .unwrap();
484
485        assert_eq!(config.stdout_level, LevelFilter::Info);
486        assert_eq!(config.fileout_level, LevelFilter::Debug);
487        assert_eq!(
488            config.component_level[&Ustr::from("RiskEngine")],
489            LevelFilter::Error
490        );
491        assert!(config.is_colored);
492        assert!(config.print_config);
493    }
494
495    #[rstest]
496    fn test_from_spec_disabled_colors() {
497        let config = LoggerConfig::from_spec("stdout=Info;is_colored=false;fileout=Debug").unwrap();
498        assert!(!config.is_colored);
499        assert_eq!(config.stdout_level, LevelFilter::Info);
500        assert_eq!(config.fileout_level, LevelFilter::Debug);
501    }
502
503    #[rstest]
504    fn test_from_spec_invalid_level() {
505        let result = LoggerConfig::from_spec("stdout=InvalidLevel");
506        assert!(result.is_err());
507        assert!(
508            result
509                .unwrap_err()
510                .to_string()
511                .contains("Invalid log level")
512        );
513    }
514
515    #[rstest]
516    fn test_from_spec_invalid_bare_flag() {
517        let result = LoggerConfig::from_spec("unknown_flag");
518        assert!(result.is_err());
519        assert!(
520            result
521                .unwrap_err()
522                .to_string()
523                .contains("Invalid spec pair")
524        );
525    }
526
527    #[rstest]
528    fn test_from_spec_missing_value() {
529        // "stdout=" with no value is technically valid empty string, which is invalid level
530        let result = LoggerConfig::from_spec("stdout=");
531        assert!(result.is_err());
532    }
533
534    #[rstest]
535    #[case("Off", LevelFilter::Off)]
536    #[case("Error", LevelFilter::Error)]
537    #[case("Warn", LevelFilter::Warn)]
538    #[case("Info", LevelFilter::Info)]
539    #[case("Debug", LevelFilter::Debug)]
540    #[case("Trace", LevelFilter::Trace)]
541    fn test_all_log_levels(#[case] level_str: &str, #[case] expected: LevelFilter) {
542        let config = LoggerConfig::from_spec(&format!("stdout={level_str}")).unwrap();
543        assert_eq!(config.stdout_level, expected);
544    }
545
546    #[rstest]
547    fn test_from_spec_single_module_path() {
548        let config = LoggerConfig::from_spec("nautilus_okx::websocket=Debug").unwrap();
549        assert_eq!(
550            config.module_level[&Ustr::from("nautilus_okx::websocket")],
551            LevelFilter::Debug
552        );
553        assert!(config.component_level.is_empty());
554    }
555
556    #[rstest]
557    fn test_from_spec_multiple_module_paths() {
558        let config =
559            LoggerConfig::from_spec("nautilus_okx::websocket=Debug;nautilus_binance::data=Trace")
560                .unwrap();
561        assert_eq!(
562            config.module_level[&Ustr::from("nautilus_okx::websocket")],
563            LevelFilter::Debug
564        );
565        assert_eq!(
566            config.module_level[&Ustr::from("nautilus_binance::data")],
567            LevelFilter::Trace
568        );
569        assert!(config.component_level.is_empty());
570    }
571
572    #[rstest]
573    fn test_from_spec_mixed_module_and_component() {
574        let config = LoggerConfig::from_spec(
575            "nautilus_okx::websocket=Debug;RiskEngine=Error;nautilus_network::data=Trace",
576        )
577        .unwrap();
578
579        assert_eq!(
580            config.module_level[&Ustr::from("nautilus_okx::websocket")],
581            LevelFilter::Debug
582        );
583        assert_eq!(
584            config.module_level[&Ustr::from("nautilus_network::data")],
585            LevelFilter::Trace
586        );
587        assert_eq!(config.module_level.len(), 2);
588        assert_eq!(
589            config.component_level[&Ustr::from("RiskEngine")],
590            LevelFilter::Error
591        );
592        assert_eq!(config.component_level.len(), 1);
593    }
594
595    #[rstest]
596    fn test_from_spec_deeply_nested_module_path() {
597        let config =
598            LoggerConfig::from_spec("nautilus_okx::websocket::handler::auth=Trace").unwrap();
599        assert_eq!(
600            config.module_level[&Ustr::from("nautilus_okx::websocket::handler::auth")],
601            LevelFilter::Trace
602        );
603    }
604
605    #[rstest]
606    fn test_from_spec_module_path_with_underscores() {
607        let config =
608            LoggerConfig::from_spec("nautilus_trader::adapters::interactive_brokers=Debug")
609                .unwrap();
610        assert_eq!(
611            config.module_level[&Ustr::from("nautilus_trader::adapters::interactive_brokers")],
612            LevelFilter::Debug
613        );
614    }
615
616    #[rstest]
617    fn test_from_spec_full_example_with_modules() {
618        let config = LoggerConfig::from_spec(
619            "stdout=Info;fileout=Debug;RiskEngine=Error;nautilus_okx::websocket=Trace;is_colored",
620        )
621        .unwrap();
622
623        assert_eq!(config.stdout_level, LevelFilter::Info);
624        assert_eq!(config.fileout_level, LevelFilter::Debug);
625        assert_eq!(
626            config.component_level[&Ustr::from("RiskEngine")],
627            LevelFilter::Error
628        );
629        assert_eq!(
630            config.module_level[&Ustr::from("nautilus_okx::websocket")],
631            LevelFilter::Trace
632        );
633        assert!(config.is_colored);
634    }
635
636    #[rstest]
637    fn test_from_spec_module_path_preserves_case() {
638        let config = LoggerConfig::from_spec("MyModule::SubModule=Info").unwrap();
639        assert!(
640            config
641                .module_level
642                .contains_key(&Ustr::from("MyModule::SubModule"))
643        );
644    }
645
646    #[rstest]
647    fn test_from_spec_single_colon_is_component() {
648        // Single colon is NOT a module path separator in Rust
649        let config = LoggerConfig::from_spec("Component:Name=Info").unwrap();
650        assert!(config.module_level.is_empty());
651        assert!(
652            config
653                .component_level
654                .contains_key(&Ustr::from("Component:Name"))
655        );
656    }
657
658    #[rstest]
659    fn test_default_module_level_is_empty() {
660        let config = LoggerConfig::default();
661        assert!(config.module_level.is_empty());
662    }
663
664    #[rstest]
665    fn test_from_spec_bypass_logging_bare() {
666        let config = LoggerConfig::from_spec("bypass_logging").unwrap();
667        assert!(config.bypass_logging);
668    }
669
670    #[rstest]
671    fn test_from_spec_bypass_logging_true() {
672        let config = LoggerConfig::from_spec("bypass_logging=true").unwrap();
673        assert!(config.bypass_logging);
674    }
675
676    #[rstest]
677    fn test_from_spec_bypass_logging_false() {
678        let config = LoggerConfig::from_spec("bypass_logging=false").unwrap();
679        assert!(!config.bypass_logging);
680    }
681
682    #[rstest]
683    fn test_toml_deserialize_minimal() {
684        let config: LoggerConfig = toml::from_str(
685            r#"
686stdout_level = "INFO"
687fileout_level = "DEBUG"
688is_colored = false
689
690[component_level]
691RiskEngine = "ERROR"
692
693[file_config]
694directory = "/var/log/nautilus"
695"#,
696        )
697        .unwrap();
698
699        assert_eq!(config.stdout_level, LevelFilter::Info);
700        assert_eq!(config.fileout_level, LevelFilter::Debug);
701        assert!(!config.is_colored);
702        assert_eq!(
703            config.component_level[&Ustr::from("RiskEngine")],
704            LevelFilter::Error
705        );
706        assert_eq!(
707            config.file_config.as_ref().unwrap().directory.as_deref(),
708            Some("/var/log/nautilus"),
709        );
710    }
711}