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}