log_io/lib.rs
1//! Structured logging pipeline for Rust.
2//!
3//! `log-io` moves structured log records through three stages: a
4//! [`Filter`] decides whether a record is wanted, a
5//! [`Format`] serializes it (JSON, logfmt, or a
6//! human-readable line), and one or more [`Sink`]s deliver the bytes.
7//! The steady-state hot path performs no heap allocation, the data
8//! model and formatters work under `no_std`, and the default build has
9//! zero dependencies. Optional bridges plug the pipeline into the `log`
10//! and `tracing` ecosystems.
11//!
12//! # Quick start
13//!
14#![cfg_attr(all(feature = "std", feature = "json"), doc = "```")]
15#![cfg_attr(not(all(feature = "std", feature = "json")), doc = "```ignore")]
16//! use log_io::{Level, Logger};
17//!
18//! let logger = Logger::builder()
19//! .level(Level::Info)
20//! .env_filter("LOG_IO") // e.g. LOG_IO="info,my_app::db=debug"
21//! .stdout_json()
22//! .build();
23//! let _guard = log_io::init(logger)?; // flushes on drop
24//!
25//! let port = 8080_u16;
26//! log_io::info!("listening on port {port}", tls = true);
27//! log_io::warn!(target: "my_app::db", "slow query", ms = 412_u64, table = "users");
28//! # Ok::<(), log_io::SetGlobalError>(())
29//! ```
30//!
31//! # Features
32//!
33//! | Feature | Default | Enables |
34//! |---|---|---|
35//! | `std` | yes | [`Logger`], sinks, [`context`], timestamps, the global logger |
36//! | `json` | yes | [`format::JsonFormat`] |
37//! | `logfmt` | yes | [`format::LogfmtFormat`] |
38//! | `human` | yes | [`format::HumanFormat`] (works under `no_std`) |
39//! | `log` | no | `compat::LogBridge`, a `log::Log` implementation (adds the `log` crate) |
40//! | `tracing` | no | `compat::TracingLayer`, a `tracing-subscriber` layer (adds `tracing-core` and `tracing-subscriber`) |
41//!
42//! Without `std` the crate is `no_std` (no `alloc` needed): [`Record`],
43//! [`Field`], [`Value`], [`Level`] and the three formatters, which write
44//! to any [`core::fmt::Write`].
45//!
46//! # Threading and async
47//!
48//! [`Logger`] and every sink are `Send + Sync`. The built-in synchronous
49//! sinks write on the calling thread under a mutex, so inside an async
50//! runtime they block the executor thread for the duration of the
51//! write; [`sink::NonBlocking`] moves the write to a background thread.
52//! Context is thread-local; [`context::ContextSnapshot`] carries it into
53//! tasks and other threads.
54//!
55//! # Durability
56//!
57//! Records at or above the flush level (default [`Level::Error`]) are
58//! flushed to every sink before the logging call returns. The one
59//! exception is a [`sink::NonBlocking`] sink with
60//! [`Overflow::DropNewest`](sink::Overflow::DropNewest): it only queues
61//! that flush so the call never waits, and a [`FlushGuard`] drop or
62//! [`Logger::flush`] still drains it. Everything else may sit in a buffer until a flush, a [`FlushGuard`] drop, or the
63//! last [`Logger`] clone being dropped. See [`sink::FileSink`] for the
64//! exit paths that lose buffered data and how to avoid them.
65//!
66//! # Allocation
67//!
68//! Emitting a record through the built-in synchronous sinks does not
69//! allocate in steady state, including timestamps, thread-local
70//! context, default fields, and `format!`-style messages, as long as at
71//! most 16 ambient (default + context) fields are attached. Creating a
72//! context guard, capturing a context snapshot, and queueing a record on
73//! [`sink::NonBlocking`] may allocate. `tests/alloc.rs` enforces this
74//! with a counting allocator.
75//!
76//! # Stability
77//!
78//! Semantic versioning. `Value`, `Error`, `Metadata`, `Record` and the
79//! configuration enums are `#[non_exhaustive]`, so they can grow in
80//! minor releases. See `docs/MIGRATION.md` for upgrading from 1.x.
81
82#![doc(html_root_url = "https://docs.rs/log-io")]
83#![cfg_attr(docsrs, feature(doc_cfg))]
84#![cfg_attr(not(feature = "std"), no_std)]
85#![forbid(unsafe_code)]
86#![deny(warnings)]
87#![deny(missing_docs)]
88#![deny(unused_must_use)]
89#![deny(unused_results)]
90#![deny(clippy::unwrap_used)]
91#![deny(clippy::expect_used)]
92#![deny(clippy::todo)]
93#![deny(clippy::unimplemented)]
94#![deny(clippy::print_stdout)]
95#![deny(clippy::print_stderr)]
96#![deny(clippy::dbg_macro)]
97#![deny(clippy::unreachable)]
98#![deny(clippy::undocumented_unsafe_blocks)]
99#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
100// The crate docs link to items behind every feature flag. Those links
101// can only resolve when all features are enabled (as on docs.rs and in
102// the CI docs job, which keep this lint at `deny`).
103#![cfg_attr(
104 not(all(
105 feature = "std",
106 feature = "json",
107 feature = "logfmt",
108 feature = "human",
109 feature = "log",
110 feature = "tracing"
111 )),
112 allow(rustdoc::broken_intra_doc_links)
113)]
114
115/// Crate version string, populated by Cargo at build time.
116pub const VERSION: &str = env!("CARGO_PKG_VERSION");
117
118mod level;
119mod record;
120mod value;
121
122pub use crate::level::{Level, ParseLevelError};
123pub use crate::record::{Field, Metadata, Record};
124pub use crate::value::Value;
125
126pub mod format;
127pub use crate::format::Format;
128
129#[cfg(feature = "std")]
130mod builder;
131#[cfg(feature = "std")]
132mod error;
133#[cfg(feature = "std")]
134mod filter;
135#[cfg(feature = "std")]
136mod logger;
137#[cfg(feature = "std")]
138mod macros;
139#[cfg(feature = "std")]
140pub(crate) mod time;
141
142#[cfg(feature = "std")]
143pub mod context;
144#[cfg(feature = "std")]
145pub mod global;
146#[cfg(feature = "std")]
147pub mod sink;
148
149#[cfg(any(feature = "log", feature = "tracing"))]
150pub mod compat;
151
152#[cfg(feature = "std")]
153pub use crate::builder::{BuildError, LoggerBuilder};
154#[cfg(feature = "std")]
155pub use crate::error::{Error, Result};
156#[cfg(feature = "std")]
157pub use crate::filter::{Filter, FilterRule, ParseFilterError, ParseFilterErrorKind};
158#[cfg(all(feature = "std", feature = "human"))]
159pub use crate::global::init_human;
160#[cfg(all(feature = "std", feature = "json"))]
161pub use crate::global::init_json;
162#[cfg(all(feature = "std", feature = "logfmt"))]
163pub use crate::global::init_logfmt;
164#[cfg(feature = "std")]
165pub use crate::global::{FlushGuard, InitError, SetGlobalError, init, set as set_global};
166#[cfg(feature = "std")]
167pub use crate::logger::{ErrorHandler, Logger, REDACTED};
168#[cfg(feature = "std")]
169pub use crate::sink::Sink;
170
171#[cfg(feature = "std")]
172#[doc(hidden)]
173pub use crate::macros::__private;
174
175// Compile and run every Rust code block in the README and the API
176// reference as doctests, so the documentation cannot drift from the
177// code. Needs every feature because the examples cover all of them.
178#[cfg(all(
179 doctest,
180 feature = "std",
181 feature = "json",
182 feature = "logfmt",
183 feature = "human",
184 feature = "log",
185 feature = "tracing"
186))]
187mod external_doc_examples {
188 #[doc = include_str!("../docs/API.md")]
189 mod api_md {}
190 #[doc = include_str!("../README.md")]
191 mod readme_md {}
192}