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