Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/base/src/syslog.rs
5394 views
1
// Copyright 2022 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
//! Facilities for sending log message to syslog.
6
//!
7
//! Every function exported by this module is thread-safe. Each function will silently fail until
8
//! `syslog::init()` is called and returns `Ok`.
9
//!
10
//! This implements and sets logger up for logging facade exposed by the [`log`
11
//! crate][log-crate-url].
12
//!
13
//! # Examples
14
//!
15
//! ```
16
//! use log::{error, warn};
17
//! use base::syslog;
18
//!
19
//! if let Err(e) = syslog::init() {
20
//! println!("failed to initiailize syslog: {}", e);
21
//! return;
22
//! }
23
//! warn!("this is your {} warning", "final");
24
//! error!("something went horribly wrong: {}", "out of RAMs");
25
//! ```
26
//!
27
//! ```
28
//! use log::{error, warn};
29
//! use base::syslog::{init_with, LogConfig, fmt};
30
//! use std::io::Write;
31
//!
32
//! let mut cfg = LogConfig::default();
33
//! cfg.log_args.stderr = true;
34
//! cfg.log_args.filter = String::from("info,base=debug,base::syslog=error,serial_console=false");
35
//!
36
//! init_with(cfg).unwrap();
37
//! error!("something went horribly wrong: {}", "out of RAMs");
38
//! ```
39
//!
40
//!
41
//! [log-crate-url]: https://docs.rs/log/
42
43
use std::fmt::Display;
44
use std::io;
45
use std::io::Write;
46
use std::sync::LazyLock;
47
use std::sync::MutexGuard;
48
49
use chrono::Utc;
50
pub use env_logger::fmt;
51
pub use env_logger::{self};
52
pub use log::*;
53
use remain::sorted;
54
use serde::Deserialize;
55
use serde::Serialize;
56
use sync::Mutex;
57
use thiserror::Error as ThisError;
58
59
use crate::descriptor::AsRawDescriptor;
60
use crate::platform::syslog::PlatformSyslog;
61
use crate::platform::RawDescriptor;
62
63
/// The priority (i.e. severity) of a syslog message.
64
///
65
/// See syslog man pages for information on their semantics.
66
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
67
pub enum Priority {
68
Emergency = 0,
69
Alert = 1,
70
Critical = 2,
71
Error = 3,
72
Warning = 4,
73
Notice = 5,
74
Info = 6,
75
Debug = 7,
76
}
77
78
impl Display for Priority {
79
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
80
use self::Priority::*;
81
82
let string = match self {
83
Emergency => "EMERGENCY",
84
Alert => "ALERT",
85
Critical => "CRITICAL",
86
Error => "ERROR",
87
Warning => "WARNING",
88
Notice => "NOTICE",
89
Info => "INFO",
90
Debug => "DEBUG",
91
};
92
93
write!(f, "{string}")
94
}
95
}
96
97
impl From<log::Level> for Priority {
98
fn from(level: log::Level) -> Self {
99
match level {
100
log::Level::Error => Priority::Error,
101
log::Level::Warn => Priority::Warning,
102
log::Level::Info => Priority::Info,
103
log::Level::Debug => Priority::Debug,
104
log::Level::Trace => Priority::Debug,
105
}
106
}
107
}
108
109
impl TryFrom<&str> for Priority {
110
type Error = &'static str;
111
112
fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
113
match value {
114
"0" | "EMERGENCY" => Ok(Priority::Emergency),
115
"1" | "ALERT" => Ok(Priority::Alert),
116
"2" | "CRITICAL" => Ok(Priority::Critical),
117
"3" | "ERROR" => Ok(Priority::Error),
118
"4" | "WARNING" => Ok(Priority::Warning),
119
"5" | "NOTICE" => Ok(Priority::Notice),
120
"6" | "INFO" => Ok(Priority::Info),
121
"7" | "DEBUG" => Ok(Priority::Debug),
122
_ => Err("Priority can only be parsed from 0-7 and given variant names"),
123
}
124
}
125
}
126
/// The facility of a syslog message.
127
///
128
/// See syslog man pages for information on their semantics.
129
#[derive(Copy, Clone, serde::Deserialize, serde::Serialize)]
130
pub enum Facility {
131
Kernel = 0,
132
User = 1 << 3,
133
Mail = 2 << 3,
134
Daemon = 3 << 3,
135
Auth = 4 << 3,
136
Syslog = 5 << 3,
137
Lpr = 6 << 3,
138
News = 7 << 3,
139
Uucp = 8 << 3,
140
Local0 = 16 << 3,
141
Local1 = 17 << 3,
142
Local2 = 18 << 3,
143
Local3 = 19 << 3,
144
Local4 = 20 << 3,
145
Local5 = 21 << 3,
146
Local6 = 22 << 3,
147
Local7 = 23 << 3,
148
}
149
150
/// Errors returned by `syslog::init()`.
151
#[sorted]
152
#[derive(ThisError, Debug)]
153
pub enum Error {
154
/// Error while attempting to connect socket.
155
#[error("failed to connect socket: {0}")]
156
Connect(io::Error),
157
/// There was an error using `open` to get the lowest file descriptor.
158
#[error("failed to get lowest file descriptor: {0}")]
159
GetLowestFd(io::Error),
160
/// The guess of libc's file descriptor for the syslog connection was invalid.
161
#[error("guess of fd for syslog connection was invalid")]
162
InvalidFd,
163
/// Initialization was never attempted.
164
#[error("initialization was never attempted")]
165
NeverInitialized,
166
/// Initialization has previously failed and can not be retried.
167
#[error("initialization previously failed and cannot be retried")]
168
Poisoned,
169
/// Error while creating socket.
170
#[error("failed to create socket: {0}")]
171
Socket(io::Error),
172
}
173
174
pub(crate) trait Syslog {
175
fn new(
176
proc_name: String,
177
facility: Facility,
178
) -> Result<(Option<Box<dyn Log + Send>>, Option<RawDescriptor>), &'static Error>;
179
}
180
181
pub struct State {
182
/// Record filter
183
filter: env_logger::filter::Filter,
184
/// All the loggers we have
185
loggers: Vec<Box<dyn Log + Send>>,
186
/// Raw Descriptors to preserve
187
descriptors: Vec<RawDescriptor>,
188
/// True if we have just been initialized with safe startup defaults (stderr logging), false
189
/// after detailed initialization has occurred.
190
early_init: bool,
191
}
192
193
/// The logger that is provided to the `log` crate. Wraps our State struct so that we can
194
/// reconfigure logging sinks on the fly.
195
struct LoggingFacade {}
196
197
impl Log for LoggingFacade {
198
fn enabled(&self, metadata: &log::Metadata) -> bool {
199
STATE.lock().enabled(metadata)
200
}
201
202
fn log(&self, record: &log::Record) {
203
STATE.lock().log(record)
204
}
205
206
fn flush(&self) {
207
STATE.lock().flush()
208
}
209
}
210
211
#[derive(Clone, serde::Deserialize, serde::Serialize)]
212
pub struct LogArgs {
213
/// A filter for log messages. Please see
214
/// module level documentation and [`env_logger` crate](https://docs.rs/env_logger)
215
///
216
/// Example: `off`, `trace`, `trace,crosvm=error,base::syslog=debug`
217
pub filter: String,
218
/// If set to true will duplicate output to stderr
219
pub stderr: bool,
220
/// TAG to use for syslog output
221
pub proc_name: String,
222
/// Enable/disable platform's "syslog"
223
pub syslog: bool,
224
/// Facility to use for syslog output
225
pub syslog_facility: Facility,
226
}
227
228
impl Default for LogArgs {
229
fn default() -> Self {
230
Self {
231
filter: String::from("info"),
232
stderr: true,
233
proc_name: String::from("crosvm"),
234
syslog: true,
235
syslog_facility: Facility::User,
236
}
237
}
238
}
239
240
#[derive(Default)]
241
pub struct LogConfig {
242
/// Logging configuration arguments.
243
pub log_args: LogArgs,
244
/// If specified will output to given Sink
245
pub pipe: Option<Box<dyn io::Write + Send>>,
246
/// descriptor to preserve on forks (intended to be used with pipe)
247
pub pipe_fd: Option<RawDescriptor>,
248
/// A formatter to use with the pipe. (Syslog has hardcoded format)
249
/// see module level documentation and [`env_logger` crate](https://docs.rs/env_logger)
250
pub pipe_formatter: Option<
251
Box<dyn Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send>,
252
>,
253
}
254
255
impl State {
256
pub fn new(cfg: LogConfig) -> Result<Self, Error> {
257
let mut loggers: Vec<Box<dyn Log + Send>> = vec![];
258
let mut descriptors = vec![];
259
let mut builder = env_logger::filter::Builder::new();
260
builder.parse(&cfg.log_args.filter);
261
let filter = builder.build();
262
263
let create_formatted_builder = || {
264
let mut builder = env_logger::Builder::new();
265
266
// Output log lines w/ local ISO 8601 timestamps.
267
builder.format(|buf, record| {
268
writeln!(
269
buf,
270
"[{} {:5} {}] {}",
271
Utc::now().format("%Y-%m-%dT%H:%M:%S%.9f%:z"),
272
record.level(),
273
record.module_path().unwrap_or("<missing module path>"),
274
record.args()
275
)
276
});
277
builder
278
};
279
280
if cfg.log_args.stderr {
281
let mut builder = create_formatted_builder();
282
builder.filter_level(log::LevelFilter::Trace);
283
builder.target(env_logger::Target::Stderr);
284
loggers.push(Box::new(builder.build()));
285
descriptors.push(std::io::stderr().as_raw_descriptor());
286
}
287
288
if let Some(fd) = cfg.pipe_fd {
289
descriptors.push(fd);
290
}
291
292
if let Some(file) = cfg.pipe {
293
let mut builder = create_formatted_builder();
294
builder.filter_level(log::LevelFilter::Trace);
295
builder.target(env_logger::Target::Pipe(Box::new(file)));
296
// https://github.com/env-logger-rs/env_logger/issues/208
297
builder.is_test(true);
298
299
if let Some(format) = cfg.pipe_formatter {
300
builder.format(format);
301
}
302
loggers.push(Box::new(builder.build()));
303
}
304
305
if cfg.log_args.syslog {
306
match PlatformSyslog::new(cfg.log_args.proc_name, cfg.log_args.syslog_facility) {
307
Ok((mut logger, fd)) => {
308
if let Some(fd) = fd {
309
descriptors.push(fd);
310
}
311
if let Some(logger) = logger.take() {
312
loggers.push(logger);
313
}
314
}
315
Err(e) => {
316
// The default log configuration used in early_init() enables syslog, so we
317
// don't want to terminate the program if syslog can't be initialized. Warn the
318
// user but continue running.
319
eprintln!("syslog init failed: {e}");
320
}
321
}
322
}
323
324
Ok(State {
325
filter,
326
loggers,
327
descriptors,
328
early_init: false,
329
})
330
}
331
}
332
333
impl Default for State {
334
fn default() -> Self {
335
Self::new(Default::default()).unwrap()
336
}
337
}
338
339
static STATE: LazyLock<Mutex<State>> = LazyLock::new(|| {
340
let mut state = State::new(LogConfig::default()).expect("failed to configure minimal logging");
341
state.early_init = true;
342
Mutex::new(state)
343
});
344
static LOGGING_FACADE: LoggingFacade = LoggingFacade {};
345
static EARLY_INIT_CALLED: Mutex<bool> = Mutex::new(false);
346
347
/// Initialize the syslog connection and internal variables.
348
///
349
/// This should only be called once per process before any other threads have been spawned or any
350
/// signal handlers have been registered. Every call made after the first will panic.
351
///
352
/// Use `init_with_filter` to initialize with filtering
353
pub fn init() -> Result<(), Error> {
354
init_with(Default::default())
355
}
356
357
/// Initialize the syslog connection and internal variables.
358
///
359
/// This should only be called once per process before any other threads have been spawned or any
360
/// signal handlers have been registered. Every call made after the first will
361
/// panic.
362
///
363
/// Arguments:
364
/// * filter: See <https://docs.rs/env_logger/0.9/env_logger/index.html> for example filter
365
/// specifications
366
/// * stderr: If set will output to stderr (in addition)
367
/// * file: If set will output to this file (in addition)
368
/// * proc_name: proc name for Syslog implementation
369
/// * syslog_facility: syslog facility
370
/// * file_formatter: custom formatter for file output. See env_logger docs
371
pub fn init_with(cfg: LogConfig) -> Result<(), Error> {
372
let mut state = STATE.lock();
373
if !state.early_init {
374
panic!("double-init of the logging system is not permitted.");
375
}
376
*state = State::new(cfg)?;
377
378
// This has no effect if the logging facade was already set.
379
apply_logging_state(&LOGGING_FACADE);
380
381
Ok(())
382
}
383
384
/// Performs early (as in, moment of process start) logging initialization. Any logging prior to
385
/// this call will be SILENTLY discarded. Calling more than once per process will panic.
386
pub fn early_init() {
387
let mut early_init_called = EARLY_INIT_CALLED.lock();
388
if !*early_init_called {
389
apply_logging_state(&LOGGING_FACADE);
390
*early_init_called = true;
391
} else {
392
panic!("double early init of the logging system is not permitted.");
393
}
394
}
395
396
/// Test only function that ensures logging has been configured. Since tests
397
/// share module state, we need a way to make sure it has been initialized
398
/// with *some* configuration.
399
pub fn test_only_ensure_inited() -> Result<(), Error> {
400
let mut early_init_called = EARLY_INIT_CALLED.lock();
401
if !*early_init_called {
402
apply_logging_state(&LOGGING_FACADE);
403
*early_init_called = true;
404
}
405
Ok(())
406
}
407
408
fn apply_logging_state(facade: &'static LoggingFacade) {
409
let _ = log::set_logger(facade);
410
log::set_max_level(log::LevelFilter::Trace);
411
}
412
413
/// Retrieves the file descriptors owned by the global syslogger.
414
///
415
/// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
416
/// pushed into `fds`.
417
///
418
/// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
419
pub fn push_descriptors(fds: &mut Vec<RawDescriptor>) {
420
let state = STATE.lock();
421
fds.extend(state.descriptors.iter());
422
}
423
424
impl Log for State {
425
fn enabled(&self, metadata: &log::Metadata) -> bool {
426
self.filter.enabled(metadata)
427
}
428
429
fn log(&self, record: &log::Record) {
430
if self.filter.matches(record) {
431
for logger in self.loggers.iter() {
432
logger.log(record)
433
}
434
}
435
}
436
437
fn flush(&self) {
438
for logger in self.loggers.iter() {
439
logger.flush()
440
}
441
}
442
}
443
444
// Struct that implements io::Write to be used for writing directly to the syslog
445
pub struct Syslogger<'a> {
446
buf: Vec<u8>,
447
level: log::Level,
448
get_state_fn: Box<dyn Fn() -> MutexGuard<'a, State> + Send + 'a>,
449
}
450
451
impl<'a> Syslogger<'a> {
452
pub fn new(level: log::Level) -> Syslogger<'a> {
453
Syslogger {
454
buf: Vec::new(),
455
level,
456
get_state_fn: Box::new(|| STATE.lock()),
457
}
458
}
459
pub fn test_only_from_state<F: 'a + Fn() -> MutexGuard<'a, State> + Send>(
460
level: log::Level,
461
get_state_fn: F,
462
) -> Syslogger<'a> {
463
Syslogger {
464
buf: Vec::new(),
465
level,
466
get_state_fn: Box::new(get_state_fn),
467
}
468
}
469
}
470
471
impl io::Write for Syslogger<'_> {
472
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
473
let state = (self.get_state_fn)();
474
self.buf.extend_from_slice(buf);
475
476
if let Some(last_newline_idx) = self.buf.iter().rposition(|&x| x == b'\n') {
477
for line in (self.buf[..last_newline_idx]).split(|&x| x == b'\n') {
478
// Also drop CR+LF line endings.
479
let send_line = match line.split_last() {
480
Some((b'\r', trimmed)) => trimmed,
481
_ => line,
482
};
483
// Match is to explicitly limit lifetime of args
484
// https://github.com/rust-lang/rust/issues/92698
485
// https://github.com/rust-lang/rust/issues/15023
486
#[allow(clippy::match_single_binding)]
487
match format_args!("{}", String::from_utf8_lossy(send_line)) {
488
args => {
489
let mut record_builder = log::Record::builder();
490
record_builder.level(self.level);
491
record_builder.target("syslogger");
492
record_builder.args(args);
493
let record = record_builder.build();
494
state.log(&record);
495
}
496
}
497
}
498
499
self.buf.drain(..=last_newline_idx);
500
}
501
Ok(buf.len())
502
}
503
504
fn flush(&mut self) -> io::Result<()> {
505
STATE.lock().flush();
506
Ok(())
507
}
508
}
509
510