1use core::fmt;
37
38use crate::record::Record;
39
40pub trait Format: Send + Sync {
81 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result;
88
89 #[inline]
99 fn write_record_generic<W: fmt::Write>(
100 &self,
101 record: &Record<'_>,
102 writer: &mut W,
103 ) -> fmt::Result
104 where
105 Self: Sized,
106 {
107 self.write_record(record, writer)
108 }
109}
110
111impl<T: Format> Format for &T {
112 #[inline]
113 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result {
114 (**self).write_record(record, writer)
115 }
116
117 #[inline]
118 fn write_record_generic<W: fmt::Write>(
119 &self,
120 record: &Record<'_>,
121 writer: &mut W,
122 ) -> fmt::Result {
123 (**self).write_record_generic(record, writer)
124 }
125}
126
127impl Format for &dyn Format {
128 #[inline]
129 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result {
130 (**self).write_record(record, writer)
131 }
132}
133
134#[cfg(feature = "std")]
135impl<T: Format> Format for std::sync::Arc<T> {
136 #[inline]
137 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result {
138 (**self).write_record(record, writer)
139 }
140
141 #[inline]
142 fn write_record_generic<W: fmt::Write>(
143 &self,
144 record: &Record<'_>,
145 writer: &mut W,
146 ) -> fmt::Result {
147 (**self).write_record_generic(record, writer)
148 }
149}
150
151#[cfg(feature = "std")]
152impl Format for std::sync::Arc<dyn Format> {
153 #[inline]
154 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result {
155 (**self).write_record(record, writer)
156 }
157}
158
159#[cfg(feature = "std")]
160impl<T: Format> Format for Box<T> {
161 #[inline]
162 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result {
163 (**self).write_record(record, writer)
164 }
165
166 #[inline]
167 fn write_record_generic<W: fmt::Write>(
168 &self,
169 record: &Record<'_>,
170 writer: &mut W,
171 ) -> fmt::Result {
172 (**self).write_record_generic(record, writer)
173 }
174}
175
176#[cfg(feature = "std")]
177impl Format for Box<dyn Format> {
178 #[inline]
179 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result {
180 (**self).write_record(record, writer)
181 }
182}
183
184#[derive(Clone, Copy)]
186pub struct FnFormat<F> {
187 f: F,
188}
189
190impl<F> fmt::Debug for FnFormat<F> {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.debug_struct("FnFormat").finish_non_exhaustive()
193 }
194}
195
196impl<F> Format for FnFormat<F>
197where
198 F: Fn(&Record<'_>, &mut dyn fmt::Write) -> fmt::Result + Send + Sync,
199{
200 #[inline]
201 fn write_record(&self, record: &Record<'_>, writer: &mut dyn fmt::Write) -> fmt::Result {
202 (self.f)(record, writer)
203 }
204}
205
206pub const fn from_fn<F>(f: F) -> FnFormat<F>
228where
229 F: Fn(&Record<'_>, &mut dyn fmt::Write) -> fmt::Result + Send + Sync,
230{
231 FnFormat { f }
232}
233
234#[cfg(any(feature = "json", feature = "logfmt", feature = "human"))]
235pub(crate) mod escape;
236
237#[cfg(any(feature = "json", feature = "human", feature = "logfmt"))]
238pub(crate) mod rfc3339;
239
240#[cfg(feature = "json")]
241mod json;
242#[cfg(feature = "json")]
243pub use self::json::JsonFormat;
244
245#[cfg(feature = "logfmt")]
246mod logfmt;
247#[cfg(feature = "logfmt")]
248pub use self::logfmt::LogfmtFormat;
249
250#[cfg(feature = "human")]
251mod human;
252#[cfg(all(feature = "human", feature = "std"))]
253pub use self::human::ColorChoice;
254#[cfg(feature = "human")]
255pub use self::human::HumanFormat;
256
257#[cfg(all(test, feature = "std"))]
258mod tests {
259 use super::*;
260 use crate::{Level, Metadata};
261
262 struct Upper;
263 impl Format for Upper {
264 fn write_record(&self, record: &Record<'_>, w: &mut dyn fmt::Write) -> fmt::Result {
265 writeln!(w, "{}", record.metadata.level.as_str_upper())
266 }
267 }
268
269 fn record() -> Record<'static> {
270 Record::new(Metadata::new(Level::Info, "t"), "m", &[])
271 }
272
273 fn render<F: Format>(f: &F) -> String {
274 let mut out = String::new();
275 f.write_record_generic(&record(), &mut out).unwrap();
276 let mut dyn_out = String::new();
277 f.write_record(&record(), &mut dyn_out).unwrap();
278 assert_eq!(out, dyn_out, "static and dynamic paths must agree");
279 out
280 }
281
282 #[test]
283 fn test_format_adapters_forward_to_inner() {
284 assert_eq!(render(&Upper), "INFO\n");
285 assert_eq!(render(&&Upper), "INFO\n");
286 assert_eq!(render(&Box::new(Upper)), "INFO\n");
287 assert_eq!(render(&std::sync::Arc::new(Upper)), "INFO\n");
288 let boxed: Box<dyn Format> = Box::new(Upper);
289 assert_eq!(render(&boxed), "INFO\n");
290 let arced: std::sync::Arc<dyn Format> = std::sync::Arc::new(Upper);
291 assert_eq!(render(&arced), "INFO\n");
292 let borrowed: &dyn Format = &Upper;
293 assert_eq!(render(&borrowed), "INFO\n");
294 }
295
296 #[test]
297 fn test_from_fn_closure_is_a_format() {
298 let f = from_fn(|r: &Record<'_>, w: &mut dyn fmt::Write| writeln!(w, "<{}>", r.message));
299 assert_eq!(render(&f), "<m>\n");
300 assert!(format!("{f:?}").starts_with("FnFormat"));
301 }
302
303 #[test]
304 fn test_format_errors_propagate() {
305 struct Failing;
306 impl fmt::Write for Failing {
307 fn write_str(&mut self, _: &str) -> fmt::Result {
308 Err(fmt::Error)
309 }
310 }
311 assert!(Upper.write_record_generic(&record(), &mut Failing).is_err());
312 }
313}