Skip to main content

LogScope

Struct LogScope 

Source
#[non_exhaustive]
pub struct LogScope { /* private fields */ }
Expand description

A guard that represents an active logging context on the current thread’s scope stack.

When the guard is dropped, the context is automatically removed from the stack. Created by LogScope::enter.

§Examples

use context_logger::{LogContext, LogScope};

// Create a context with some data
let context = LogContext::new().with_local_record("user_id", 123);

// Enter the context (pushes to stack)
let guard = LogScope::enter(context);

// Log operations here will have access to the context
// ...

// When `guard` goes out of scope, the context is automatically removed

Implementations§

Source§

impl LogScope

Source

pub fn enter(context: LogContext) -> Self

Pushes the given context onto the current thread’s scope stack and returns a guard.

The context remains active until the returned guard is dropped, at which point it is automatically removed from the stack.

§In Asynchronous Code

Warning: in asynchronous code Self::enter should be used very carefully or avoided entirely. Holding the drop guard across .await points will result in incorrect logs:

use context_logger::{LogContext, LogScope};

async fn my_async_fn() {
    let ctx = LogContext::new()
        .with_local_record("request_id", "req-123")
        .with_local_record("user_id", 42);
    // WARNING: This context will remain active until this
    // guard is dropped...
    let _guard = LogScope::enter(ctx);
    // But this code causing the runtime to switch to another task,
    // while remaining in this context.
    tokio::task::yield_now().await;
}

Please use the crate::FutureExt::in_log_context instead.

Source

pub fn in_scope<R>(context: LogContext, f: impl FnOnce() -> R) -> R

Enters the given context, runs a closure, and exits the scope automatically.

This is a convenience method for short synchronous sections where context should be active only during closure execution.

§Examples
use context_logger::{LogContext, LogScope};

let context = LogContext::new().with_local_record("request_id", "req-123");
let result = LogScope::in_scope(
    context,
    || 40 + 2,
);

assert_eq!(result, 42);
Source

pub fn add_record(key: impl Into<Cow<'static, str>>, value: impl Into<LogValue>)

Adds a record to the currently active scope.

This is useful for adding records dynamically without having direct access to the current scope.

§Note

If there is no active context, this operation will have no effect.

§Ordering

The order in which records appear in log output is not guaranteed. Do not rely on any specific ordering of keys.

§Examples
use context_logger::{LogContext, LogScope};
use log::info;

fn process_request() {
    // Add a record to the current scope dynamically
    LogScope::add_record("processing_time_ms", 42);
    info!("Request processed");
}

let _guard = LogScope::enter(LogContext::new()
    .with_local_record("request_id", "req-123"));

process_request(); // Will log with both request_id and processing_time_ms
Source

pub fn current_context() -> LogContext

Extracts the currently active logging context.

This is useful for propagating context when spawning new threads or async tasks, allowing child tasks to inherit logging information from the current scope.

§Example
use std::time::Duration;

use context_logger::{ContextLogger, FutureExt, LogContext, LogScope};

fn try_init_logger() -> Result<(), Box<dyn std::error::Error>> {
    let level = log::LevelFilter::Info;

    let logger = structured_logger::Builder::with_level(level.as_str())
        .with_target_writer("*", structured_logger::json::new_writer(std::io::stdout()))
        .build();
    ContextLogger::new(logger)
        .default_record("instance", "contexted_log_async")
        .try_init(level)?;

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    try_init_logger()?;

    log::info!("Initialized context logger");

    // Create a new context with properties.
    let log_context = LogContext::new()
        .with_local_record("user_id", "12345")
        .with_inherited_record("global_record", "example");

    count_with_tokio_spawn(7).in_log_context(log_context).await;

    log::info!("Finished counting");

    Ok(())
}

async fn count_with_tokio_spawn(counter: u64) {
    log::info!("Invoked a function with detached work");

    // The scope stack is thread-local, so the active context is not visible
    // inside `tokio::spawn` by default. Capture it here and pass it explicitly.
    let context = LogScope::current_context();
    let handle = tokio::spawn(
        async move {
            for i in 0..counter {
                log::info!(i; "Counting...");
                tokio::time::sleep(Duration::from_millis(250)).await;
            }
        }
        .in_log_context(context),
    );

    handle.await.unwrap();
}
§Notes
  • Returns an empty context if there is no active scope.
  • The returned LogContext is a clone of the active context, so it’s safe to move into spawned tasks.

Trait Implementations§

Source§

impl Debug for LogScope

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for LogScope

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.