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}