slog_extlog/lib.rs
1//! External logging and statistics tracking API for [`slog`].
2//!
3//! This crate adds support for two features to the slog ecosystem.
4//!
5//! # Generating "external" logs
6//! External logs are logs that form an API and so must not be modified or removed
7//! without agreeing as a spec change. New logs can always safely be added.
8//!
9//! The real advantage of external logs is that the log itself becomes a type, therefore
10//! guaranteeing that required fields must always be provided and giving compile-time checking of
11//! log generation.
12//!
13//! When using this crate, slog loggers can be used in two ways.
14//!
15//! * External logs can be defined using this crate, and then logged using
16//! a [`StatisticsLogger`]. They can also be used as triggers for statistics generation.
17//! * For internal, low-level logging, the usual slog macros (`info!`, `debug!`, `trace!` etc)
18//! can be used.
19//!
20//! Any object can be made into an external log by implementing [`ExtLoggable`]. In nearly all
21//! cases this trait should be automatically derived using the
22//! [`slog-extlog-derive`] crate.
23//!
24//! ## Log objects
25//! To use this crate to define external logs:
26//!
27//! - Add `slog-extlog` with the `derive` feature enabled to your `Cargo.toml`.
28//!
29//! To make a new external log using this crate:
30//!
31//! - Define a structure with appropriate fields to act as the log.
32//! - Define a constant string named `CRATE_LOG_NAME` which uniquely identifies this crate in
33//! log identifiers. This must uniquely identify your crate.
34//! - `use slog_extlog_derive::ExtLoggable` and derive the `ExtLoggable` trait for this object.
35//! - If your structure is overly complex or unusual, manually implement [`ExtLoggable`].
36//! - Early in your program or libary, obtain or create a [`Logger`] of the correct format and
37//! wrap it in a ['StatisticsLogger`].
38//!
39//! Unless you want to support statistics tracking, then the easiest way to obtain an
40//! appropriate logger is to create a [`DefaultLogger`](./type.DefaultLogger.html).
41//!
42//! You can then call the [`slog_extlog::xlog!()`] macro, passing in the [`StatisticsLogger`] and
43//! an instance of your structure, and it will be logged according to the Logger's associated Drain
44//! as usual.
45//! Structure parameters will be added as key-value pairs, but with the bonus that you get
46//! type checking.
47//!
48//! You can continue to make developer logs simply using `slog` as normal:
49//!
50//! - Add `slog` to your `Cargo.toml`.
51//! - Use the usual [`slog`] macros, e.g., `slog::debug!`.
52//!
53//! ## Parameters
54//! Parameters to external logs must implement [`slog::Value`].
55//!
56//! For types you own, you can also derive `slog::Value` using `#[derive SlogValue]` from the
57//! [`slog-extlog-derive`] crate.
58//!
59//! For types you do not own, you can define a wrapper type that implements `Value` using
60//! [`impl_value_wrapper`](macro.impl_value_wrapper.html).
61//!
62//! # Statistics tracking
63//!
64//! [`ExtLoggable`] objects can automatically trigger changes to statistics tracked by the
65//! associated [`StatsLogger`].
66//!
67//! To make this work, the following approach is required.
68//!
69//! - Create a static set of statistic definitions using the [`define_stats`] macro.
70//! - Add `StatTrigger` attributes to each external log that explains which statistics
71//! the log should update.
72//!
73//! The automatic derivation code then takes care of updating the statistics as and when required.
74//!
75//! # Example
76//! An example of a simple program that defines and produces some basic logs.
77//!
78//! ```
79//! use serde::Serialize;
80//! use slog_extlog::{stats::DefaultStatisticsLogFormatter, stats::StatsLoggerBuilder, define_stats, xlog};
81//! use slog_extlog_derive::{ExtLoggable, SlogValue};
82//!
83//! use slog::{Drain, debug, info, o};
84//! use std::sync::Mutex;
85//!
86//! #[derive(Clone, Serialize, ExtLoggable)]
87//! #[LogDetails(Id="101", Text="Foo Request received", Level="Info")]
88//! struct FooReqRcvd;
89//!
90//! #[derive(Clone, Serialize, ExtLoggable)]
91//! #[LogDetails(Id="103", Text="Foo response sent", Level="Info")]
92//! struct FooRspSent(FooRspCode);
93//!
94//! #[derive(Clone, Serialize, SlogValue)]
95//! enum FooRspCode {
96//! Success,
97//! InvalidUser,
98//! }
99//!
100//! #[derive(Clone, Serialize, SlogValue)]
101//! enum FooMethod {
102//! GET,
103//! PATCH,
104//! POST,
105//! PUT,
106//! }
107//!
108//! #[derive(Clone, Serialize, SlogValue)]
109//! struct FooContext {
110//! id: String,
111//! method: FooMethod,
112//! }
113//!
114//! const CRATE_LOG_NAME: &'static str = "FOO";
115//!
116//! #[tokio::main]
117//! async fn main() {
118//! // Use a basic logger with some context.
119//! let logger = slog::Logger::root(
120//! Mutex::new(slog_json::Json::default(std::io::stdout())).map(slog::Fuse),
121//! o!());
122//! let logger = logger.new(o!("cxt" => FooContext {
123//! id: "123456789".to_string(),
124//! method: FooMethod::POST,
125//! }));
126//! let foo_logger = StatsLoggerBuilder::default().fuse(logger);
127//!
128//! // Now make some logs...
129//! xlog!(foo_logger, FooReqRcvd);
130//! debug!(foo_logger, "Some debug info");
131//! xlog!(foo_logger, FooRspSent(FooRspCode::Success));
132//! let count = 1;
133//! info!(foo_logger, "New counter value"; "count" => count);
134//! }
135//! ```
136//!
137//! [`define_stats`]: ./macro.define_stats.html
138//! [`Logger`]: ../slog/struct.Logger.html
139//! [`ExtLoggable`]: trait.ExtLoggable.html
140//! [`slog`]: ../slog/index.html
141//! [`StatisticsLogger`]: stats/struct.StatisticsLogger.html
142//! [`slog-extlog-derive`]: ../slog_extlog_derive/index.html
143//! [`slog::Value`]: ../slog/trait.Value.html
144//! [`xlog!()`]: macro.xlog.html
145
146// Copyright 2017 Metaswitch Networks
147
148/// Re-export erased_serde since the derive crate references it
149pub use erased_serde;
150
151// Statistics handling
152pub mod stats;
153
154// Utilities for users to call in tests.
155pub mod slog_test;
156
157/// A trait that defines requirements to be automatically derivable.
158///
159/// Any generic parameters in `ExtLoggable` objects must have this as a trait bound.
160pub trait SlogValueDerivable: std::fmt::Debug + Clone + serde::Serialize + Send + 'static {}
161
162impl<T> SlogValueDerivable for T where T: std::fmt::Debug + Clone + serde::Serialize + Send + 'static
163{}
164
165/// The default logger type.
166pub type DefaultLogger = stats::StatisticsLogger;
167
168/// An object that can be logged.
169///
170/// Usually custom-derived using the [`slog-extlog-derive`](../slog_extlog_derive/index.html)
171/// crate.
172pub trait ExtLoggable: slog::Value {
173 /// Log this object with the provided `Logger`.
174 ///
175 /// Do not call directly - use [`xlog!`](macro.xlog.html) instead.
176 fn ext_log(&self, logger: &stats::StatisticsLogger);
177}
178
179/// Log an external log through an `slog::Logger`.
180///
181/// Syntactic sugar for the `ExtLoggable` trait for consistency with the standard slog macros.
182#[macro_export]
183macro_rules! xlog {
184 ($l:expr, $($args:tt)*) => {
185 $crate::ExtLoggable::ext_log(&$($args)*, &$l)
186 };
187}
188
189/// Generate a [`slog::Value`](../slog/trait.Value.html) trait implementation for a type we don't
190/// own but which implements `std::fmt::Display` or `std::fmt::Debug`.
191///
192/// This allows using types from third-party crates as values in external logs. The macro defines
193/// a new wrapper type to be used in external logs, which does implement `slog::Value`.
194///
195/// For example, if you want to use the (fictional) `foo` crate and log errors from it, then write:
196///
197/// ```ignore
198/// slog_extlog::impl_value_wrapper!(FooError, foo::Error);
199/// ```
200/// Then anywhere you want to log a `foo::Error`, you can instead use `FooError(foo::Error)` - the
201/// logged value will use `foo:Error`'s impl of `Display`.
202///
203/// For a type which implements `Debug` but not `Display`, then you can use a `?` character:
204///
205/// ```ignore
206/// slog_extlog::impl_value_wrapper!(FooInternal, ?foo::InternalType);
207/// ```
208///
209/// Note: this macro can be deprecated once `default impl` is available and
210/// [this issue](https://github.com/slog-rs/slog/issues/120) is fixed.
211///
212#[macro_export]
213macro_rules! impl_value_wrapper {
214 ($new:ident, ?($inner:tt)*) => {
215 pub struct $new(pub $($inner)*);
216 impl slog::Value for $new {
217 fn serialize(&self, record: &slog::Record, key: Key, serializer: &mut slog::Serializer) -> Result {
218 use slog::KV;
219 slog::b!(key => ?&self.0).serialize(record, serializer)
220 }
221 }
222 };
223 ($new:ident, ($inner:tt)*) => {
224 pub struct $new(pub $($inner)*);
225 impl slog::Value for $new {
226 fn serialize(&self, record: &slog::Record, key: Key, serializer: &mut slog::Serializer) -> Result {
227 use slog::KV;
228 slog::b!(key => %&self.0).serialize(record, serializer)
229 }
230 }
231 };
232}
233
234// Re-export the derive macros to ensure that version compatibility is preserved
235#[cfg(feature = "derive")]
236pub use slog_extlog_derive::{ExtLoggable, SlogValue};