BoundaryML/baml · error · LogError

Failed to acquire lock

Error message

Failed to acquire lock

What it means

LogError::LockError is a parameterless variant meaning the logger failed to acquire its internal lock (e.g. a Mutex/PoisonError or try_lock failure) guarding shared logging state. It renders as 'Failed to acquire lock'.

Solutions

  1. Find and fix any panic occurring while the logger lock is held (poisoned mutex)
  2. Retry the log call; the lock may be transiently unavailable
  3. Serialize logging through fewer writer threads or an async channel
  4. If persistent, reinitialize the logger to replace the poisoned state

Example fix

// before
let _guard = logger.lock().unwrap(); // panics if poisoned
// after
match logger.lock() {
    Ok(g) => { /* log */ },
    Err(_) => eprintln!("logger lock unavailable"),
}
Defensive patterns

Strategy: retry

Validate before calling

// Detect a poisoned logger lock before relying on logging
if logger.lock().is_err() {
    eprintln!("logger lock poisoned; reinitializing logger");
}

Type guard

fn is_lock_err(e: &LogError) -> bool { matches!(e, LogError::LockError) }

Try / catch

for attempt in 0..2 {
    match logger.log(record) {
        Err(LogError::LockError) if attempt == 0 => continue,
        Err(e) => { eprintln!("log failed: {e}"); break; }
        Ok(_) => break,
    }
}

Prevention

When it happens

Trigger: Concurrent logging contention where the lock cannot be acquired, or a lock poisoned by a panic in another thread while it was held.

Common situations: High-concurrency multithreaded apps logging simultaneously; a panic inside a logging call poisoning the mutex; using try_lock paths under heavy load.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/5b6455fcab198036. Report an issue: GitHub.

Appendix: source

Thrown at engine/baml-lib/baml-log/src/logger.rs:419

    static ref CONFIG: RwLock<LogConfig> = RwLock::new(LogConfig::from_env());
    static ref LOGGED_LINES: RwLock<HashSet<(Option<String>, Option<String>, Option<u32>)>> = RwLock::new(HashSet::new());
    /// Optional log file path — when set, log output goes to this file instead of stdout.
    static ref LOG_FILE: Mutex<Option<PathBuf>> = Mutex::new(None);
}

/// Error type for logging operations
#[derive(Debug, Error)]
pub enum LogError {
    /// Error writing to output
    #[error("IO error: {0}")]
    Io(#[from] io::Error),

    /// Error serializing to JSON
    #[error("JSON serialization error: {0}")]
    Json(#[from] serde_json::Error),

    /// Error acquiring lock
    #[error("Failed to acquire lock")]
    LockError,

    /// Configuration error
    #[error("Configuration error: {0}")]
    Config(String),
}

// /// JSON-serializable log entry
// #[derive(Serialize)]
// struct LogEntry<'a> {
//     /// Timestamp in ISO 8601 format
//     timestamp: String,
//     /// Log level as a string
//     level: &'a str,
//     /// Log message
//     message: String,
//     /// Optional module path
//     #[serde(skip_serializing_if = "Option::is_none")]

View on GitHub (pinned to bd85ce9dee)