#fastly #logging #log-messages #compute #log-level #endpoints #façade

log-fastly

Implementation of the log façade for Fastly Compute

38 releases

new 0.11.1 Dec 3, 2024
0.11.0 Oct 7, 2024
0.10.5 Sep 16, 2024
0.10.2 Jul 9, 2024
0.1.2 Jul 13, 2020

#99 in Debugging

Download history 580/week @ 2024-08-13 495/week @ 2024-08-20 807/week @ 2024-08-27 804/week @ 2024-09-03 696/week @ 2024-09-10 1078/week @ 2024-09-17 1215/week @ 2024-09-24 875/week @ 2024-10-01 1091/week @ 2024-10-08 1020/week @ 2024-10-15 903/week @ 2024-10-22 928/week @ 2024-10-29 1077/week @ 2024-11-05 951/week @ 2024-11-12 879/week @ 2024-11-19 482/week @ 2024-11-26

3,669 downloads per month
Used in perimeterx-fastly-enforce…

Apache-2.0 WITH LLVM-exception

37KB
379 lines

Implementation of the [log] logging façade for Fastly Compute.

With this logger configured, log's logging statements will send log messages to your chosen Real-Time Log Streaming endpoints. You should initialize the logger as soon as your program starts. Logging statements will not do anything before initialization.

See the Fastly documentation for more information about configuring logging endpoints for your service.

Getting started

All you need to get started is your endpoint name and the level of log messages you want to emit. For example, if you have an endpoint called my_endpoint, and you only want to emit log messages at the "Warn" or "Error" level, you can use [init_simple()]:

log_fastly::init_simple("my_endpoint", log::LevelFilter::Warn);
log::warn!("This will be written to my_endpoint...");
log::info!("...but this won't");

Advanced configuration

For more precise control, including multiple endpoints and default endpoints for different logging levels, use the Builder interface. The first example is equivalent to:

log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Warn)
    .default_endpoint("my_endpoint")
    .init();

The [Builder::max_level()] option sets the most verbose level of logging that will be emitted. Logging statements above that level, like log::info!() in the first example, will do nothing.

Note: The default level is LevelFilter::Off, which emits no logging at all. You'll want to change this for most configurations.

[Builder::default_endpoint()] sets the endpoint used whenever a logging statement is called without a target field to specify its endpoint. With the default endpoint set to my_endpoint, the logging statements in the first example are equivalent to:

log::warn!(target: "my_endpoint", "This will be written to my_endpoint...");
log::info!(target: "my_endpoint", "...but this won't");

Use with Compute Log Tailing

Compute Log Tailing is helpful for getting debugging output quickly from a Compute program under development by capturing output from stdout or stderr. To configure logging to output to stdout or stderr in addition to the specified log endpoint, enable echoing when building the logger:

log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Warn)
    .default_endpoint("my_endpoint")
    .echo_stdout(true)
    .init();

Multiple endpoints

Setting an endpoint as the default will automatically register it for use with the logger, but you can register additional endpoints with [Builder::endpoint()]:

log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Warn)
    .default_endpoint("my_endpoint")
    .endpoint("my_other_endpoint")
    .init();
log::warn!(target: "my_endpoint", "This will be written to my_endpoint...");
log::warn!(target: "my_other_endpoint", "...but this will be written to my_other_endpoint");

Per-endpoint logging levels

You can also set a per-endpoint logging level, though levels higher than max_level are always ignored:

log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Warn)
    .default_endpoint("my_endpoint")
    .endpoint_level("my_other_endpoint", log::LevelFilter::Trace)
    .endpoint_level("error_only", log::LevelFilter::Error)
    .init();
log::warn!(target: "my_other_endpoint", "This will be written to my_other_endpoint...");
log::trace!(target: "my_other_endpoint", "...but this won't, because max_level wins");
log::error!(target: "error_only", "This will be written to error_only...");
log::warn!(target: "error_only", "...but this won't, because the endpoint's level is lower");

Per-level default endpoints

In the previous examples, the same endpoint is set as the default for all logging levels. You can also specify default endpoints for individual levels using [Builder::default_level_endpoint()]. The defaults are combined in order, so you can specify an overall default endpoint, and then as many level-specific endpoints as you need:

log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Info)
    .default_endpoint("my_endpoint")
    .default_level_endpoint("error_only", log::Level::Error)
    .init();
log::info!("This will be written to my_endpoint...");
log::warn!(".. and this will too.");
log::error!("But this will be written to error_only");

Module name filters

In addition to per-endpoint logging levels, you can set logging levels based on the name of the Rust module that contains the logging statement.

No filtering is done based on the module name by default, but if any module name patterns are specified, only log statements that match one of the patterns will be emitted. For example, if your application is a crate called my_app, you can filter out any messages other than my_app's :

log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Info)
    .default_endpoint("my_endpoint")
    .filter_module("my_app", log::LevelFilter::Warn)
    .init();
log::warn!("This will be written to my_endpoint");
// This won't emit any log messages, because no patterns match `some_dependency`
some_dependency::function_that_logs();

The filter expressions support the full syntax of the regex crate. This is particularly useful if your patterns overlap, but you'd like to still treat them distinctly. For example, suppose you want to set the log level of the module my_app::my_module to be more restrictive than the top level my_app module. You can use $ to make sure the less-restrictive pattern matches to the end of the module name, instead of matching any module name that contains my_app:

mod my_module {
    pub fn do_a_thing() {
        log::warn!("This won't be written, because this module's max level is Error");
    }
}
log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Info)
    .default_endpoint("my_endpoint")
    .filter_module("my_app$", log::LevelFilter::Warn)
    .filter_module("my_app::my_module", log::LevelFilter::Error)
    .init();
log::warn!("This will be written to my_endpoint");
// This won't emit any log messages, because "my_app$" doesn't match, and "my_app::my_module"
// is limited to Error
my_module::do_a_thing();

Registering endpoints

All endpoints used by your logging statements must be registered when the logger is created. The following functions automatically register an endpoint if it is not already registered.

  • [init_simple()]
  • [Builder::endpoint()]
  • [Builder::endpoint_level()]
  • [Builder::default_endpoint()]
  • [Builder::default_level_endpoint()]

You can pass the endpoint name as a string, or an explicit fastly::log::Endpoint value. The following examples are equivalent:

log_fastly::init_simple("my_endpoint", log::LevelFilter::Info);
log_fastly::init_simple(
    fastly::log::Endpoint::from_name("my_endpoint"),
    log::LevelFilter::Info,
);

If a logging statement uses target: "my_endpoint" but my_endpoint is not registered, the message will be logged to the default endpoint for that level, if one exists.

Endpoint and module name collisions

Due to a limitation of log, logging from a module with the same name as one of your endpoints will cause logs to be sent to that endpoint, even if no target is specified, and the endpoint is not a default. For example, if you have an endpoint named my_app, and your application is a crate also called my_app:

log_fastly::Logger::builder()
    .max_level(log::LevelFilter::Info)
    .default_endpoint("my_endpoint")
    .endpoint("my_app")
    .init();
log::info!("This will be written to my_app, even though my_endpoint is the default");
log::info!(
    target: "my_endpoint",
    "This will be written to my_endpoint, because the target is explicit",
);

We hope to address this issue in future releases, but the current workarounds are to either make sure your endpoint names and crate name are distinct, or use the explicit target syntax whenever logging from your top-level module.

Dependencies

~9–11MB
~204K SLoC