timber_rust/
message.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Dante Doménech Martinez dante19031999@gmail.com
3
4#[cfg(feature = "json")]
5use serde_json::Value;
6use std::any::Any;
7use std::borrow::Cow;
8use std::error::Error;
9use std::fmt::Display;
10use std::time::SystemTime;
11
12/// A trait for [`Message`] implementations that can be formatted and downcast.
13///
14/// This trait is the core of the `rust_timber` extensibility. It allows the logger
15/// to handle standard text, JSON, or complex Error objects through type erasure.
16pub trait MessageImpl: Send + Sync + Any {
17    /// Returns the log level (e.g., "INFO", "DEBUG") as a displayable object.
18    /// Using `&dyn Display` ensures zero-copy for static string levels.
19    fn level(&self) -> &dyn Display;
20
21    /// Returns the message body content.
22    /// This allows for deferred formatting of complex types like JSON or Error objects.
23    fn content(&self) -> &dyn Display;
24
25    /// Returns the exact [SystemTime] when the message was created.
26    /// Crucial for maintaining chronological order in asynchronous logging.
27    fn instant(&self) -> SystemTime;
28
29    /// Returns a reference to `self` as a [`dyn Any`][std::any::Any] for downcasting purposes.
30    /// This allows services to recover the original concrete type if specialized processing is needed.
31    fn as_any(&self) -> &dyn Any;
32}
33
34/// A standard text-based log message.
35///
36/// Uses [`Cow<'static, str>`] to avoid heap allocations when using static string literals.
37pub struct StringMessageImpl {
38    level: Cow<'static, str>,
39    content: Cow<'static, str>,
40    instant: SystemTime,
41}
42
43impl MessageImpl for StringMessageImpl {
44    fn level(&self) -> &dyn Display {
45        &self.level
46    }
47
48    fn content(&self) -> &dyn Display {
49        &self.content
50    }
51
52    fn instant(&self) -> SystemTime {
53        self.instant
54    }
55
56    fn as_any(&self) -> &dyn Any {
57        self
58    }
59}
60
61impl StringMessageImpl {
62    /// Returns a reference to the underlying string content.
63    pub fn get_string(&self) -> &str {
64        &self.content
65    }
66}
67
68/// A message implementation that stores structured JSON data.
69///
70/// This is used when you want to pass raw data to a logger that supports
71/// structured output (like a database or an ELK stack).
72///
73/// # Feature Requirement
74/// Only available when the `json` feature is enabled.
75#[cfg(feature = "json")]
76#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
77pub struct JsonMessageImpl {
78    pub(crate) level: Cow<'static, str>,
79    pub(crate) content: Value,
80    instant: SystemTime,
81}
82
83#[cfg(feature = "json")]
84#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
85impl MessageImpl for JsonMessageImpl {
86    fn level(&self) -> &dyn Display {
87        &self.level
88    }
89
90    fn content(&self) -> &dyn Display {
91        &self.content
92    }
93
94    fn instant(&self) -> SystemTime {
95        self.instant
96    }
97
98    fn as_any(&self) -> &dyn Any {
99        self
100    }
101}
102
103#[cfg(feature = "json")]
104#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
105impl JsonMessageImpl {
106    /// Returns a reference to the underlying JSON value.
107    pub fn json(&self) -> &Value {
108        &self.content
109    }
110}
111
112/// A message containing a boxed Error object.
113///
114/// This allows capturing full stack traces and error chains while still
115/// satisfying the `Display` requirements of a logger.
116pub struct ErrorMessageImpl {
117    level: Cow<'static, str>,
118    content: Box<dyn Error + Send + Sync>,
119    instant: SystemTime,
120}
121
122impl MessageImpl for ErrorMessageImpl {
123    fn level(&self) -> &dyn Display {
124        &self.level
125    }
126
127    fn content(&self) -> &dyn Display {
128        &self.content
129    }
130
131    fn instant(&self) -> SystemTime {
132        self.instant
133    }
134
135    fn as_any(&self) -> &dyn Any {
136        self
137    }
138}
139
140impl ErrorMessageImpl {
141    /// Returns a reference to the inner trait object for detailed error inspection.
142    pub fn error(&self) -> &dyn Error {
143        &*self.content
144    }
145}
146
147/// The public-facing container for any log message.
148///
149/// This struct wraps a [`Box<dyn MessageImpl>`][MessageImpl], providing a uniform API
150/// regardless of the underlying data type.
151pub struct Message {
152    m_impl: Box<dyn MessageImpl + Send + Sync>,
153}
154
155impl Message {
156    /// Creates a new message from a concrete implementation.
157    /// The implementation is moved into a Box and becomes owned by the Message.
158    pub fn new(m_impl: Box<dyn MessageImpl + Send + Sync>) -> Self {
159        Message { m_impl }
160    }
161
162    /// Accesses the log level (e.g., "INFO").
163    /// Returns a dynamic reference to an object implementing Display.
164    pub fn level(&self) -> &dyn Display {
165        self.m_impl.level()
166    }
167
168    /// Accesses the message content.
169    /// The formatting is deferred until the Display trait is actually invoked.
170    pub fn content(&self) -> &dyn Display {
171        self.m_impl.content()
172    }
173
174    /// Returns the creation timestamp.
175    pub fn instant(&self) -> SystemTime {
176        self.m_impl.instant()
177    }
178
179    /// Accesses the underlying implementation for downcasting purposes.
180    /// This returns a reference to the trait object itself.
181    pub fn implementation(&self) -> &dyn MessageImpl {
182        &*self.m_impl
183    }
184
185    /// Unwraps the message and returns the implementation.
186    /// This returns the trait object itself.
187    pub fn unwrap(self) -> Box<dyn MessageImpl + Send + Sync> {
188        self.m_impl
189    }
190}
191
192/// The primary entry point for creating log messages.
193///
194/// Methods are designed to be "allocation-aware," using [`Cow`] to keep
195/// static string logging as fast as possible.
196pub struct MessageFactory {}
197
198impl MessageFactory {
199    /// Creates a [text-based message][`StringMessageImpl`].
200    ///
201    /// If passed `&'static str`, no heap allocation occurs for the strings.
202    pub fn string_msg<S1, S2>(level: S1, content: S2) -> Message
203    where
204        S1: Into<Cow<'static, str>>,
205        S2: Into<Cow<'static, str>>,
206    {
207        Message {
208            m_impl: Box::new(StringMessageImpl {
209                level: level.into(),
210                content: content.into(),
211                instant: SystemTime::now(),
212            }),
213        }
214    }
215
216    /// Creates a [json-based message][`JsonMessageImpl`] from a json `Value`.
217    ///
218    /// # Feature Requirement
219    /// Only available when the `json` feature is enabled.
220    #[cfg(feature = "json")]
221    pub fn json_msg<S>(level: S, content: Value) -> Message
222    where
223        S: Into<Cow<'static, str>>,
224    {
225        Message {
226            m_impl: Box::new(JsonMessageImpl {
227                level: level.into(),
228                content: content.into(),
229                instant: SystemTime::now(),
230            }),
231        }
232    }
233
234    /// Creates a [error-based message][`ErrorMessageImpl`] from a boxed `Error`.
235    pub fn error_msg<S>(level: S, content: Box<dyn Error + Send + Sync>) -> Message
236    where
237        S: Into<Cow<'static, str>>,
238    {
239        Message {
240            m_impl: Box::new(ErrorMessageImpl {
241                level: level.into(),
242                content,
243                instant: SystemTime::now(),
244            }),
245        }
246    }
247}