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
//! Ticklog is a fast, minimal logging library for latency-critical Rust
//! applications such as high-frequency trading, where the cost of a log call on
//! the hot path must stay in the low tens of nanoseconds.
//!
//! # How it works
//!
//! A logging macro runs entirely on the calling thread's **hot path**: it checks
//! the level, then encodes a compact binary record into that thread's private
//! lock-free buffer and returns. Format and source strings are stored by pointer
//! and arguments in their native form, so no text formatting happens here.
//!
//! A background **drain** thread does the rest: it decodes each record, formats
//! and timestamps it, and writes the text to a [`LogSink`], keeping all of that
//! cost off the calling thread.
//!
//! # Benchmarks
//!
//! Per-call latency on a Mac (M4, macOS 15, Rust 1.85, `release` profile).
//! Each measurement is the median of 200 criterion samples. Lower is better.
//!
//! | Logger | info!("x={}", 42u64) | info!("{}", "hello world") | info!("{} {} {}", 42u64, 3.14159, "hello world") |
//! |--------|----------:|----------:|--------:|
//! | **ticklog** | **8.0 ns** | **9.7 ns** | **11.6 ns** |
//! | env_logger | 231 ns | 232 ns | 307 ns |
//! | slog | 274 ns | 269 ns | 454 ns |
//! | tracing | 386 ns | 425 ns | 458 ns |
//!
//! # Quick start
//!
//! ```no_run
//! use ticklog::{info, FileSink};
//!
//! // Build the logger, then keep the returned guard alive for as long as you
//! // want to log.
//! let _guard = ticklog::builder()
//! .sink(FileSink::new("app.log").unwrap())
//! .build()
//! .unwrap();
//!
//! info!("listening on {}", 8080);
//! ```
//!
//! [`Builder::build`] may be called only once per process and returns a
//! [`Guard`]. **Hold the guard for as long as you want to log:** when it is
//! dropped it flushes the sink, stops the background drain thread, and disables
//! logging, so every log call afterwards is a silent no-op.
//!
//! # Logging macros
//!
//! [`trace!`], [`debug!`], [`info!`], [`warn!`], and [`error!`] take a format
//! string literal followed by positional arguments:
//!
//! ```no_run
//! # use ticklog::{info, error};
//! info!("connected to {}", "db-01");
//! error!("request {} failed after {} retries", 42, 3);
//! ```
//!
//! Placeholders are `{}` (Display) and `{:?}` (Debug). The number of
//! placeholders is checked against the number of arguments **at compile time**,
//! so a mismatch is a build error rather than a runtime surprise.
//!
//! # Configuration
//!
//! [`builder`] returns a [`Builder`] with these knobs:
//!
//! - [`sink`](Builder::sink): where output goes. Defaults to a [`ConsoleSink`]
//! on stderr (colored when stderr is a terminal).
//! - [`max_level`](Builder::max_level): the level ceiling; records above it are
//! dropped on the hot path before any encoding. Defaults to [`Level::Info`].
//! - [`backpressure`](Builder::backpressure): what a logging thread does when its
//! buffer is full, either [`Backpressure::Drop`] (the default, never blocks) or
//! [`Backpressure::Block`] (spin until space frees up).
//! - [`timezone_offset`](Builder::timezone_offset): offset applied when
//! formatting timestamps. Defaults to UTC.
//! - [`drain_affinity`](Builder::drain_affinity): pin the drain thread to a set
//! of logical CPUs.
//!
//! # Sinks
//!
//! A [`LogSink`] is the final destination for formatted lines. The crate ships:
//!
//! - [`ConsoleSink`]: stdout or stderr, with automatic or forced ANSI coloring
//! by level (see [`ColorMode`]).
//! - [`FileSink`]: a buffered single file, opened in append or truncate mode.
//! - [`WriterSink`]: wraps any [`std::io::Write`]; the escape hatch for custom
//! destinations such as rotating files or the network.
//!
//! Compose and filter sinks with [`FanOut`] (dispatch one record to several
//! sinks) and [`LogSinkExt::with_max_level`] (limit a sink to a level and below):
//!
//! ```
//! use ticklog::{ConsoleSink, FanOut, Level, LogSinkExt};
//!
//! let sink = FanOut::new()
//! .add(ConsoleSink::stderr().with_max_level(Level::Warn))
//! .add(ConsoleSink::stdout().with_max_level(Level::Info));
//! ```
//!
//! # Threads
//!
//! Any thread may log, and each allocates its own buffer on first use. To
//! move that one-time allocation off a latency-sensitive path, call [`warm_up`]
//! on the thread before its first log call.
//!
//! # Safety
//!
//! The public API contains no `unsafe` functions. Internally, `unsafe` is
//! confined to three areas, each with a documented invariant:
//!
//! - **Thread-local buffer access:** per-thread buffers live in an
//! [`UnsafeCell`](std::cell::UnsafeCell) guarded by a re-entrancy flag. A
//! re-entrant log call on the same thread is detected and refused before it
//! can form a second mutable reference, preventing aliasing UB.
//! - **Lock-free ring buffer:** the buffer shared between the calling thread
//! and the drain thread uses atomic ordering to coordinate access without
//! locks. The calling thread only writes; the drain thread only reads.
//! - **Affinity syscalls:** platform thread-affinity calls require raw pointer
//! and FFI usage, gated behind `#[cfg(target_os)]`.
pub use pin_thread;
pub use ;
pub use TicklogError;
pub use Guard;
pub use Level;
pub use ;
pub use warm_up;
/// Internal helpers used by the logging macros. Not part of the public API and
/// exempt from semver guarantees; referenced only through `$crate::__private`
/// in macro expansions.