piquel-log 0.0.7

Small, composable backend initialization for tracing
Documentation

piquel-log

Small, composable backend initialization for tracing.

piquel-log is a backend helper for applications that want:

  • a simple Logger::new().init()? path
  • a logger handle that can evolve the backend stack at runtime
  • console output by default, with an option to disable it
  • optional file output behind a feature flag
  • optional queryable in-memory log storage behind a feature flag
  • optional log crate interoperability behind a feature flag
  • a single layer that can be attached to an existing tracing_subscriber stack

Features

  • default: console output
  • Logger::with_console(false): disable the console sink
  • file: file output with latest.log plus one session file per initialization
  • Logger::add_file_backend(...): add a file sink after initialization
  • store: thread-safe append-only in-memory log storage
  • Logger::add_store_backend(...): add a store sink after initialization
  • log: explicit log to tracing bridge during init
  • full: enables file, log, and store

Quick start

use piquel_log::Logger;

# fn main() -> Result<(), Box<dyn std::error::Error>> {
Logger::new().init()?;
tracing::info!("hello from tracing");
# Ok(())
# }

Disable console output

# #[cfg(feature = "file")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use piquel_log::{FileConfig, Logger};

let file = FileConfig::new("logs").with_session_file_prefix("app");
Logger::new()
    .with_console(false)
    .with_file(file)
    .init()?;
# Ok(())
# }
# #[cfg(not(feature = "file"))]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use piquel_log::Logger;

Logger::new().with_console(false).init()?;
# Ok(())
# }

Custom subscriber stacks

use piquel_log::Logger;
use tracing_subscriber::{filter::LevelFilter, prelude::*, Registry};

# fn main() -> Result<(), Box<dyn std::error::Error>> {
let backend = Logger::new().build()?;
let subscriber = Registry::default().with(LevelFilter::INFO).with(backend);
let _guard = tracing::subscriber::set_default(subscriber);
tracing::info!("hello from a custom stack");
# Ok(())
# }

File output

# #[cfg(feature = "file")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use piquel_log::{FileConfig, Logger};

let file = FileConfig::new("logs").with_session_file_prefix("app");
Logger::new().with_file(file).init()?;
# Ok(())
# }
# #[cfg(not(feature = "file"))]
# fn main() {}

Runtime backend updates

# #[cfg(feature = "file")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use piquel_log::{FileConfig, Logger};

let logger = Logger::new();
logger.init()?;

logger.add_file_backend(
    FileConfig::new("logs").with_session_file_prefix("runtime"),
)?;

tracing::info!("also written to the runtime file backend");
# Ok(())
# }
# #[cfg(not(feature = "file"))]
# fn main() {}

Queryable log store

# #[cfg(feature = "store")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use piquel_log::{LogFilter, LogLevel, LogStore, Logger};

let store = LogStore::new();

Logger::new()
    .with_console(false)
    .with_store(store.clone())
    .init()?;

tracing::warn!(target: "app::db", user = "alice", "slow query");

let entries = store.query(
    &LogFilter::new()
        .with_max_level(LogLevel::Warn)
        .with_target_prefix("app::"),
);

assert_eq!(entries.len(), 1);
# Ok(())
# }
# #[cfg(not(feature = "store"))]
# fn main() {}

The store is in-memory, append-only, thread-safe, and unbounded. It does not apply retention or eviction.

log crate interoperability

# #[cfg(feature = "log")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use piquel_log::Logger;

Logger::new().with_log_bridge(true).init()?;
log::warn!("bridged from log");
# Ok(())
# }
# #[cfg(not(feature = "log"))]
# fn main() {}