logged_stream/lib.rs
1//! `logged-stream` provides a single wrapper type, [`LoggedStream`], that wraps any underlying IO
2//! object and logs every read, write, error, shutdown and drop that passes through it. The wrapper
3//! re-implements the same IO trait it wraps — [`Read`] / [`Write`], or the [`tokio`] asynchronous
4//! analogues [`AsyncRead`] / [`AsyncWrite`] — so it is a drop-in replacement for the stream it
5//! decorates and works transparently in both synchronous and asynchronous code.
6//!
7//! # Architecture
8//!
9//! [`LoggedStream`] is generic over four independent, pluggable parts. Each logged event flows
10//! through them in order: `event -> Formatter -> Filter -> Logger`.
11//!
12//! - **The inner IO object (`S`).** The stream you are wrapping. Any type implementing [`Read`] /
13//! [`Write`] (or the [`tokio`] async equivalents) works — a socket, a file, an in-memory buffer,
14//! or your own type. [`LoggedStream`] implements the same IO trait `S` does, so it slots in
15//! wherever `S` was used.
16//! - **Formatter ([`BufferFormatter`]).** Turns the read and written byte buffers into the display
17//! strings you see in the log.
18//! - **Filter ([`RecordFilter`]).** Decides which records are logged. It runs on every record kind,
19//! including shutdown and drop.
20//! - **Logger ([`Logger`]).** The sink that consumes accepted records.
21//!
22//! All three of [`BufferFormatter`], [`RecordFilter`] and [`Logger`] are public, `Send + 'static`
23//! and object-safe, with blanket implementations for `Box<...>` (and `Arc<T>` where `T: Sync` for
24//! [`BufferFormatter`]). You are free to supply your own implementation of any part.
25//!
26//! # Provided implementations
27//!
28//! ## Formatters ([`BufferFormatter`])
29//!
30//! Control how byte buffers are rendered. Each formatter stores a separator (default `:`) and
31//! exposes parallel constructors: `new`, `new_static`, `new_owned` and `new_default`.
32//!
33//! | Formatter | Renders each byte as |
34//! | --- | --- |
35//! | [`LowercaseHexadecimalFormatter`] | lowercase hexadecimal — `0a:ff` |
36//! | [`UppercaseHexadecimalFormatter`] | uppercase hexadecimal — `0A:FF` |
37//! | [`DecimalFormatter`] | decimal — `10:255` |
38//! | [`OctalFormatter`] | octal — `012:377` |
39//! | [`BinaryFormatter`] | binary — `00001010:11111111` |
40//!
41//! ## Filters ([`RecordFilter`])
42//!
43//! Decide which records reach the logger.
44//!
45//! | Filter | Behavior |
46//! | --- | --- |
47//! | [`DefaultFilter`] | Accepts every record. |
48//! | [`RecordKindFilter`] | Accepts only the record kinds in an allow-list given at construction. |
49//! | [`AllFilter`] | AND — a record passes only if every child filter accepts it (an empty list accepts everything). |
50//! | [`AnyFilter`] | OR — a record passes if any child filter accepts it (an empty list rejects everything). |
51//!
52//! ## Loggers ([`Logger`])
53//!
54//! Consume each accepted record.
55//!
56//! | Logger | Destination |
57//! | --- | --- |
58//! | [`ConsoleLogger`] | Emits records through the `log` facade. |
59//! | [`FileLogger`] | Writes records to a file, one line per record. |
60//! | [`MemoryStorageLogger`] | Retains recent records in a bounded in-memory buffer. |
61//! | [`ChannelLogger`] | Sends records over an `mpsc` channel for handling elsewhere. |
62//!
63//! [`ConsoleLogger`] and [`FileLogger`] additionally accept an optional prefix (`with_prefix` /
64//! `set_prefix`, none by default), written verbatim before the record kind character of every line.
65//! It tells apart several [`LoggedStream`]s — for example one per connection — that share a single
66//! console or file:
67//!
68//! ```text
69//! [2026-07-20T12:34:56Z] [conn 5] > 01:02:03:04
70//! [2026-07-20T12:34:56Z] [conn 7] < 05:06:07:08
71//! ```
72//!
73//! To let several [`FileLogger`]s write to one file, construct them with [`FileLogger::open`], which
74//! opens the file in append mode. Each record is rendered up front and written with a single
75//! `write_all` call, so concurrent loggers never interleave parts of a line. Passing independently
76//! opened non-append files (for example from `File::create`) instead gives each logger its own
77//! starting offset, and they will silently overwrite each other's records.
78//!
79//! If none of the provided implementations matches your requirements, you can implement
80//! [`BufferFormatter`], [`RecordFilter`] or [`Logger`] yourself and pass your type to
81//! [`LoggedStream`] exactly like a built-in.
82//!
83//! [`Write`]: std::io::Write
84//! [`Read`]: std::io::Read
85//! [`AsyncRead`]: tokio::io::AsyncRead
86//! [`AsyncWrite`]: tokio::io::AsyncWrite
87
88mod buffer_formatter;
89mod filter;
90mod logger;
91mod record;
92mod stream;
93
94pub use buffer_formatter::BinaryFormatter;
95pub use buffer_formatter::BufferFormatter;
96pub use buffer_formatter::DecimalFormatter;
97pub use buffer_formatter::LowercaseHexadecimalFormatter;
98pub use buffer_formatter::OctalFormatter;
99pub use buffer_formatter::UppercaseHexadecimalFormatter;
100pub use filter::AllFilter;
101pub use filter::AnyFilter;
102pub use filter::DefaultFilter;
103pub use filter::RecordFilter;
104pub use filter::RecordKindFilter;
105pub use logger::ChannelLogger;
106pub use logger::ConsoleLogger;
107pub use logger::FileLogger;
108pub use logger::Logger;
109pub use logger::MemoryStorageLogger;
110pub use record::Record;
111pub use record::RecordKind;
112pub use stream::LoggedStream;