timber_rust/service/
vector.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Dante Doménech Martinez dante19031999@gmail.com
3
4use crate::service::{ServiceError, StandardWriteMessageFormatter, WriteMessageFormatter};
5use crate::{Fallback, LoggerStatus, Message, Service};
6use std::any::Any;
7use std::sync::Mutex;
8
9/// A simple structured representation of a log message stored within a [`Vector`] service.
10///
11/// Unlike the dynamic [`Message`], this struct stores the level and content
12/// as owned [`String`]s, making it easy to inspect after the logger has finished.
13pub struct VectorMessage {
14    /// The string representation of the log level (e.g., "INFO").
15    pub level: String,
16    /// The formatted content of the log message.
17    pub message: String,
18}
19
20/// A logging [`Service`] that collects log entries into an in-memory [`Vec`].
21///
22/// This service is primarily used for **integration testing** or **UI feedback loops**,
23/// allowing you to capture every log generated by a specific operation and
24/// inspect them programmatically afterward.
25///
26/// ### Thread Safety
27/// The internal vector is protected by a [`Mutex`]. Multiple worker threads can
28/// push logs concurrently without data races or interleaving.
29pub struct Vector {
30    /// The thread-safe storage for captured log messages.
31    logs: Mutex<Vec<VectorMessage>>,
32}
33
34impl Vector {
35    /// Creates a new [`Vector`] service on the heap with a pre-allocated capacity.
36    ///
37    /// # Parameters
38    /// - `capacity`: The initial number of messages the vector can hold without reallocating.
39    pub fn new(capacity: usize) -> Box<Self> {
40        Box::new(Self {
41            logs: Mutex::new(Vec::with_capacity(capacity)),
42        })
43    }
44
45    /// Allows safe, read-only access to the captured logs without consuming the service.
46    ///
47    /// This is useful for "heartbeat" checks or assertions in tests while the logger
48    /// is still active.
49    ///
50    /// ### Returns
51    /// - [`Some(R)`][`Some`]: The result of the closure `f`.
52    /// - [`None`]: If the internal lock was poisoned.
53    pub fn inspect_vector<R>(&self, f: impl FnOnce(&Vec<VectorMessage>) -> R) -> Option<R> {
54        self.logs.lock().ok().map(|r| f(&*r))
55    }
56
57    /// Consumes the service and returns all captured log messages.
58    ///
59    /// This is the most efficient way to retrieve logs for final assertions
60    /// or post-processing, as it extracts the data from the mutex.
61    ///
62    /// # Errors
63    /// Returns [`ServiceError::LockPoisoned`] if a thread panicked while holding the lock.
64    pub fn recover_vector(self) -> Result<Vec<VectorMessage>, ServiceError> {
65        self.logs
66            .into_inner()
67            .map_err(|_| ServiceError::LockPoisoned)
68    }
69}
70
71impl Service for Vector {
72    /// Returns [`LoggerStatus::Running`].
73    fn status(&self) -> LoggerStatus {
74        LoggerStatus::Running
75    }
76
77    /// Transforms the [`Message`] into a [`VectorMessage`] and pushes it onto the internal stack.
78    ///
79    /// # Errors
80    /// Returns [`ServiceError::LockPoisoned`] if the internal mutex is unreachable.
81    fn work(&self, msg: &Message) -> Result<(), ServiceError> {
82        let mut logs = self.logs.lock().map_err(|_| ServiceError::LockPoisoned)?;
83        logs.push(VectorMessage {
84            level: msg.level().to_string(),
85            message: msg.content().to_string(),
86        });
87        Ok(())
88    }
89
90    fn as_any(&self) -> &dyn Any {
91        self
92    }
93}
94
95impl Fallback for Vector {
96    /// Attempts to log an error to `stdout` if the primary [`work`](Self::work) call fails.
97    ///
98    /// This method performs a best-effort write. If the mutex is locked by a hanging
99    /// thread, the fallback will be skipped to avoid a deadlock.
100    fn fallback(&self, error: &ServiceError, msg: &Message) {
101        let mut formatter = StandardWriteMessageFormatter::default();
102        let mut out = std::io::stdout();
103        let _ = formatter.format_io(msg, &mut out);
104        let _ = eprintln!("FmtWriteService Error: {}", error);
105    }
106}