1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Logger implementation for low level kernel log (using `/dev/kmsg`)
//!
//! Usually intended for low level implementations, like [systemd generators][1],
//! which have to use `/dev/kmsg`:
//!
//! > Since syslog is not available (see above) write log messages to /dev/kmsg instead.
//!
//! [1]: http://www.freedesktop.org/wiki/Software/systemd/Generators/
//!
//! # Examples
//!
//! ```toml
//! [dependencies]
//! log = "0.4"
//! kernlog = "0.3"
//! ```
//! 
//! ```rust
//! #[macro_use]
//! extern crate log;
//! extern crate kernlog;
//! 
//! fn main() {
//!     kernlog::init().unwrap();
//!     warn!("something strange happened");
//! }
//! ```
//! Note you have to have permissions to write to `/dev/kmsg`,
//! which normal users (not root) usually don't.
//! 
//! If compiled with nightly it can use libc feature to get process id
//! and report it into log. This feature is unavailable for stable release
//! for now. To enable nightly features, compile with `--features nightly`:
//!
//! ```toml
//! [dependencies.kernlog]
//! version = "*"
//! features = ["nightly"]
//! ```

#![deny(missing_docs)]
#![cfg_attr(feature="nightly", feature(libc))]

#[macro_use]
extern crate log;
extern crate libc;

use std::fs::{OpenOptions, File};
use std::io::{Write, self};
use std::sync::Mutex;
use std::env;

use log::{Log, Metadata, Record, Level, LevelFilter, SetLoggerError};

/// Kernel logger implementation
pub struct KernelLog {
    kmsg: Mutex<File>,
    maxlevel: LevelFilter
}

impl KernelLog {
    /// Create new kernel logger
    pub fn new() -> io::Result<KernelLog> {
        KernelLog::with_level(LevelFilter::Trace)
    }

    /// Create new kernel logger from `KERNLOG_LEVEL` environment variable
    pub fn from_env() -> io::Result<KernelLog> {
        match env::var("KERNLOG_LEVEL") {
            Err(_) => KernelLog::new(),
            Ok(s) => match s.parse() {
                Ok(filter) => KernelLog::with_level(filter),
                Err(_) => KernelLog::new(),
            }
        }
    }

    /// Create new kernel logger with error level filter
    pub fn with_level(filter: LevelFilter) -> io::Result<KernelLog> {
        Ok(KernelLog {
            kmsg: Mutex::new(OpenOptions::new().write(true).open("/dev/kmsg")?),
            maxlevel: filter
        })
    }
}

impl Log for KernelLog {
    fn enabled(&self, meta: &Metadata) -> bool {
        meta.level() <= self.maxlevel
    }

    fn log(&self, record: &Record) {
        if record.level() > self.maxlevel {
            return;
        }

        let level: u8 = match record.level() {
            Level::Error => 3,
            Level::Warn => 4,
            Level::Info => 5,
            Level::Debug => 6,
            Level::Trace => 7,
        };

        let mut buf = Vec::new();
        writeln!(buf, "<{}>{}[{}]: {}", level, record.target(),
                 unsafe { ::libc::getpid() },
                 record.args()).unwrap();

        if let Ok(mut kmsg) = self.kmsg.lock() {
            let _ = kmsg.write(&buf);
            let _ = kmsg.flush();
        }
    }

    fn flush(&self) {}
}

/// KernelLog initialization error
#[derive(Debug)]
pub enum KernelLogInitError {
    /// IO error
    Io(io::Error),
    /// Set logger error
    Log(SetLoggerError)
}

impl std::fmt::Display for KernelLogInitError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            KernelLogInitError::Io(err) => err.fmt(f),
            KernelLogInitError::Log(err) => err.fmt(f),
        }
    }
}

impl std::error::Error for KernelLogInitError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            KernelLogInitError::Io(err) => Some(err),
            KernelLogInitError::Log(err) => Some(err),
        }
    }
}

impl From<SetLoggerError> for KernelLogInitError {
    fn from(err: SetLoggerError) -> Self {
        KernelLogInitError::Log(err)
    }
}
impl From<io::Error> for KernelLogInitError {
    fn from(err: io::Error) -> Self {
        KernelLogInitError::Io(err)
    }
}

/// Setup kernel logger as a default logger
pub fn init() -> Result<(), KernelLogInitError> {
    let klog = KernelLog::from_env()?;
    let maxlevel = klog.maxlevel;
    log::set_boxed_logger(Box::new(klog))?;
    log::set_max_level(maxlevel);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{KernelLog, init};

    #[test]
    fn log_to_kernel() {
        init().unwrap();
        debug!("hello, world!");
    }
}