use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use std::time::SystemTime;
use crate::Error;
use crate::kv;
use crate::kv::KeyValues;
use crate::str::OwnedStr;
use crate::str::RefStr;
#[derive(Clone, Debug)]
pub struct Record<'a> {
now: SystemTime,
level: Level,
target: RefStr<'a>,
module_path: Option<RefStr<'a>>,
file: Option<RefStr<'a>>,
line: Option<u32>,
column: Option<u32>,
payload: OwnedStr,
kvs: KeyValues<'a>,
}
impl<'a> Record<'a> {
pub fn time(&self) -> SystemTime {
self.now
}
pub fn level(&self) -> Level {
self.level
}
pub fn target(&self) -> &'a str {
self.target.get()
}
pub fn target_static(&self) -> Option<&'static str> {
self.target.get_static()
}
pub fn module_path(&self) -> Option<&'a str> {
self.module_path.map(|s| s.get())
}
pub fn module_path_static(&self) -> Option<&'static str> {
self.module_path.and_then(|s| s.get_static())
}
pub fn file(&self) -> Option<&'a str> {
self.file.map(|s| s.get())
}
pub fn file_static(&self) -> Option<&'static str> {
self.file.and_then(|s| s.get_static())
}
pub fn filename(&self) -> Cow<'a, str> {
self.file()
.map(std::path::Path::new)
.and_then(std::path::Path::file_name)
.map(std::ffi::OsStr::to_string_lossy)
.unwrap_or_default()
}
pub fn line(&self) -> Option<u32> {
self.line
}
pub fn column(&self) -> Option<u32> {
self.column
}
pub fn payload(&self) -> &str {
self.payload.get()
}
pub fn payload_static(&self) -> Option<&'static str> {
self.payload.get_static()
}
pub fn key_values(&self) -> &KeyValues<'a> {
&self.kvs
}
pub fn to_owned(&self) -> RecordOwned {
RecordOwned {
now: self.now,
level: self.level,
target: self.target.into_owned(),
module_path: self.module_path.map(RefStr::into_owned),
file: self.file.map(RefStr::into_owned),
line: self.line,
column: self.column,
payload: self.payload.clone(),
kvs: self
.kvs
.iter()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect(),
}
}
pub fn to_builder(&self) -> RecordBuilder<'a> {
RecordBuilder {
record: Record {
now: self.now,
level: self.level,
target: self.target,
module_path: self.module_path,
file: self.file,
line: self.line,
column: self.column,
payload: self.payload.clone(),
kvs: self.kvs.clone(),
},
}
}
pub fn builder() -> RecordBuilder<'a> {
RecordBuilder::default()
}
}
#[derive(Debug)]
pub struct RecordBuilder<'a> {
record: Record<'a>,
}
impl Default for RecordBuilder<'_> {
fn default() -> Self {
RecordBuilder {
record: Record {
now: SystemTime::now(),
level: Level::Info,
target: RefStr::Static(""),
module_path: None,
file: None,
line: None,
column: None,
payload: OwnedStr::Static(""),
kvs: Default::default(),
},
}
}
}
impl<'a> RecordBuilder<'a> {
pub fn payload(mut self, payload: impl Into<Cow<'static, str>>) -> Self {
self.record.payload = match payload.into() {
Cow::Borrowed(s) => OwnedStr::Static(s),
Cow::Owned(s) => OwnedStr::Owned(s.into_boxed_str()),
};
self
}
pub fn level(mut self, level: Level) -> Self {
self.record.level = level;
self
}
pub fn target(mut self, target: &'a str) -> Self {
self.record.target = RefStr::Borrowed(target);
self
}
pub fn target_static(mut self, target: &'static str) -> Self {
self.record.target = RefStr::Static(target);
self
}
pub fn module_path(mut self, path: Option<&'a str>) -> Self {
self.record.module_path = path.map(RefStr::Borrowed);
self
}
pub fn module_path_static(mut self, path: &'static str) -> Self {
self.record.module_path = Some(RefStr::Static(path));
self
}
pub fn file(mut self, file: Option<&'a str>) -> Self {
self.record.file = file.map(RefStr::Borrowed);
self
}
pub fn file_static(mut self, file: &'static str) -> Self {
self.record.file = Some(RefStr::Static(file));
self
}
pub fn line(mut self, line: Option<u32>) -> Self {
self.record.line = line;
self
}
pub fn key_values(mut self, kvs: impl Into<KeyValues<'a>>) -> Self {
self.record.kvs = kvs.into();
self
}
pub fn build(self) -> Record<'a> {
self.record
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct FilterCriteria<'a> {
level: Level,
target: &'a str,
}
impl<'a> FilterCriteria<'a> {
pub fn level(&self) -> Level {
self.level
}
pub fn target(&self) -> &'a str {
self.target
}
pub fn to_builder(&self) -> FilterCriteriaBuilder<'a> {
FilterCriteriaBuilder {
metadata: FilterCriteria {
level: self.level,
target: self.target,
},
}
}
pub fn builder() -> FilterCriteriaBuilder<'a> {
FilterCriteriaBuilder::default()
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct FilterCriteriaBuilder<'a> {
metadata: FilterCriteria<'a>,
}
impl Default for FilterCriteriaBuilder<'_> {
fn default() -> Self {
FilterCriteriaBuilder {
metadata: FilterCriteria {
level: Level::Info,
target: "",
},
}
}
}
impl<'a> FilterCriteriaBuilder<'a> {
pub fn level(mut self, arg: Level) -> Self {
self.metadata.level = arg;
self
}
pub fn target(mut self, target: &'a str) -> Self {
self.metadata.target = target;
self
}
pub fn build(self) -> FilterCriteria<'a> {
self.metadata
}
}
#[derive(Clone, Debug)]
pub struct RecordOwned {
now: SystemTime,
level: Level,
target: OwnedStr,
module_path: Option<OwnedStr>,
file: Option<OwnedStr>,
line: Option<u32>,
column: Option<u32>,
payload: OwnedStr,
kvs: Vec<(kv::KeyOwned, kv::ValueOwned)>,
}
impl RecordOwned {
pub fn as_record(&self) -> Record<'_> {
Record {
now: self.now,
level: self.level,
target: self.target.by_ref(),
module_path: self.module_path.as_ref().map(OwnedStr::by_ref),
file: self.file.as_ref().map(OwnedStr::by_ref),
line: self.line,
column: self.column,
payload: self.payload.clone(),
kvs: KeyValues::from(self.kvs.as_slice()),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Level {
Trace = 1,
Trace2 = 2,
Trace3 = 3,
Trace4 = 4,
Debug = 5,
Debug2 = 6,
Debug3 = 7,
Debug4 = 8,
Info = 9,
Info2 = 10,
Info3 = 11,
Info4 = 12,
Warn = 13,
Warn2 = 14,
Warn3 = 15,
Warn4 = 16,
Error = 17,
Error2 = 18,
Error3 = 19,
Error4 = 20,
Fatal = 21,
Fatal2 = 22,
Fatal3 = 23,
Fatal4 = 24,
}
impl Level {
pub const fn name(&self) -> &'static str {
const LEVEL_NAMES: [&str; 24] = [
"TRACE", "TRACE2", "TRACE3", "TRACE4", "DEBUG", "DEBUG2", "DEBUG3", "DEBUG4", "INFO",
"INFO2", "INFO3", "INFO4", "WARN", "WARN2", "WARN3", "WARN4", "ERROR", "ERROR2",
"ERROR3", "ERROR4", "FATAL", "FATAL2", "FATAL3", "FATAL4",
];
LEVEL_NAMES[*self as usize - 1]
}
}
impl fmt::Debug for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(self.name())
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(self.name())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum LevelFilter {
Off,
Equal(Level),
NotEqual(Level),
MoreSevere(Level),
MoreSevereEqual(Level),
MoreVerbose(Level),
MoreVerboseEqual(Level),
All,
}
impl LevelFilter {
pub fn test(&self, level: Level) -> bool {
match self {
LevelFilter::Off => false,
LevelFilter::Equal(l) => level == *l,
LevelFilter::NotEqual(l) => level != *l,
LevelFilter::MoreSevere(l) => level > *l,
LevelFilter::MoreSevereEqual(l) => level >= *l,
LevelFilter::MoreVerbose(l) => level < *l,
LevelFilter::MoreVerboseEqual(l) => level <= *l,
LevelFilter::All => true,
}
}
}
impl FromStr for Level {
type Err = Error;
fn from_str(s: &str) -> Result<Level, Self::Err> {
for (repr, level) in [
("fatal", Level::Fatal),
("error", Level::Error),
("warn", Level::Warn),
("info", Level::Info),
("debug", Level::Debug),
("trace", Level::Trace),
("fatal2", Level::Fatal2),
("fatal3", Level::Fatal3),
("fatal4", Level::Fatal4),
("error2", Level::Error2),
("error3", Level::Error3),
("error4", Level::Error4),
("warn2", Level::Warn2),
("warn3", Level::Warn3),
("warn4", Level::Warn4),
("info2", Level::Info2),
("info3", Level::Info3),
("info4", Level::Info4),
("debug2", Level::Debug2),
("debug3", Level::Debug3),
("debug4", Level::Debug4),
("trace2", Level::Trace2),
("trace3", Level::Trace3),
("trace4", Level::Trace4),
] {
if s.eq_ignore_ascii_case(repr) {
return Ok(level);
}
}
Err(Error::new(format!("malformed level: {s:?}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_level() {
let levels = [
Level::Trace,
Level::Trace2,
Level::Trace3,
Level::Trace4,
Level::Debug,
Level::Debug2,
Level::Debug3,
Level::Debug4,
Level::Info,
Level::Info2,
Level::Info3,
Level::Info4,
Level::Warn,
Level::Warn2,
Level::Warn3,
Level::Warn4,
Level::Error,
Level::Error2,
Level::Error3,
Level::Error4,
Level::Fatal,
Level::Fatal2,
Level::Fatal3,
Level::Fatal4,
];
for &level in &levels {
let s = level.name();
let parsed = s.parse::<Level>().unwrap();
assert_eq!(level, parsed);
}
}
}