log_fastly/
lib.rs

1//! Implementation of the [`log`] logging façade for Fastly Compute.
2//!
3//! With this logger configured, [`log`'s logging statements][log-use] will send log messages to
4//! your chosen [Real-Time Log Streaming][rtls] endpoints. You should initialize the logger as soon
5//! as your program starts. Logging statements will not do anything before initialization.
6//!
7//! See the [Fastly
8//! documentation](https://docs.fastly.com/en/guides/integrations#_logging-endpoints) for more
9//! information about configuring logging endpoints for your service.
10//!
11//! # Getting started
12//!
13//! All you need to get started is your endpoint name and the level of log messages you want to
14//! emit. For example, if you have an endpoint called `my_endpoint`, and you only want to emit log
15//! messages at the "Warn" or "Error" level, you can use [`init_simple()`]:
16//!
17//! ```no_run
18//! log_fastly::init_simple("my_endpoint", log::LevelFilter::Warn);
19//! log::warn!("This will be written to my_endpoint...");
20//! log::info!("...but this won't");
21//! ```
22//!
23//! # Advanced configuration
24//!
25//! For more precise control, including multiple endpoints and default endpoints for different
26//! logging levels, use the [`Builder`] interface. The first example is equivalent to:
27//!
28//! ```no_run
29//! log_fastly::Logger::builder()
30//!     .max_level(log::LevelFilter::Warn)
31//!     .default_endpoint("my_endpoint")
32//!     .init();
33//! ```
34//!
35//! The [`Builder::max_level()`] option sets the most verbose level of logging that will be
36//! emitted. Logging statements above that level, like `log::info!()` in the first example, will do
37//! nothing.
38//!
39//! **Note:** The default level is `LevelFilter::Off`, which emits no logging at all. You'll want to
40//! change this for most configurations.
41//!
42//! [`Builder::default_endpoint()`] sets the endpoint used whenever a logging statement is called
43//! without a `target` field to specify its endpoint. With the default endpoint set to
44//! `my_endpoint`, the logging statements in the first example are equivalent to:
45//!
46//! ```no_run
47//! log::warn!(target: "my_endpoint", "This will be written to my_endpoint...");
48//! log::info!(target: "my_endpoint", "...but this won't");
49//! ```
50//!
51//! ## Use with Compute Log Tailing
52//!
53//! [Compute Log Tailing][log-tailing] is helpful for getting debugging output quickly from a
54//! Compute program under development by capturing output from `stdout` or `stderr`. To
55//! configure logging to output to `stdout` or `stderr` in addition to the specified log endpoint,
56//! enable echoing when building the logger:
57//!
58//! ```no_run
59//! log_fastly::Logger::builder()
60//!     .max_level(log::LevelFilter::Warn)
61//!     .default_endpoint("my_endpoint")
62//!     .echo_stdout(true)
63//!     .init();
64//! ```
65//!
66//! [log-tailing]: https://www.fastly.com/blog/introducing-compute-edge-log-tailing-for-better-observability-and-easier-debugging
67//!
68//! ## Multiple endpoints
69//!
70//! Setting an endpoint as the default will automatically register it for use with the logger, but
71//! you can register additional endpoints with [`Builder::endpoint()`]:
72//!
73//! ```no_run
74//! log_fastly::Logger::builder()
75//!     .max_level(log::LevelFilter::Warn)
76//!     .default_endpoint("my_endpoint")
77//!     .endpoint("my_other_endpoint")
78//!     .init();
79//! log::warn!(target: "my_endpoint", "This will be written to my_endpoint...");
80//! log::warn!(target: "my_other_endpoint", "...but this will be written to my_other_endpoint");
81//! ```
82//!
83//! ## Per-endpoint logging levels
84//!
85//! You can also set a per-endpoint logging level, though levels higher than `max_level` are always
86//! ignored:
87//!
88//! ```no_run
89//! log_fastly::Logger::builder()
90//!     .max_level(log::LevelFilter::Warn)
91//!     .default_endpoint("my_endpoint")
92//!     .endpoint_level("my_other_endpoint", log::LevelFilter::Trace)
93//!     .endpoint_level("error_only", log::LevelFilter::Error)
94//!     .init();
95//! log::warn!(target: "my_other_endpoint", "This will be written to my_other_endpoint...");
96//! log::trace!(target: "my_other_endpoint", "...but this won't, because max_level wins");
97//! log::error!(target: "error_only", "This will be written to error_only...");
98//! log::warn!(target: "error_only", "...but this won't, because the endpoint's level is lower");
99//! ```
100//!
101//! ## Per-level default endpoints
102//!
103//! In the previous examples, the same endpoint is set as the default for all logging levels. You
104//! can also specify default endpoints for individual levels using
105//! [`Builder::default_level_endpoint()`]. The defaults are combined in order, so you can specify an
106//! overall default endpoint, and then as many level-specific endpoints as you need:
107//!
108//! ```no_run
109//! log_fastly::Logger::builder()
110//!     .max_level(log::LevelFilter::Info)
111//!     .default_endpoint("my_endpoint")
112//!     .default_level_endpoint("error_only", log::Level::Error)
113//!     .init();
114//! log::info!("This will be written to my_endpoint...");
115//! log::warn!(".. and this will too.");
116//! log::error!("But this will be written to error_only");
117//! ```
118//!
119//! ## Module name filters
120//!
121//! In addition to per-endpoint logging levels, you can set logging levels based on the name of the
122//! Rust module that contains the logging statement.
123//!
124//! No filtering is done based on the module name by default, but if any module name patterns are
125//! specified, only log statements that match one of the patterns will be emitted. For example, if
126//! your application is a crate called `my_app`, you can filter out any messages other than
127//! `my_app`'s :
128//!
129//! ```no_run
130//! # mod some_dependency { pub fn function_that_logs() {} }
131//! log_fastly::Logger::builder()
132//!     .max_level(log::LevelFilter::Info)
133//!     .default_endpoint("my_endpoint")
134//!     .filter_module("my_app", log::LevelFilter::Warn)
135//!     .init();
136//! log::warn!("This will be written to my_endpoint");
137//! // This won't emit any log messages, because no patterns match `some_dependency`
138//! some_dependency::function_that_logs();
139//! ```
140//!
141//! The filter expressions support the full syntax of the [`regex`][regex] crate. This is
142//! particularly useful if your patterns overlap, but you'd like to still treat them distinctly. For
143//! example, suppose you want to set the log level of the module `my_app::my_module` to be more
144//! restrictive than the top level `my_app` module. You can use `$` to make sure the
145//! less-restrictive pattern matches to the end of the module name, instead of matching any module
146//! name that contains `my_app`:
147//!
148//! ```no_run
149//! mod my_module {
150//!     pub fn do_a_thing() {
151//!         log::warn!("This won't be written, because this module's max level is Error");
152//!     }
153//! }
154//! log_fastly::Logger::builder()
155//!     .max_level(log::LevelFilter::Info)
156//!     .default_endpoint("my_endpoint")
157//!     .filter_module("my_app$", log::LevelFilter::Warn)
158//!     .filter_module("my_app::my_module", log::LevelFilter::Error)
159//!     .init();
160//! log::warn!("This will be written to my_endpoint");
161//! // This won't emit any log messages, because "my_app$" doesn't match, and "my_app::my_module"
162//! // is limited to Error
163//! my_module::do_a_thing();
164//! ```
165//!
166//! # Registering endpoints
167//!
168//! All endpoints used by your logging statements must be registered when the logger is created. The
169//! following functions automatically register an endpoint if it is not already registered.
170//!
171//! - [`init_simple()`]
172//! - [`Builder::endpoint()`]
173//! - [`Builder::endpoint_level()`]
174//! - [`Builder::default_endpoint()`]
175//! - [`Builder::default_level_endpoint()`]
176//!
177//! You can pass the endpoint name as a string, or an explicit [`fastly::log::Endpoint`] value. The
178//! following examples are equivalent:
179//!
180//! ```no_run
181//! log_fastly::init_simple("my_endpoint", log::LevelFilter::Info);
182//! ```
183//!
184//! ```no_run
185//! log_fastly::init_simple(
186//!     fastly::log::Endpoint::from_name("my_endpoint"),
187//!     log::LevelFilter::Info,
188//! );
189//! ```
190//!
191//! If a logging statement uses `target: "my_endpoint"` but `my_endpoint` is not registered, the
192//! message will be logged to the default endpoint for that level, if one exists.
193//!
194//! # Endpoint and module name collisions
195//!
196//! Due to a [limitation of `log`][log-issue], logging from a module with the same name as one of
197//! your endpoints will cause logs to be sent to that endpoint, even if no `target` is specified,
198//! and the endpoint is not a default. For example, if you have an endpoint named `my_app`, and your
199//! application is a crate also called `my_app`:
200//!
201//! ```no_run
202//! log_fastly::Logger::builder()
203//!     .max_level(log::LevelFilter::Info)
204//!     .default_endpoint("my_endpoint")
205//!     .endpoint("my_app")
206//!     .init();
207//! log::info!("This will be written to my_app, even though my_endpoint is the default");
208//! log::info!(
209//!     target: "my_endpoint",
210//!     "This will be written to my_endpoint, because the target is explicit",
211//! );
212//! ```
213//!
214//! We hope to address this issue in future releases, but the current workarounds are to either make
215//! sure your endpoint names and crate name are distinct, or use the explicit target syntax whenever
216//! logging from your top-level module.
217//!
218//! [log]: https://docs.rs/log
219//! [log-use]: https://docs.rs/log#use
220//! [rtls]: https://docs.fastly.com/en/guides/about-fastlys-realtime-log-streaming-features
221//! [regex]: https://docs.rs/regex#syntax
222//! [log-issue]: https://github.com/rust-lang/log/issues/390
223
224// Warnings (other than unused variables) in doctests are promoted to errors.
225#![doc(test(attr(deny(warnings))))]
226#![doc(test(attr(allow(dead_code))))]
227#![doc(test(attr(allow(unused_variables))))]
228#![warn(missing_docs)]
229#![deny(rustdoc::broken_intra_doc_links)]
230#![deny(rustdoc::invalid_codeblock_attributes)]
231
232#[cfg(feature = "native-test-stubs")]
233mod native_test_stubs;
234#[cfg(feature = "native-test-stubs")]
235pub use native_test_stubs::*;
236
237#[cfg(not(feature = "native-test-stubs"))]
238use fastly::{
239    error::{anyhow, Error},
240    log::Endpoint,
241};
242use log::{Level, LevelFilter, Log, Metadata, Record};
243use regex::{RegexSet, RegexSetBuilder};
244use std::collections::HashMap;
245use std::io::Write;
246
247/// An implementation of the [`log::Log`] trait for Fastly Compute.
248///
249/// Create and initialize a `Logger` by using the builder returned by [`Logger::builder()`].
250#[derive(Debug)]
251pub struct Logger {
252    /// All of the endpoints that are registered with the logger.
253    endpoints: HashMap<String, (Endpoint, Option<LevelFilter>)>,
254    /// Default endpoints for each of the log levels.
255    default_endpoints: HashMap<Level, Endpoint>,
256    /// The maximum log level for all endpoints.
257    max_level: LevelFilter,
258    /// An optional matcher for each log level
259    module_filters: Option<HashMap<Level, RegexSet>>,
260    /// Whether to echo all log messages to `stdout`.
261    echo_stdout: bool,
262    /// Whether to echo all log messages to `stderr`.
263    echo_stderr: bool,
264}
265
266impl Log for Logger {
267    fn enabled(&self, metadata: &Metadata) -> bool {
268        self.lookup_endpoint(metadata, None).is_some()
269    }
270
271    fn log(&self, record: &Record) {
272        if let Some(endpoint) = self.lookup_endpoint(record.metadata(), record.module_path()) {
273            // Collapse the formatting of the message into a single string, so that we only call the
274            // underlying `write` once. This prevents the message from being split into multiple
275            // lines in surprising ways, depending on how the format arguments are structured.
276            let mut owned = Default::default();
277            let msg = record.args().as_str().unwrap_or_else(|| {
278                owned = record.args().to_string();
279                &owned
280            });
281            // Given the type, we don't really have much of a choice about what to do if this goes
282            // wrong; panicking is probably not the right thing.
283            let _ = write!(endpoint.clone(), "{}", msg);
284            if self.echo_stdout {
285                println!("{}", msg);
286            }
287            if self.echo_stderr {
288                eprintln!("{}", msg);
289            }
290        }
291    }
292
293    fn flush(&self) {}
294}
295
296impl Logger {
297    /// Get a new [`Builder`].
298    pub fn builder() -> Builder {
299        Builder::new()
300    }
301
302    fn max_level(&self) -> LevelFilter {
303        self.max_level
304    }
305
306    /// Look up an endpoint for a log message.
307    ///
308    /// Returns `None` if no endpoint was found, or if the filter settings would prevent this
309    /// message from being emitted.
310    fn lookup_endpoint(&self, metadata: &Metadata, module_path: Option<&str>) -> Option<&Endpoint> {
311        let level = metadata.level();
312        // Immediately filter out any messages above the max level
313        if level > self.max_level {
314            return None;
315        }
316        if let Some(module_path) = module_path {
317            // If there is a module filter for this level, try to match it
318            if let Some(filter) = self.module_filters.as_ref().and_then(|fs| fs.get(&level)) {
319                if !filter.is_match(module_path) {
320                    return None;
321                }
322            }
323        }
324        // If the target matches an endpoint, apply the endpoint-specific filter
325        if let Some((endpoint, filter)) = self.endpoints.get(metadata.target()) {
326            if let Some(filter) = filter {
327                if level <= *filter {
328                    Some(endpoint)
329                } else {
330                    None
331                }
332            } else {
333                // If there is no filter, and the level didn't exceed the overall max, log it
334                Some(endpoint)
335            }
336        } else {
337            // For unrecognized targets, log it if there's a default endpoint for the level
338            self.default_endpoints.get(&level)
339        }
340    }
341}
342
343/// A builder type for [`Logger`].
344///
345/// You can use this builder to register endpoints, set default endpoints, control the levels of
346/// logging messages emitted, and filter messages based on module name.
347///
348/// A `Builder` is consumed by calling [`Builder::init()`] or [`Builder::try_init()`] to build and
349/// automatically initialize the logger, or by calling [`Builder::build()`] to build the logger for
350/// manual initialization or nesting within another logger. After you call any of these methods, you
351/// can no longer call any of them on that same builder.
352#[derive(Debug)]
353pub struct Builder {
354    inner: Result<Inner, Error>,
355}
356
357#[derive(Debug)]
358struct Inner {
359    /// All of the endpoints that are registered with the logger.
360    endpoints: HashMap<String, (Endpoint, Option<LevelFilter>)>,
361    /// Default endpoints for each of the log levels.
362    default_endpoints: HashMap<Level, Endpoint>,
363    /// The maximum log level for all endpoints.
364    max_level: LevelFilter,
365    /// The module patterns we will match for each log level
366    module_filters: HashMap<LevelFilter, Vec<String>>,
367    /// Whether to echo all log messages to `stdout`.
368    echo_stdout: bool,
369    /// Whether to echo all log messages to `stderr`.
370    echo_stderr: bool,
371}
372
373impl Default for Builder {
374    fn default() -> Self {
375        Self::new()
376    }
377}
378
379impl Builder {
380    /// Create a new `Builder`.
381    ///
382    /// By default, no endpoints are registered, the maximum log level is set to `Off`, and no
383    /// module name filtering is done.
384    pub fn new() -> Self {
385        let inner = Inner {
386            endpoints: HashMap::new(),
387            default_endpoints: HashMap::new(),
388            max_level: LevelFilter::Off,
389            module_filters: HashMap::new(),
390            echo_stdout: false,
391            echo_stderr: false,
392        };
393        Self { inner: Ok(inner) }
394    }
395
396    /// Run a closure on the inner field, if it exists.
397    fn with_inner<F: FnOnce(&mut Inner) -> R, R>(&mut self, f: F) {
398        // It's fine not to use this result, because we're deferring errors to `build()`
399        let _ = self.inner.as_mut().map(f);
400    }
401
402    /// Run a closure on the inner field if it exists, and a fallible argument.
403    ///
404    /// If the argument is an `Err`, this replaces `inner` with that `Err`.
405    fn with_inner_and_then<A, F, R, E>(&mut self, arg: Result<A, E>, f: F)
406    where
407        F: FnOnce(&mut Inner, A) -> R,
408        E: Into<Error>,
409    {
410        match arg {
411            Ok(x) => self.with_inner(|i| f(i, x)),
412            Err(e) => self.inner = Err(e.into()),
413        }
414    }
415
416    /// Register an endpoint.
417    ///
418    /// ```no_run
419    /// log_fastly::Logger::builder()
420    ///     .max_level(log::LevelFilter::Trace)
421    ///     .endpoint("my_endpoint")
422    ///     .init();
423    /// log::info!(target: "my_endpoint", "Hello");
424    /// ```
425    pub fn endpoint<E>(&mut self, endpoint: E) -> &mut Self
426    where
427        E: TryInto<Endpoint>,
428        <E as TryInto<Endpoint>>::Error: Into<Error>,
429    {
430        self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
431            i.endpoints
432                .insert(endpoint.name().to_owned(), (endpoint, None))
433        });
434        self
435    }
436
437    /// Register an endpoint and set the maximum logging level for its messages.
438    ///
439    /// ```no_run
440    /// log_fastly::Logger::builder()
441    ///     .max_level(log::LevelFilter::Trace)
442    ///     .endpoint_level("debug_endpoint", log::LevelFilter::Debug)
443    ///     .init();
444    /// log::info!(target: "debug_endpoint", "This will be written to debug_endpoint...");
445    /// log::trace!(target: "debug_endpoint", "...but this won't be...");
446    /// ```
447    pub fn endpoint_level<E>(&mut self, endpoint: E, level: LevelFilter) -> &mut Self
448    where
449        E: TryInto<Endpoint>,
450        <E as TryInto<Endpoint>>::Error: Into<Error>,
451    {
452        self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
453            i.endpoints
454                .insert(endpoint.name().to_owned(), (endpoint, Some(level)))
455        });
456        self
457    }
458
459    /// Set the default endpoint for all messages.
460    ///
461    /// The default endpoint is used when the logging statement does not use the `target:
462    /// "endpoint"` syntax.
463    ///
464    /// This overrides any previous default endpoints, set either by this method or by
465    /// [`Builder::default_level_endpoint()`].
466    ///
467    /// ```no_run
468    /// log_fastly::Logger::builder()
469    ///     .max_level(log::LevelFilter::Info)
470    ///     .default_level_endpoint("error_only", log::Level::Error)
471    ///     .default_endpoint("my_endpoint")
472    ///     .endpoint("other_endpoint")
473    ///     .init();
474    /// log::info!("This will be written to my_endpoint...");
475    /// log::error!("...and this will too");
476    /// log::warn!(target: "other_endpoint", "This will go to other_endpoint, though");
477    /// ```
478    pub fn default_endpoint<E>(&mut self, endpoint: E) -> &mut Self
479    where
480        E: TryInto<Endpoint>,
481        <E as TryInto<Endpoint>>::Error: Into<Error>,
482    {
483        self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
484            for level in &[
485                Level::Error,
486                Level::Warn,
487                Level::Info,
488                Level::Debug,
489                Level::Trace,
490            ] {
491                i.default_endpoints.insert(*level, endpoint.clone());
492            }
493        });
494        self
495    }
496
497    /// Set the default endpoint for all messages of the given level.
498    ///
499    /// The default endpoint is used when the logging statement does not use the `target:
500    /// "endpoint"` syntax.
501    ///
502    /// This overrides any previous default endpoints set for this level, either by this method or
503    /// by [`Builder::default_endpoint()`].
504    ///
505    /// ```no_run
506    /// log_fastly::Logger::builder()
507    ///     .max_level(log::LevelFilter::Info)
508    ///     .default_endpoint("my_endpoint")
509    ///     .default_level_endpoint("error_only", log::Level::Error)
510    ///     .endpoint("other_endpoint")
511    ///     .init();
512    /// log::info!("This will be written to my_endpoint...");
513    /// log::error!("...but this will be written to error_only");
514    /// log::error!(target: "other_endpoint", "This will go to other_endpoint, though");
515    /// ```
516    pub fn default_level_endpoint<E>(&mut self, endpoint: E, level: Level) -> &mut Self
517    where
518        E: TryInto<Endpoint>,
519        <E as TryInto<Endpoint>>::Error: Into<Error>,
520    {
521        self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
522            i.default_endpoints.insert(level, endpoint.clone());
523            i.endpoints
524                .insert(endpoint.name().to_owned(), (endpoint, None))
525        });
526        self
527    }
528
529    /// Set the maximum logging level for all messages.
530    ///
531    /// No messages that exceed this level will be emitted, even if a higher level is set for a
532    /// specific endpoint or module name.
533    ///
534    /// **Note:** The default level is `LevelFilter::Off`, which emits no logging at all. You'll
535    /// want to change this for most configurations.
536    ///
537    /// ```no_run
538    /// log_fastly::Builder::new()
539    ///     .max_level(log::LevelFilter::Warn)
540    ///     .endpoint_level("my_endpoint", log::LevelFilter::Info)
541    ///     .init();
542    /// log::warn!(target: "my_endpoint", "This will be written to my_endpoint...");
543    /// log::info!(target: "my_endpoint", "...but this won't");
544    /// ```
545    pub fn max_level(&mut self, level: LevelFilter) -> &mut Self {
546        self.with_inner(|i| i.max_level = level);
547        self
548    }
549
550    /// Set a logging level for modules whose names match the given [regular
551    /// expression][regex-syntax].
552    ///
553    /// By default, logging statements in any module are emitted if their level is within
554    /// [`Builder::max_level()`] and the level for their target endpoint. If you configure filters
555    /// with this method, the name of the module calling the logging statement must also match one
556    /// of the patterns, and be within the filter's specified level.
557    ///
558    /// ```no_run
559    /// mod my_module {
560    ///     pub fn do_a_thing() {
561    ///         log::warn!("This won't be written, because this module's max level is Error...");
562    ///         log::error!("...but this will be written");
563    ///     }
564    /// }
565    ///
566    /// log_fastly::Logger::builder()
567    ///     .max_level(log::LevelFilter::Info)
568    ///     .default_endpoint("my_endpoint")
569    ///     .filter_module("my_module", log::LevelFilter::Error)
570    ///     .init();
571    /// log::info!("This won't be written, because it's not in my_module");
572    /// // This will only emit one log message, because "my_app$" doesn't match, and
573    /// // "my_app::my_module" is limited to Error
574    /// my_module::do_a_thing();
575    /// ```
576    ///
577    /// [regex-syntax]: https://docs.rs/regex#syntax
578    pub fn filter_module(&mut self, regex: &str, level: LevelFilter) -> &mut Self {
579        self.with_inner(|i| {
580            i.module_filters
581                .entry(level)
582                .or_default()
583                .push(regex.to_owned())
584        });
585        self
586    }
587
588    /// Set whether all log messages should be echoed to `stdout` (`false` by default).
589    ///
590    /// If this is set to `true`, all logging statements will write the message to `stdout` in
591    /// addition to the specified endpoint. This is particularly useful when debugging with
592    /// [Compute Log Tailing][log-tailing].
593    ///
594    /// [log-tailing]: https://www.fastly.com/blog/introducing-compute-edge-log-tailing-for-better-observability-and-easier-debugging
595    pub fn echo_stdout(&mut self, enabled: bool) -> &mut Self {
596        self.with_inner(|i| i.echo_stdout = enabled);
597        self
598    }
599
600    /// Set whether all log messages should be echoed to `stderr` (`false` by default).
601    ///
602    /// If this is set to `true`, all logging statements will write the message to `stderr` in
603    /// addition to the specified endpoint. This is particularly useful when debugging with
604    /// [Compute Log Tailing][log-tailing].
605    ///
606    /// [log-tailing]: https://www.fastly.com/blog/introducing-compute-edge-log-tailing-for-better-observability-and-easier-debugging
607    pub fn echo_stderr(&mut self, enabled: bool) -> &mut Self {
608        self.with_inner(|i| i.echo_stderr = enabled);
609        self
610    }
611
612    /// Build the logger and initialize it as the global logger.
613    ///
614    /// ```no_run
615    /// log_fastly::Builder::new()
616    ///     .default_endpoint("my_endpoint")
617    ///     .init();
618    /// log::info!("Hello");
619    /// ```
620    ///
621    /// # Panics
622    ///
623    /// This may panic for any of the reasons that [`Builder::try_init()`] would return an error.
624    pub fn init(&mut self) {
625        self.try_init().expect("log_fastly::Builder::init() failed")
626    }
627
628    /// Build the logger and initialize it as the global logger.
629    ///
630    /// This will fail with an `Err` value if a global logger is already initialized, or for any of
631    /// the reasons that [`Builder::build()`] can fail.
632    ///
633    /// ```no_run
634    /// log_fastly::Builder::new()
635    ///     .default_endpoint("my_endpoint")
636    ///     .try_init()
637    ///     .unwrap();
638    /// log::info!("Hello");
639    /// ```
640    pub fn try_init(&mut self) -> Result<(), Error> {
641        let logger = self.build()?;
642
643        // Get the overall max level while we still own the logger
644        let max_level = logger.max_level();
645
646        // Now try to install the logger
647        let res = log::set_boxed_logger(Box::new(logger));
648
649        if res.is_ok() {
650            // And finally set the max level in the global `log` state, if we successfully installed
651            // the logger
652            log::set_max_level(max_level);
653        }
654
655        res.map_err(Into::into)
656    }
657
658    /// Build a `Logger`, using up this builder.
659    ///
660    /// This is mainly useful if you want to manually initialize the logger, or nest it within
661    /// another [`log::Log`] implementation.
662    ///
663    /// This will fail with an `Err` value if:
664    ///
665    /// - This builder has already been used to build a `Logger`.
666    ///
667    /// - Any of the registered endpoint names are invalid.
668    ///
669    /// - Any of the module name filters set by [`Builder::filter_module()`] have invalid
670    ///   [regex syntax][regex-syntax].
671    ///
672    /// ```no_run
673    /// let logger = log_fastly::Builder::new()
674    ///     .default_endpoint("my_endpoint")
675    ///     .build()
676    ///     .unwrap();
677    /// log::set_boxed_logger(Box::new(logger)).unwrap();
678    /// ```
679    ///
680    /// [regex-syntax]: https://docs.rs/regex#syntax
681    pub fn build(&mut self) -> Result<Logger, Error> {
682        let inner = std::mem::replace(
683            &mut self.inner,
684            Err(anyhow!("Builder can only be built once")),
685        )?;
686
687        // Calculate the overall max logging level, taking into account all the endpoint-specific
688        // and default filters.
689
690        // First, find the max endpoint-specific log level.
691        let endpoint_max = inner
692            .endpoints
693            .values()
694            // If an endpoint doesn't have a filter, it's only constrained by the overall
695            // `max_level`.
696            .map(|e| e.1.unwrap_or_else(LevelFilter::max))
697            .max()
698            .unwrap_or(LevelFilter::Off);
699        // Then, find the highest log level that has a default logger.
700        let default_max = inner
701            .default_endpoints
702            .keys()
703            .max()
704            .map(Level::to_level_filter)
705            .unwrap_or(LevelFilter::Off);
706        // Take whichever of these is bigger, but then clamp it with our `max_level` which takes
707        // precedence.
708        let max_level = std::cmp::min(inner.max_level, std::cmp::max(endpoint_max, default_max));
709
710        let module_filters = generate_module_filters(inner.module_filters)?;
711
712        Ok(Logger {
713            endpoints: inner.endpoints,
714            default_endpoints: inner.default_endpoints,
715            max_level,
716            module_filters,
717            echo_stdout: inner.echo_stdout,
718            echo_stderr: inner.echo_stderr,
719        })
720    }
721}
722
723/// Build a [`RegexSet`] for each logging level from the specified filters.
724fn generate_module_filters(
725    mut regex_map: HashMap<LevelFilter, Vec<String>>,
726) -> Result<Option<HashMap<Level, RegexSet>>, Error> {
727    if regex_map.is_empty() {
728        Ok(None)
729    } else {
730        // build from highest level to lowest, extending the set as we go so that each set contains
731        // all of the regexes specified for the levels above
732        let levels = [
733            LevelFilter::Trace,
734            LevelFilter::Debug,
735            LevelFilter::Info,
736            LevelFilter::Warn,
737            LevelFilter::Error,
738        ];
739        let mut module_filters = HashMap::with_capacity(levels.len());
740        let mut running_regexes = vec![];
741        for level in levels.iter() {
742            if let Some(regexes) = regex_map.remove(level) {
743                running_regexes.extend(regexes);
744            }
745            let matcher = RegexSetBuilder::new(running_regexes.iter()).build()?;
746            let level = level
747                .to_level()
748                .expect("only iterating on LevelFilters that match a Level");
749            module_filters.insert(level, matcher);
750        }
751        Ok(Some(module_filters))
752    }
753}
754
755/// Initialize logging with a single endpoint filtered by log level.
756///
757/// For advanced configuration, see the [`Builder`] type, and the [module-level
758/// documentation](index.html#advanced-configuration).
759///
760/// ```no_run
761/// log_fastly::init_simple("my_endpoint", log::LevelFilter::Warn);
762/// log::warn!("This will be written to my_endpoint...");
763/// log::info!("...but this won't");
764/// ```
765pub fn init_simple<E>(endpoint: E, level: LevelFilter)
766where
767    E: TryInto<Endpoint>,
768    <E as TryInto<Endpoint>>::Error: Into<Error>,
769{
770    Logger::builder()
771        .default_endpoint(endpoint)
772        .max_level(level)
773        .try_init()
774        .expect("log_fastly::init_simple() failed");
775}