system_service/
logging.rs

1// Copyright 2018 OneSignal, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// 	http://www.apache.org/licenses/LICENSE-2.0
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.
14use std::io;
15use std::sync;
16
17use log;
18
19/// Additional requirents for CLI options to initialize the logging subsystem
20pub trait LogOptions {
21    /// Print a <number> indicating syslog level with each message
22    ///
23    /// Allows systemd to record in system log at appropriate level.
24    fn include_systemd_level(&self) -> bool {
25        false
26    }
27
28    /// Only messages with this prefix will be filtered. For instance, if the
29    /// package name is "foobar", returning a string "foobar" here will cause
30    /// only messages from the main package to be emitted.
31    fn target_filter(&self) -> Vec<String>;
32
33    /// Controls minimum level of messages to be logged.
34    ///
35    /// Messages lower than this level will not be printed.
36    fn max_log_level(&self) -> log::LevelFilter;
37}
38
39impl<'a> LogOptions for &'a LogOptions {
40    fn include_systemd_level(&self) -> bool {
41        (*self).include_systemd_level()
42    }
43
44    fn target_filter(&self) -> Vec<String> {
45        (*self).target_filter()
46    }
47
48    fn max_log_level(&self) -> log::LevelFilter {
49        (*self).max_log_level()
50    }
51}
52
53pub struct Logger<T> {
54    level: log::LevelFilter,
55    output: sync::Mutex<T>,
56    target_filter: Vec<String>,
57    include_systemd_level: bool,
58}
59
60impl<T: Send + io::Write> Logger<T> {
61    pub fn new<O: LogOptions>(
62        output: T,
63        options: &O,
64    ) -> Logger<io::LineWriter<T>> {
65        let level = options.max_log_level();
66        log::set_max_level(level);
67        Logger {
68            level: level,
69            output: sync::Mutex::new(io::LineWriter::new(output)),
70            target_filter: options.target_filter(),
71            include_systemd_level: options.include_systemd_level(),
72        }
73    }
74
75    /// Map a log level to a systemd level prefix
76    ///
77    /// Systemd can consume a leading numeric prefix in brackets to choose which
78    /// system log level to record the message at. The log crate's "Info" level
79    /// is mapped to "Notice" system level since they seem semantically
80    /// equivalent. Warning, error, and debug levels are as expected. Both Trace
81    /// and Debug are recorded at the syslog Debug level since there's no trace
82    /// level.
83    fn systemd_level(&self, record: &log::Record) -> &'static str {
84        use ::log::Level::*;
85        if self.include_systemd_level {
86            match record.level() {
87                Error => "<3> ",
88                Warn => "<4> ",
89                Info => "<5> ",
90                Debug => "<7> ",
91                Trace => "<7> ",
92            }
93        } else {
94            ""
95        }
96    }
97}
98
99impl<T: Send + io::Write> log::Log for Logger<T> {
100    fn enabled(&self, metadata: &log::Metadata) -> bool {
101        metadata.level() <= self.level
102    }
103
104    fn log(&self, record: &log::Record) {
105        if self.enabled(record.metadata()) &&
106            self.target_filter.iter().any(|t| record.target().starts_with(t))
107        {
108            let prefix = self.systemd_level(record);
109
110            if let Ok(ref mut writer) = self.output.lock() {
111                // Nothing we can do with an error here other than panic the
112                // program, and that doesn't sound great either.
113                let _ = writeln!(writer, "{}{}", prefix, record.args());
114            }
115        }
116    }
117
118    fn flush(&self) {
119        if let Ok(ref mut output) = self.output.lock() {
120            let _ = output.flush();
121        }
122    }
123}
124
125pub fn init<O: LogOptions>(options: &LogOptions) -> Result<(), log::SetLoggerError> {
126    // Use env_logger if RUST_LOG environment variable is defined. Otherwise,
127    // use the stdout program-only logger with optional systemd prefixing.
128    if ::std::env::var("RUST_LOG").is_ok() {
129        ::env_logger::try_init()
130    } else {
131        log::set_boxed_logger(Box::new(Logger::new(io::stdout(), &options)))
132    }
133}
134