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 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 msg = format!("{}", record.args());
277 // Given the type, we don't really have much of a choice about what to do if this goes
278 // wrong; panicking is probably not the right thing.
279 let _ = write!(endpoint.clone(), "{}", msg);
280 if self.echo_stdout {
281 println!("{}", msg);
282 }
283 if self.echo_stderr {
284 eprintln!("{}", msg);
285 }
286 }
287 }
288
289 fn flush(&self) {}
290}
291
292impl Logger {
293 /// Get a new [`Builder`].
294 pub fn builder() -> Builder {
295 Builder::new()
296 }
297
298 fn max_level(&self) -> LevelFilter {
299 self.max_level
300 }
301
302 /// Look up an endpoint for a log message.
303 ///
304 /// Returns `None` if no endpoint was found, or if the filter settings would prevent this
305 /// message from being emitted.
306 fn lookup_endpoint(&self, metadata: &Metadata, module_path: Option<&str>) -> Option<&Endpoint> {
307 let level = metadata.level();
308 // Immediately filter out any messages above the max level
309 if level > self.max_level {
310 return None;
311 }
312 if let Some(module_path) = module_path {
313 // If there is a module filter for this level, try to match it
314 if let Some(filter) = self.module_filters.as_ref().and_then(|fs| fs.get(&level)) {
315 if !filter.is_match(module_path) {
316 return None;
317 }
318 }
319 }
320 // If the target matches an endpoint, apply the endpoint-specific filter
321 if let Some((endpoint, filter)) = self.endpoints.get(metadata.target()) {
322 if let Some(filter) = filter {
323 if level <= *filter {
324 Some(endpoint)
325 } else {
326 None
327 }
328 } else {
329 // If there is no filter, and the level didn't exceed the overall max, log it
330 Some(endpoint)
331 }
332 } else {
333 // For unrecognized targets, log it if there's a default endpoint for the level
334 self.default_endpoints.get(&level)
335 }
336 }
337}
338
339/// A builder type for [`Logger`].
340///
341/// You can use this builder to register endpoints, set default endpoints, control the levels of
342/// logging messages emitted, and filter messages based on module name.
343///
344/// A `Builder` is consumed by calling [`Builder::init()`] or [`Builder::try_init()`] to build and
345/// automatically initialize the logger, or by calling [`Builder::build()`] to build the logger for
346/// manual initialization or nesting within another logger. After you call any of these methods, you
347/// can no longer call any of them on that same builder.
348#[derive(Debug)]
349pub struct Builder {
350 inner: Result<Inner, Error>,
351}
352
353#[derive(Debug)]
354struct Inner {
355 /// All of the endpoints that are registered with the logger.
356 endpoints: HashMap<String, (Endpoint, Option<LevelFilter>)>,
357 /// Default endpoints for each of the log levels.
358 default_endpoints: HashMap<Level, Endpoint>,
359 /// The maximum log level for all endpoints.
360 max_level: LevelFilter,
361 /// The module patterns we will match for each log level
362 module_filters: HashMap<LevelFilter, Vec<String>>,
363 /// Whether to echo all log messages to `stdout`.
364 echo_stdout: bool,
365 /// Whether to echo all log messages to `stderr`.
366 echo_stderr: bool,
367}
368
369impl Default for Builder {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375impl Builder {
376 /// Create a new `Builder`.
377 ///
378 /// By default, no endpoints are registered, the maximum log level is set to `Off`, and no
379 /// module name filtering is done.
380 pub fn new() -> Self {
381 let inner = Inner {
382 endpoints: HashMap::new(),
383 default_endpoints: HashMap::new(),
384 max_level: LevelFilter::Off,
385 module_filters: HashMap::new(),
386 echo_stdout: false,
387 echo_stderr: false,
388 };
389 Self { inner: Ok(inner) }
390 }
391
392 /// Run a closure on the inner field, if it exists.
393 fn with_inner<F: FnOnce(&mut Inner) -> R, R>(&mut self, f: F) {
394 // It's fine not to use this result, because we're deferring errors to `build()`
395 let _ = self.inner.as_mut().map(f);
396 }
397
398 /// Run a closure on the inner field if it exists, and a fallible argument.
399 ///
400 /// If the argument is an `Err`, this replaces `inner` with that `Err`.
401 fn with_inner_and_then<A, F, R, E>(&mut self, arg: Result<A, E>, f: F)
402 where
403 F: FnOnce(&mut Inner, A) -> R,
404 E: Into<Error>,
405 {
406 match arg {
407 Ok(x) => self.with_inner(|i| f(i, x)),
408 Err(e) => self.inner = Err(e.into()),
409 }
410 }
411
412 /// Register an endpoint.
413 ///
414 /// ```no_run
415 /// log_fastly::Logger::builder()
416 /// .max_level(log::LevelFilter::Trace)
417 /// .endpoint("my_endpoint")
418 /// .init();
419 /// log::info!(target: "my_endpoint", "Hello");
420 /// ```
421 pub fn endpoint<E>(&mut self, endpoint: E) -> &mut Self
422 where
423 E: TryInto<Endpoint>,
424 <E as TryInto<Endpoint>>::Error: Into<Error>,
425 {
426 self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
427 i.endpoints
428 .insert(endpoint.name().to_owned(), (endpoint, None))
429 });
430 self
431 }
432
433 /// Register an endpoint and set the maximum logging level for its messages.
434 ///
435 /// ```no_run
436 /// log_fastly::Logger::builder()
437 /// .max_level(log::LevelFilter::Trace)
438 /// .endpoint_level("debug_endpoint", log::LevelFilter::Debug)
439 /// .init();
440 /// log::info!(target: "debug_endpoint", "This will be written to debug_endpoint...");
441 /// log::trace!(target: "debug_endpoint", "...but this won't be...");
442 /// ```
443 pub fn endpoint_level<E>(&mut self, endpoint: E, level: LevelFilter) -> &mut Self
444 where
445 E: TryInto<Endpoint>,
446 <E as TryInto<Endpoint>>::Error: Into<Error>,
447 {
448 self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
449 i.endpoints
450 .insert(endpoint.name().to_owned(), (endpoint, Some(level)))
451 });
452 self
453 }
454
455 /// Set the default endpoint for all messages.
456 ///
457 /// The default endpoint is used when the logging statement does not use the `target:
458 /// "endpoint"` syntax.
459 ///
460 /// This overrides any previous default endpoints, set either by this method or by
461 /// [`Builder::default_level_endpoint()`].
462 ///
463 /// ```no_run
464 /// log_fastly::Logger::builder()
465 /// .max_level(log::LevelFilter::Info)
466 /// .default_level_endpoint("error_only", log::Level::Error)
467 /// .default_endpoint("my_endpoint")
468 /// .endpoint("other_endpoint")
469 /// .init();
470 /// log::info!("This will be written to my_endpoint...");
471 /// log::error!("...and this will too");
472 /// log::warn!(target: "other_endpoint", "This will go to other_endpoint, though");
473 /// ```
474 pub fn default_endpoint<E>(&mut self, endpoint: E) -> &mut Self
475 where
476 E: TryInto<Endpoint>,
477 <E as TryInto<Endpoint>>::Error: Into<Error>,
478 {
479 self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
480 for level in &[
481 Level::Error,
482 Level::Warn,
483 Level::Info,
484 Level::Debug,
485 Level::Trace,
486 ] {
487 i.default_endpoints.insert(*level, endpoint.clone());
488 }
489 });
490 self
491 }
492
493 /// Set the default endpoint for all messages of the given level.
494 ///
495 /// The default endpoint is used when the logging statement does not use the `target:
496 /// "endpoint"` syntax.
497 ///
498 /// This overrides any previous default endpoints set for this level, either by this method or
499 /// by [`Builder::default_endpoint()`].
500 ///
501 /// ```no_run
502 /// log_fastly::Logger::builder()
503 /// .max_level(log::LevelFilter::Info)
504 /// .default_endpoint("my_endpoint")
505 /// .default_level_endpoint("error_only", log::Level::Error)
506 /// .endpoint("other_endpoint")
507 /// .init();
508 /// log::info!("This will be written to my_endpoint...");
509 /// log::error!("...but this will be written to error_only");
510 /// log::error!(target: "other_endpoint", "This will go to other_endpoint, though");
511 /// ```
512 pub fn default_level_endpoint<E>(&mut self, endpoint: E, level: Level) -> &mut Self
513 where
514 E: TryInto<Endpoint>,
515 <E as TryInto<Endpoint>>::Error: Into<Error>,
516 {
517 self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
518 i.default_endpoints.insert(level, endpoint.clone());
519 i.endpoints
520 .insert(endpoint.name().to_owned(), (endpoint, None))
521 });
522 self
523 }
524
525 /// Set the maximum logging level for all messages.
526 ///
527 /// No messages that exceed this level will be emitted, even if a higher level is set for a
528 /// specific endpoint or module name.
529 ///
530 /// **Note:** The default level is `LevelFilter::Off`, which emits no logging at all. You'll
531 /// want to change this for most configurations.
532 ///
533 /// ```no_run
534 /// log_fastly::Builder::new()
535 /// .max_level(log::LevelFilter::Warn)
536 /// .endpoint_level("my_endpoint", log::LevelFilter::Info)
537 /// .init();
538 /// log::warn!(target: "my_endpoint", "This will be written to my_endpoint...");
539 /// log::info!(target: "my_endpoint", "...but this won't");
540 /// ```
541 pub fn max_level(&mut self, level: LevelFilter) -> &mut Self {
542 self.with_inner(|i| i.max_level = level);
543 self
544 }
545
546 /// Set a logging level for modules whose names match the given [regular
547 /// expression][regex-syntax].
548 ///
549 /// By default, logging statements in any module are emitted if their level is within
550 /// [`Builder::max_level()`] and the level for their target endpoint. If you configure filters
551 /// with this method, the name of the module calling the logging statement must also match one
552 /// of the patterns, and be within the filter's specified level.
553 ///
554 /// ```no_run
555 /// mod my_module {
556 /// pub fn do_a_thing() {
557 /// log::warn!("This won't be written, because this module's max level is Error...");
558 /// log::error!("...but this will be written");
559 /// }
560 /// }
561 ///
562 /// log_fastly::Logger::builder()
563 /// .max_level(log::LevelFilter::Info)
564 /// .default_endpoint("my_endpoint")
565 /// .filter_module("my_module", log::LevelFilter::Error)
566 /// .init();
567 /// log::info!("This won't be written, because it's not in my_module");
568 /// // This will only emit one log message, because "my_app$" doesn't match, and
569 /// // "my_app::my_module" is limited to Error
570 /// my_module::do_a_thing();
571 /// ```
572 ///
573 /// [regex-syntax]: https://docs.rs/regex#syntax
574 pub fn filter_module(&mut self, regex: &str, level: LevelFilter) -> &mut Self {
575 self.with_inner(|i| {
576 i.module_filters
577 .entry(level)
578 .or_insert_with(|| vec![])
579 .push(regex.to_owned())
580 });
581 self
582 }
583
584 /// Set whether all log messages should be echoed to `stdout` (`false` by default).
585 ///
586 /// If this is set to `true`, all logging statements will write the message to `stdout` in
587 /// addition to the specified endpoint. This is particularly useful when debugging with
588 /// [Compute Log Tailing][log-tailing].
589 ///
590 /// [log-tailing]: https://www.fastly.com/blog/introducing-compute-edge-log-tailing-for-better-observability-and-easier-debugging
591 pub fn echo_stdout(&mut self, enabled: bool) -> &mut Self {
592 self.with_inner(|i| i.echo_stdout = enabled);
593 self
594 }
595
596 /// Set whether all log messages should be echoed to `stderr` (`false` by default).
597 ///
598 /// If this is set to `true`, all logging statements will write the message to `stderr` in
599 /// addition to the specified endpoint. This is particularly useful when debugging with
600 /// [Compute Log Tailing][log-tailing].
601 ///
602 /// [log-tailing]: https://www.fastly.com/blog/introducing-compute-edge-log-tailing-for-better-observability-and-easier-debugging
603 pub fn echo_stderr(&mut self, enabled: bool) -> &mut Self {
604 self.with_inner(|i| i.echo_stderr = enabled);
605 self
606 }
607
608 /// Build the logger and initialize it as the global logger.
609 ///
610 /// ```no_run
611 /// log_fastly::Builder::new()
612 /// .default_endpoint("my_endpoint")
613 /// .init();
614 /// log::info!("Hello");
615 /// ```
616 ///
617 /// # Panics
618 ///
619 /// This may panic for any of the reasons that [`Builder::try_init()`] would return an error.
620 pub fn init(&mut self) {
621 self.try_init().expect("log_fastly::Builder::init() failed")
622 }
623
624 /// Build the logger and initialize it as the global logger.
625 ///
626 /// This will fail with an `Err` value if a global logger is already initialized, or for any of
627 /// the reasons that [`Builder::build()`] can fail.
628 ///
629 /// ```no_run
630 /// log_fastly::Builder::new()
631 /// .default_endpoint("my_endpoint")
632 /// .try_init()
633 /// .unwrap();
634 /// log::info!("Hello");
635 /// ```
636 pub fn try_init(&mut self) -> Result<(), Error> {
637 let logger = self.build()?;
638
639 // Get the overall max level while we still own the logger
640 let max_level = logger.max_level();
641
642 // Now try to install the logger
643 let res = log::set_boxed_logger(Box::new(logger));
644
645 if res.is_ok() {
646 // And finally set the max level in the global `log` state, if we successfully installed
647 // the logger
648 log::set_max_level(max_level);
649 }
650
651 res.map_err(Into::into)
652 }
653
654 /// Build a `Logger`, using up this builder.
655 ///
656 /// This is mainly useful if you want to manually initialize the logger, or nest it within
657 /// another [`log::Log`] implementation.
658 ///
659 /// This will fail with an `Err` value if:
660 ///
661 /// - This builder has already been used to build a `Logger`.
662 ///
663 /// - Any of the registered endpoint names are invalid.
664 ///
665 /// - Any of the module name filters set by [`Builder::filter_module()`] have invalid [regex
666 /// syntax][regex-syntax].
667 ///
668 /// ```no_run
669 /// let logger = log_fastly::Builder::new()
670 /// .default_endpoint("my_endpoint")
671 /// .build()
672 /// .unwrap();
673 /// log::set_boxed_logger(Box::new(logger)).unwrap();
674 /// ```
675 ///
676 /// [regex-syntax]: https://docs.rs/regex#syntax
677 pub fn build(&mut self) -> Result<Logger, Error> {
678 let inner = std::mem::replace(
679 &mut self.inner,
680 Err(anyhow!("Builder can only be built once")),
681 )?;
682
683 // Calculate the overall max logging level, taking into account all the endpoint-specific
684 // and default filters.
685
686 // First, find the max endpoint-specific log level.
687 let endpoint_max = inner
688 .endpoints
689 .values()
690 // If an endpoint doesn't have a filter, it's only constrained by the overall
691 // `max_level`.
692 .map(|e| e.1.unwrap_or_else(LevelFilter::max))
693 .max()
694 .unwrap_or(LevelFilter::Off);
695 // Then, find the highest log level that has a default logger.
696 let default_max = inner
697 .default_endpoints
698 .keys()
699 .max()
700 .map(Level::to_level_filter)
701 .unwrap_or(LevelFilter::Off);
702 // Take whichever of these is bigger, but then clamp it with our `max_level` which takes
703 // precedence.
704 let max_level = std::cmp::min(inner.max_level, std::cmp::max(endpoint_max, default_max));
705
706 let module_filters = generate_module_filters(inner.module_filters)?;
707
708 Ok(Logger {
709 endpoints: inner.endpoints,
710 default_endpoints: inner.default_endpoints,
711 max_level,
712 module_filters,
713 echo_stdout: inner.echo_stdout,
714 echo_stderr: inner.echo_stderr,
715 })
716 }
717}
718
719/// Build a [`RegexSet`] for each logging level from the specified filters.
720fn generate_module_filters(
721 mut regex_map: HashMap<LevelFilter, Vec<String>>,
722) -> Result<Option<HashMap<Level, RegexSet>>, Error> {
723 if regex_map.is_empty() {
724 Ok(None)
725 } else {
726 // build from highest level to lowest, extending the set as we go so that each set contains
727 // all of the regexes specified for the levels above
728 let levels = [
729 LevelFilter::Trace,
730 LevelFilter::Debug,
731 LevelFilter::Info,
732 LevelFilter::Warn,
733 LevelFilter::Error,
734 ];
735 let mut module_filters = HashMap::with_capacity(levels.len());
736 let mut running_regexes = vec![];
737 for level in levels.iter() {
738 if let Some(regexes) = regex_map.remove(&level) {
739 running_regexes.extend(regexes);
740 }
741 let matcher = RegexSetBuilder::new(running_regexes.iter()).build()?;
742 let level = level
743 .to_level()
744 .expect("only iterating on LevelFilters that match a Level");
745 module_filters.insert(level, matcher);
746 }
747 Ok(Some(module_filters))
748 }
749}
750
751/// Initialize logging with a single endpoint filtered by log level.
752///
753/// For advanced configuration, see the [`Builder`] type, and the [module-level
754/// documentation](index.html#advanced-configuration).
755///
756/// ```no_run
757/// log_fastly::init_simple("my_endpoint", log::LevelFilter::Warn);
758/// log::warn!("This will be written to my_endpoint...");
759/// log::info!("...but this won't");
760/// ```
761pub fn init_simple<E>(endpoint: E, level: LevelFilter)
762where
763 E: TryInto<Endpoint>,
764 <E as TryInto<Endpoint>>::Error: Into<Error>,
765{
766 Logger::builder()
767 .default_endpoint(endpoint)
768 .max_level(level)
769 .try_init()
770 .expect("log_fastly::init_simple() failed");
771}