18 releases

0.4.4 Aug 6, 2021
0.4.3 Jan 31, 2021
0.4.1 Oct 20, 2020
0.4.0 Jul 20, 2020
0.1.3 Nov 10, 2018

#542 in Debugging


Used in spirit

Apache-2.0 OR MIT

350KB
4K SLoC

Spirit-log

Travis Build Status

Helpers and configuration fragments to integrate logging into the spirit configuration framework.

See the docs and the examples.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.


lib.rs:

A spirit fragments and helpers to configure and control logging.

The Fragments here allow to configure relatively complex logging (multiple loggers, different formats, different destinations), both from command line and the configuration. It allows runtime reloading of them.

Internally it is based on the fern crate and just adds the configuration and runtime reloading (through log-reroute).

It assumes the application doesn't set the global logger itself. It also sets the panic hook through the log_panics crate. The with-backtrace cargo feature is propagated through.

Features

  • background: Includes the ability to log asynchronously ‒ the writing to log files happens in a background thread and allows the rest of the application to not block on IO.
  • cfg-help: Support for configuration options help at runtime. On by default.
  • with-backtrace: The log_panics logs with backtraces. On by default.
  • syslog: Adds the support for logging into syslog.

Startup

When using the automatic management with a pipeline, this is how a startup happens:

  • As soon as the pipeline is registered, a logging on the WARN level is sent to stderr.
  • After command line arguments are parsed the stderr logging is updated to reflect that (or left on the WARN level if nothing is set by the user).
  • After configuration is loaded from the files, full logging is configured according to that.

Integration with other loggers

If you need something specific (for example sentry), you can plug in additional loggers through the pipeline ‒ the Dispatch allows adding arbitrary loggers. The Pipeline::map is a good place to do it.

Performance warning

This allows the user to create arbitrary number of loggers. Furthermore, the logging is synchronous by default and not buffered. When writing a lot of logs or sending them over the network, this could become a bottleneck.

Background logging

The background feature flag adds the ability to do the actual logging in a background thread. This allows not blocking the actual application by IO or other expensive operations.

On the other hand, if the application crashes, some logs may be lost (or, depending on setup, when the logging thread doesn't keep up). Also, you need to flush the logger on shutdown, by using the FlushGuard.

It is done through the Background transformation.

Planned features

These pieces are planned some time in future, but haven't happened yet (pull requests are welcome).

  • Reconnecting to the remote server if a TCP connection is lost.
  • Log file rotation.
  • Colors on stdout/stderr.

Usage without Pipelines

It is possible to use without the Pipeline, manually. However, certain care needs to be taken to initialize everything that needs to be initialized.

It is either possible to just get the Dispatch object and call apply, that however is a single-shot initialization and the logger can't be replaced.

The helper functions init and install can be used to gain the ability to replace Dispatch loggers multiple times.

Examples

Manual single use installation

use spirit::AnyError;
use spirit::prelude::*;
use spirit_log::Cfg;

// Well, you'd get it somewhere from configuration, but…
let cfg = Cfg::default();
let logger = cfg.create("logger")?;
logger.apply()?;

Manual multiple-use installation

use spirit::AnyError;
use spirit::prelude::*;
use spirit_log::Cfg;

spirit_log::init();
// This part can be done multiple times.
let cfg = Cfg::default();
let logger = cfg.create("logger")?;
spirit_log::install(logger);

Automatic usage with a Pipeline, reloading and command line options

use log::info;
use serde::Deserialize;
use spirit::{Pipeline, Spirit};
use spirit::prelude::*;
use spirit_log::{Cfg as LogCfg, CfgAndOpts as LogBoth, Opts as LogOpts};
use structopt::StructOpt;

#[derive(Clone, Debug, StructOpt)]
struct Opts {
    #[structopt(flatten)]
    logging: LogOpts,
}

impl Opts {
    fn logging(&self) -> LogOpts {
        self.logging.clone()
    }
}

#[derive(Clone, Debug, Default, Deserialize)]
struct Cfg {
    #[serde(default, skip_serializing_if = "LogCfg::is_empty")]
    logging: LogCfg,
}

impl Cfg {
    fn logging(&self) -> LogCfg {
        self.logging.clone()
    }
}

fn main() {
    Spirit::<Opts, Cfg>::new()
        .with(
            Pipeline::new("logging").extract(|opts: &Opts, cfg: &Cfg| LogBoth {
                cfg: cfg.logging(),
                opts: opts.logging(),
            }),
        )
        .run(|_spirit| {
            info!("Hello world");
            Ok(())
        });
}

The configuration could look something like this:

[[logging]]
level = "DEBUG"
type = "file"
filename = "/tmp/example.log"
clock = "UTC"

Dependencies

~5.5–8MB
~141K SLoC