pub struct LoggedStream<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static, L: Logger + 'static> { /* private fields */ }Expand description
Wrapper for an IO object that logs every read, write, error, shutdown and drop that passes through it.
LoggedStream wraps an underlying IO object implementing the Read / Write traits, or
their asynchronous tokio analogues AsyncRead / AsyncWrite, and logs all read and
write operations, errors, shutdowns and drops. It re-implements the same IO trait it wraps, so
it is a drop-in replacement that works transparently in both synchronous and asynchronous code.
§Architecture
LoggedStream is generic over four independent, pluggable parts. Each logged event flows
through them in order: event -> Formatter -> Filter -> Logger.
- The inner IO object (
S). The stream you are wrapping. Any type implementingRead/Write(or thetokioasync equivalents) works — a socket, a file, an in-memory buffer, or your own type.LoggedStreamimplements the same IO traitSdoes, so it slots in whereverSwas used. - Formatter (
BufferFormatter). Turns the read and written byte buffers into the display strings you see in the log. - Filter (
RecordFilter). Decides which records are logged. It runs on every record kind, including shutdown and drop. - Logger (
Logger). The sink that consumes accepted records.
All three of BufferFormatter, RecordFilter and Logger are public, Send + 'static
and object-safe, with blanket implementations for Box<...> (and Arc<T> where T: Sync for
BufferFormatter). You are free to supply your own implementation of any part.
§Provided implementations
§Formatters (BufferFormatter)
Control how byte buffers are rendered. Each formatter stores a separator (default :) and
exposes parallel constructors: new, new_static, new_owned and new_default.
| Formatter | Renders each byte as |
|---|---|
LowercaseHexadecimalFormatter | lowercase hexadecimal — 0a:ff |
UppercaseHexadecimalFormatter | uppercase hexadecimal — 0A:FF |
DecimalFormatter | decimal — 10:255 |
OctalFormatter | octal — 012:377 |
BinaryFormatter | binary — 00001010:11111111 |
§Filters (RecordFilter)
Decide which records reach the logger.
| Filter | Behavior |
|---|---|
DefaultFilter | Accepts every record. |
RecordKindFilter | Accepts only the record kinds in an allow-list given at construction. |
AllFilter | AND — a record passes only if every child filter accepts it (an empty list accepts everything). |
AnyFilter | OR — a record passes if any child filter accepts it (an empty list rejects everything). |
§Loggers (Logger)
Consume each accepted record.
| Logger | Destination |
|---|---|
ConsoleLogger | Emits records through the log facade. |
FileLogger | Writes records to a file, one line per record. |
MemoryStorageLogger | Retains recent records in a bounded in-memory buffer. |
ChannelLogger | Sends records over an mpsc channel for handling elsewhere. |
ConsoleLogger and FileLogger additionally accept an optional prefix (with_prefix /
set_prefix, none by default), written verbatim before the record kind character of every line.
It tells apart several LoggedStreams — for example one per connection — that share a single
console or file:
[2026-07-20T12:34:56Z] [conn 5] > 01:02:03:04
[2026-07-20T12:34:56Z] [conn 7] < 05:06:07:08To let several FileLoggers write to one file, construct them with FileLogger::open, which
opens the file in append mode. Each record is rendered up front and written with a single
write_all call, so concurrent loggers never interleave parts of a line. Passing independently
opened non-append files (for example from File::create) instead gives each logger its own
starting offset, and they will silently overwrite each other’s records.
If none of the provided implementations matches your requirements, you can implement
BufferFormatter, RecordFilter or Logger yourself and pass your type to
LoggedStream exactly like a built-in.
Implementations§
Source§impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static, L: Logger + 'static> LoggedStream<S, Formatter, Filter, L>
impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static, L: Logger + 'static> LoggedStream<S, Formatter, Filter, L>
Sourcepub fn new(stream: S, formatter: Formatter, filter: Filter, logger: L) -> Self
pub fn new(stream: S, formatter: Formatter, filter: Filter, logger: L) -> Self
Construct a new instance of LoggedStream using provided arguments.
Sourcepub fn log_open(&mut self, message: impl Into<String>)
pub fn log_open(&mut self, message: impl Into<String>)
Emit a custom RecordKind::Open record carrying message.
RecordKind::Open is never produced automatically by the read, write, shutdown and drop
machinery — this method is the way to emit one. Use it to annotate the start of a stream,
for example to record the peer of a freshly established connection
("Established connection with 127.0.0.1:8080") or other per-stream metadata.
Like every other record, the Open record is passed through the filter before it reaches
the logger, so a RecordKindFilter that does not allow Open will suppress it. The message
is logged verbatim; it is not run through the formatter, which only applies to byte buffers.
For asynchronous streams, call this before splitting the wrapper with tokio::io::split,
since the resulting halves do not expose it.
Source§impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static> LoggedStream<S, Formatter, Filter, MemoryStorageLogger>
impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static> LoggedStream<S, Formatter, Filter, MemoryStorageLogger>
pub fn get_log_records(&self) -> VecDeque<Record>
pub fn clear_log_records(&mut self)
Source§impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static> LoggedStream<S, Formatter, Filter, ChannelLogger>
impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static> LoggedStream<S, Formatter, Filter, ChannelLogger>
pub fn take_receiver(&mut self) -> Option<Receiver<Record>>
pub fn take_receiver_unchecked(&mut self) -> Receiver<Record>
Trait Implementations§
Source§impl<S: AsyncRead + Unpin + 'static, Formatter: BufferFormatter + Unpin + 'static, Filter: RecordFilter + Unpin + 'static, L: Logger + Unpin + 'static> AsyncRead for LoggedStream<S, Formatter, Filter, L>
impl<S: AsyncRead + Unpin + 'static, Formatter: BufferFormatter + Unpin + 'static, Filter: RecordFilter + Unpin + 'static, L: Logger + Unpin + 'static> AsyncRead for LoggedStream<S, Formatter, Filter, L>
Source§impl<S: AsyncWrite + Unpin + 'static, Formatter: BufferFormatter + Unpin + 'static, Filter: RecordFilter + Unpin + 'static, L: Logger + Unpin + 'static> AsyncWrite for LoggedStream<S, Formatter, Filter, L>
impl<S: AsyncWrite + Unpin + 'static, Formatter: BufferFormatter + Unpin + 'static, Filter: RecordFilter + Unpin + 'static, L: Logger + Unpin + 'static> AsyncWrite for LoggedStream<S, Formatter, Filter, L>
Source§fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, Error>>
fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll<Result<usize, Error>>
buf into the object. Read moreSource§fn poll_flush(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Error>>
fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Result<(), Error>>
Source§fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Error>>
fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Result<(), Error>>
Source§fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize, Error>>
fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll<Result<usize, Error>>
poll_write, except that it writes from a slice of buffers. Read moreSource§fn is_write_vectored(&self) -> bool
fn is_write_vectored(&self) -> bool
poll_write_vectored
implementation. Read moreSource§impl<S: Debug + 'static, Formatter: Debug + 'static, Filter: RecordFilter + Debug + 'static, L: Logger + Debug + 'static> Debug for LoggedStream<S, Formatter, Filter, L>
impl<S: Debug + 'static, Formatter: Debug + 'static, Filter: RecordFilter + Debug + 'static, L: Logger + Debug + 'static> Debug for LoggedStream<S, Formatter, Filter, L>
Source§impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static, L: Logger + 'static> Drop for LoggedStream<S, Formatter, Filter, L>
impl<S: 'static, Formatter: 'static, Filter: RecordFilter + 'static, L: Logger + 'static> Drop for LoggedStream<S, Formatter, Filter, L>
Source§impl<S: Read + 'static, Formatter: BufferFormatter + 'static, Filter: RecordFilter + 'static, L: Logger + 'static> Read for LoggedStream<S, Formatter, Filter, L>
impl<S: Read + 'static, Formatter: BufferFormatter + 'static, Filter: RecordFilter + 'static, L: Logger + 'static> Read for LoggedStream<S, Formatter, Filter, L>
Source§fn read(&mut self, buf: &mut [u8]) -> Result<usize>
fn read(&mut self, buf: &mut [u8]) -> Result<usize>
1.36.0 · Source§fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize, Error>
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize, Error>
read, except that it reads into a slice of buffers. Read moreSource§fn is_read_vectored(&self) -> bool
fn is_read_vectored(&self) -> bool
can_vector)1.0.0 · Source§fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error>
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error>
buf. Read more1.0.0 · Source§fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error>
fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error>
buf. Read more1.6.0 · Source§fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>
buf. Read moreSource§fn read_buf(&mut self, buf: BorrowedCursor<'_, u8>) -> Result<(), Error>
fn read_buf(&mut self, buf: BorrowedCursor<'_, u8>) -> Result<(), Error>
read_buf)Source§fn read_buf_exact(
&mut self,
cursor: BorrowedCursor<'_, u8>,
) -> Result<(), Error>
fn read_buf_exact( &mut self, cursor: BorrowedCursor<'_, u8>, ) -> Result<(), Error>
read_buf)cursor. Read more1.0.0 · Source§fn by_ref(&mut self) -> &mut Selfwhere
Self: Sized,
fn by_ref(&mut self) -> &mut Selfwhere
Self: Sized,
Read. Read more1.0.0 · Source§fn chain<R>(self, next: R) -> Chain<Self, R> ⓘ
fn chain<R>(self, next: R) -> Chain<Self, R> ⓘ
1.0.0 · Source§fn take(self, limit: u64) -> Take<Self> ⓘwhere
Self: Sized,
fn take(self, limit: u64) -> Take<Self> ⓘwhere
Self: Sized,
limit bytes from it. Read moreSource§fn read_array<const N: usize>(&mut self) -> Result<[u8; N], Error>where
Self: Sized,
fn read_array<const N: usize>(&mut self) -> Result<[u8; N], Error>where
Self: Sized,
read_array)Source§impl<S: Write + 'static, Formatter: BufferFormatter + 'static, Filter: RecordFilter + 'static, L: Logger + 'static> Write for LoggedStream<S, Formatter, Filter, L>
impl<S: Write + 'static, Formatter: BufferFormatter + 'static, Filter: RecordFilter + 'static, L: Logger + 'static> Write for LoggedStream<S, Formatter, Filter, L>
Source§fn write(&mut self, buf: &[u8]) -> Result<usize>
fn write(&mut self, buf: &[u8]) -> Result<usize>
Source§fn flush(&mut self) -> Result<()>
fn flush(&mut self) -> Result<()>
Source§fn is_write_vectored(&self) -> bool
fn is_write_vectored(&self) -> bool
can_vector)1.0.0 · Source§fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
Source§fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
write_all_vectored)Auto Trait Implementations§
impl<S, Formatter, Filter, L> Freeze for LoggedStream<S, Formatter, Filter, L>
impl<S, Formatter, Filter, L> RefUnwindSafe for LoggedStream<S, Formatter, Filter, L>
impl<S, Formatter, Filter, L> Send for LoggedStream<S, Formatter, Filter, L>
impl<S, Formatter, Filter, L> Sync for LoggedStream<S, Formatter, Filter, L>
impl<S, Formatter, Filter, L> Unpin for LoggedStream<S, Formatter, Filter, L>
impl<S, Formatter, Filter, L> UnsafeUnpin for LoggedStream<S, Formatter, Filter, L>
impl<S, Formatter, Filter, L> UnwindSafe for LoggedStream<S, Formatter, Filter, L>
Blanket Implementations§
Source§impl<R> AsyncReadExt for R
impl<R> AsyncReadExt for R
Source§fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Read<'a, Self>where
Self: Unpin,
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Read<'a, Self>where
Self: Unpin,
Source§fn read_buf<'a, B>(&'a mut self, buf: &'a mut B) -> ReadBuf<'a, Self, B>
fn read_buf<'a, B>(&'a mut self, buf: &'a mut B) -> ReadBuf<'a, Self, B>
Source§fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExact<'a, Self>where
Self: Unpin,
fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExact<'a, Self>where
Self: Unpin,
buf. Read moreSource§fn read_u8(&mut self) -> ReadU8<&mut Self>where
Self: Unpin,
fn read_u8(&mut self) -> ReadU8<&mut Self>where
Self: Unpin,
Source§fn read_i8(&mut self) -> ReadI8<&mut Self>where
Self: Unpin,
fn read_i8(&mut self) -> ReadI8<&mut Self>where
Self: Unpin,
Source§fn read_u16(&mut self) -> ReadU16<&mut Self>where
Self: Unpin,
fn read_u16(&mut self) -> ReadU16<&mut Self>where
Self: Unpin,
Source§fn read_i16(&mut self) -> ReadI16<&mut Self>where
Self: Unpin,
fn read_i16(&mut self) -> ReadI16<&mut Self>where
Self: Unpin,
Source§fn read_u32(&mut self) -> ReadU32<&mut Self>where
Self: Unpin,
fn read_u32(&mut self) -> ReadU32<&mut Self>where
Self: Unpin,
Source§fn read_i32(&mut self) -> ReadI32<&mut Self>where
Self: Unpin,
fn read_i32(&mut self) -> ReadI32<&mut Self>where
Self: Unpin,
Source§fn read_u64(&mut self) -> ReadU64<&mut Self>where
Self: Unpin,
fn read_u64(&mut self) -> ReadU64<&mut Self>where
Self: Unpin,
Source§fn read_i64(&mut self) -> ReadI64<&mut Self>where
Self: Unpin,
fn read_i64(&mut self) -> ReadI64<&mut Self>where
Self: Unpin,
Source§fn read_u128(&mut self) -> ReadU128<&mut Self>where
Self: Unpin,
fn read_u128(&mut self) -> ReadU128<&mut Self>where
Self: Unpin,
Source§fn read_i128(&mut self) -> ReadI128<&mut Self>where
Self: Unpin,
fn read_i128(&mut self) -> ReadI128<&mut Self>where
Self: Unpin,
Source§fn read_f32(&mut self) -> ReadF32<&mut Self>where
Self: Unpin,
fn read_f32(&mut self) -> ReadF32<&mut Self>where
Self: Unpin,
Source§fn read_f64(&mut self) -> ReadF64<&mut Self>where
Self: Unpin,
fn read_f64(&mut self) -> ReadF64<&mut Self>where
Self: Unpin,
Source§fn read_u16_le(&mut self) -> ReadU16Le<&mut Self>where
Self: Unpin,
fn read_u16_le(&mut self) -> ReadU16Le<&mut Self>where
Self: Unpin,
Source§fn read_i16_le(&mut self) -> ReadI16Le<&mut Self>where
Self: Unpin,
fn read_i16_le(&mut self) -> ReadI16Le<&mut Self>where
Self: Unpin,
Source§fn read_u32_le(&mut self) -> ReadU32Le<&mut Self>where
Self: Unpin,
fn read_u32_le(&mut self) -> ReadU32Le<&mut Self>where
Self: Unpin,
Source§fn read_i32_le(&mut self) -> ReadI32Le<&mut Self>where
Self: Unpin,
fn read_i32_le(&mut self) -> ReadI32Le<&mut Self>where
Self: Unpin,
Source§fn read_u64_le(&mut self) -> ReadU64Le<&mut Self>where
Self: Unpin,
fn read_u64_le(&mut self) -> ReadU64Le<&mut Self>where
Self: Unpin,
Source§fn read_i64_le(&mut self) -> ReadI64Le<&mut Self>where
Self: Unpin,
fn read_i64_le(&mut self) -> ReadI64Le<&mut Self>where
Self: Unpin,
Source§fn read_u128_le(&mut self) -> ReadU128Le<&mut Self>where
Self: Unpin,
fn read_u128_le(&mut self) -> ReadU128Le<&mut Self>where
Self: Unpin,
Source§fn read_i128_le(&mut self) -> ReadI128Le<&mut Self>where
Self: Unpin,
fn read_i128_le(&mut self) -> ReadI128Le<&mut Self>where
Self: Unpin,
Source§fn read_f32_le(&mut self) -> ReadF32Le<&mut Self>where
Self: Unpin,
fn read_f32_le(&mut self) -> ReadF32Le<&mut Self>where
Self: Unpin,
Source§fn read_f64_le(&mut self) -> ReadF64Le<&mut Self>where
Self: Unpin,
fn read_f64_le(&mut self) -> ReadF64Le<&mut Self>where
Self: Unpin,
Source§fn read_to_end<'a>(&'a mut self, buf: &'a mut Vec<u8>) -> ReadToEnd<'a, Self>where
Self: Unpin,
fn read_to_end<'a>(&'a mut self, buf: &'a mut Vec<u8>) -> ReadToEnd<'a, Self>where
Self: Unpin,
buf. Read more