#![deny(missing_docs)]
use env_logger::filter::Builder as FilterBuilder;
use log::{set_boxed_logger, LevelFilter, SetLoggerError};
use logger::Configuration;
use parking_lot::RwLock;
use std::{fmt, io, sync::Arc, time::SystemTime};
use thiserror::Error;
mod events;
#[allow(dead_code)]
#[cfg(not(target_os = "windows"))]
mod logd;
mod logger;
#[cfg(target_os = "android")]
mod logging_iterator;
#[cfg(target_os = "android")]
mod pmsg;
mod thread;
pub use events::*;
pub use logger::Logger;
const LOGGER_ENTRY_MAX_LEN: usize = 5 * 1024;
#[derive(Error, Debug)]
pub enum Error {
#[error("IO error")]
Io(#[from] io::Error),
#[error("Event exceeds maximum size")]
EventSize,
#[error("Timestamp error: {0}")]
Timestamp(String),
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum Priority {
_Unknown = 0,
_Default = 1,
Verbose = 2,
Debug = 3,
Info = 4,
Warn = 5,
Error = 6,
_Fatal = 7,
_Silent = 8,
}
impl std::fmt::Display for Priority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let c = match self {
Priority::_Unknown => 'U',
Priority::_Default | Priority::Debug => 'D',
Priority::Verbose => 'V',
Priority::Info => 'I',
Priority::Warn => 'W',
Priority::Error => 'E',
Priority::_Fatal => 'F',
Priority::_Silent => 'S',
};
f.write_str(&c.to_string())
}
}
impl From<log::Level> for Priority {
fn from(l: log::Level) -> Priority {
match l {
log::Level::Error => Priority::Error,
log::Level::Warn => Priority::Warn,
log::Level::Info => Priority::Info,
log::Level::Debug => Priority::Debug,
log::Level::Trace => Priority::Verbose,
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum Buffer {
Main,
Radio,
Events,
System,
Crash,
Stats,
Security,
Custom(u8),
}
impl From<Buffer> for u8 {
fn from(b: Buffer) -> u8 {
match b {
Buffer::Main => 0,
Buffer::Radio => 1,
Buffer::Events => 2,
Buffer::System => 3,
Buffer::Crash => 4,
Buffer::Stats => 5,
Buffer::Security => 6,
Buffer::Custom(id) => id,
}
}
}
#[derive(Debug, Default, Clone)]
enum TagMode {
Target,
#[default]
TargetStrip,
Custom(String),
}
struct Record<'tag, 'msg> {
timestamp: SystemTime,
pid: u16,
thread_id: u16,
buffer_id: Buffer,
tag: &'tag str,
priority: Priority,
message: &'msg str,
}
pub fn builder() -> Builder {
Builder::default()
}
pub struct Builder {
filter: FilterBuilder,
tag: TagMode,
prepend_module: bool,
pstore: bool,
buffer: Option<Buffer>,
}
impl Default for Builder {
fn default() -> Self {
Self {
filter: FilterBuilder::default(),
tag: TagMode::default(),
prepend_module: false,
pstore: true,
buffer: None,
}
}
}
impl Builder {
pub fn new() -> Builder {
Builder::default()
}
pub fn buffer(&mut self, buffer: Buffer) -> &mut Self {
self.buffer = Some(buffer);
self
}
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.tag = TagMode::Custom(tag.to_string());
self
}
pub fn tag_target(&mut self) -> &mut Self {
self.tag = TagMode::Target;
self
}
pub fn tag_target_strip(&mut self) -> &mut Self {
self.tag = TagMode::TargetStrip;
self
}
pub fn prepend_module(&mut self, prepend_module: bool) -> &mut Self {
self.prepend_module = prepend_module;
self
}
pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
self.filter.filter_module(module, level);
self
}
pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
self.filter.filter_level(level);
self
}
pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self {
self.filter.filter(module, level);
self
}
pub fn parse_filters(&mut self, filters: &str) -> &mut Self {
self.filter.parse(filters);
self
}
#[cfg(target_os = "android")]
pub fn pstore(&mut self, log_to_pstore: bool) -> &mut Self {
self.pstore = log_to_pstore;
self
}
pub fn try_init(&mut self) -> Result<Logger, SetLoggerError> {
let configuration = Configuration {
filter: self.filter.build(),
tag: self.tag.clone(),
prepend_module: self.prepend_module,
pstore: self.pstore,
buffer_id: self.buffer.unwrap_or(Buffer::Main),
};
let max_level = configuration.filter.filter();
let configuration = Arc::new(RwLock::new(configuration));
let logger = Logger {
configuration: configuration.clone(),
};
let logger_impl = logger::LoggerImpl::new(configuration).expect("failed to build logger");
set_boxed_logger(Box::new(logger_impl))
.map(|_| {
log::set_max_level(max_level);
})
.map(|_| logger)
}
pub fn init(&mut self) -> Logger {
self.try_init()
.expect("Builder::init should not be called after logger initialized")
}
}
#[cfg(target_os = "android")]
pub fn log(
timestamp: SystemTime,
buffer_id: Buffer,
priority: Priority,
pid: u16,
thread_id: u16,
tag: &str,
message: &str,
) -> Result<(), Error> {
let record = Record {
timestamp,
pid,
thread_id,
buffer_id,
tag,
priority,
message,
};
logd::log(&record);
Ok(())
}
#[cfg(not(target_os = "android"))]
pub fn log(
timestamp: SystemTime,
buffer_id: Buffer,
priority: Priority,
pid: u16,
thread_id: u16,
tag: &str,
message: &str,
) -> Result<(), Error> {
let record = Record {
timestamp,
pid,
thread_id,
buffer_id,
tag,
priority,
message,
};
log_record(&record)
}
#[cfg(target_os = "android")]
fn log_record(record: &Record) -> Result<(), Error> {
logd::log(record);
Ok(())
}
#[cfg(not(target_os = "android"))]
fn log_record(record: &Record) -> Result<(), Error> {
use std::time::UNIX_EPOCH;
const DATE_TIME_FORMAT: &[time::format_description::FormatItem<'_>] =
time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]");
let Record {
timestamp,
tag,
priority,
message,
thread_id,
pid,
..
} = record;
let timestamp = timestamp
.duration_since(UNIX_EPOCH)
.map_err(|e| Error::Timestamp(e.to_string()))
.and_then(|ts| {
time::OffsetDateTime::from_unix_timestamp_nanos(ts.as_nanos() as i128).map_err(|e| Error::Timestamp(e.to_string()))
})
.and_then(|ts| ts.format(&DATE_TIME_FORMAT).map_err(|e| Error::Timestamp(e.to_string())))?;
eprintln!("{} {} {} {} {}: {}", timestamp, pid, thread_id, priority, tag, message);
Ok(())
}