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};