Crate hclog

Source
Expand description

A configurable logging library for Rust

The hclog (highly configurable log) library for Rust is designed to be a highly configurable logging solution. Its primary goal is to offer a customizable logging backend that caters to individual user needs, providing detailed control over logging options. Users have the flexibility to select from a variety of logging backends, specify the information included in log messages, and choose the desired module or logging level.

§Idea of this crate

Existing log crates often provide a limited set of logging levels, such as error, info, and debug. While this may be sufficient for smaller applications, larger applications - especially those with modularized environments - require more precise control over logging output. In a modularized environment, different components like the GUI, network stack and storage subsystem generate log events, making it challenging to manage and analyze logs. A limited set of logging levels can lead to inaccurate, misleading or a massive amount of log messages which are not helpful for debugging or monitoring. A more fine-grained control over the desired log level can increase the quality of the log messages and reduce the complexity of the log output.

hclog is designed to address this issue(s) by providing fine-grained control over logging output. Each module has it’s own scope with its own LogKeys, which can be configured and turned on and off individually. This allows developers to tailor logging settings to specific components, ensuring that log events are accurate, informative, and actionable. For example, a web application might define separate scopes for its web server, database, and authentication modules, each with its own logging configuration.

§Concept of this crate

This library’s core concept is a key-based logging system, where each logger or logging backend is uniquely identified by a LogKey. Each LogKey can be individually configured with its own logging Level and facade variant FacadeVariant.

To avoid naming conflicts between different modules or crates, LogKeys are organized within a scope, which serves as a namespace or container. When an application integrates multiple libraries, each defines its own set of LogKeys, which are stored within separate scopes. This hierarchical structure allows for organized and collision-free logging management. Each LogKey is a unique identifier which can be configured on his own. Note that all identifiers should be exclusive to the scope and not be used in any other scopes.

Assume a sample application with three modules A, B and C that look like this:

+---------------+
|  Application  |
+-------+-------+
        |
        |        +---+---+    +---+---+    +---+---+
        +--------+ Mod A +----+ Mod B +----+ Mod C +
        |        +---+---+    +---+---+    +---+---+
        |            |            |            |
+-------+------------+------------+------------+-----+
| App Scope   | ModA Scope | ModB Scope | ModC Scope |
+-------------+------------+------------+------------+
| AA          | MAA        | MBA        | MCA        |
| AB          | MAB        | MBB        | MCB        |
| AC          | MAC        | MBC        | MCC        |
| AD          | MAD        | MBC        | MCD        |
+----------------------------------------------------+

The usage and examples below show how a sample imlementation of this structure could look like.

§Usage

The basic logging functionality is provided by the lI!, lD1!, lE! and so on macros. These macros expect a LogKey - as defined on initialization - and a format string similar to common print! macros in rust. The LogKey is used to identify the logger and the format string is the message to be printed. Taken the App Scope from the example above the minimal usage would look like this:

hclog::lI!(AA, "This is an info message");
hclog::lD5!(AB, "This is a debug message with level {}", Level::Debug5);

Avoid writing expressions with side-effects in log statements. They may not be evaluated.

§In Libraries

The example above just shows the basic usage of the logging macros without the actual initialization of those keys. The following example shows how to initialize the library and define the LogKeys for the ModA library:

use hclog::{Scope, LogKey, Level, FacadeVariant, options::Options, Result};

#[derive(Copy, Clone, Debug)]
enum ModAKeys {
 MAA,
 MAB,
 MAC,
 MAD,
}
use ModAKeys::*;

impl std::fmt::Display for ModAKeys {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self {
            Self::MAA => "MAA",
            Self::MAB => "MAB",
            Self::MAC => "MAC",
            Self::MAD => "MAD",
        })
    }
}

impl Scope for ModAKeys {
    fn default_level() -> Level { Level::Info }
    fn init<S: std::fmt::Display>(
        name: S, level: Level, facade: FacadeVariant, options: Options
    ) -> Result<()> {
        hclog::init::<Self, S>(name, level, facade, options)?;
        hclog::add_submodules(&[Self::MAA, Self::MAB, Self::MAC, Self::MAD])
    }
}

impl LogKey for ModAKeys {
    fn log_key(&self) -> usize {
        match self {
            Self::MAA => 0,
            Self::MAB => 1,
            Self::MAC => 2,
            Self::MAD => 3,
        }
    }
}

fn do_log() {
    hclog::lI!(MAA, "this is a info message in Library Scope");
    hclog::LD10!(MAA, "this is a debug message in Library Scope");
}

fn init_mod_a(level: Level, facade: FacadeVariant, options: Options) -> Result<()> {
    /* main initialization part of the library */
    ModAKeys::init("ModA", level, facade, options)?;
    Ok(())
}

The LogKeys are defined as an enum implementing the LogKey and Scope traits. Implementing the Scope trait is necessary to initialize the library - if not already done - and also creates the namespace for this set of LogKeys. The LogKey trait is used to define the unique identifier for the loggers in this scope. Additionally, the Display trait is required - as a bound by LogKey - to display the name in the log output.

§In Applications/executables

Taking the example above a sample application could look like this:

Note: The example below uses the same Level and FacadeVariant for the application and the libraries. This is not necessary and can be configured individually.

use hclog::{Scope, LogKey, Level, FacadeVariant, options::Options, Result};
// use moda;  // import the 'module' above

#[derive(Copy, Clone, Debug)]
enum AppKeys {
   AA,
   AB,
   AC,
   AD,
}
use AppKeys::*;

impl std::fmt::Display for AppKeys {
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self {
            Self::AA => "AA",
            Self::AB => "AB",
            Self::AC => "AC",
            Self::AD => "AD",
        })
    }
}

impl Scope for AppKeys {
    fn init<S: std::fmt::Display>(
        name: S, level: Level, facade: FacadeVariant, options: Options
    ) -> Result<()> {
        hclog::init::<Self, S>(name, level, facade, options)?;
        hclog::add_submodules(&[Self::AA, Self::AB, Self::AC])?;
        // call initialization routine in the library
        // moda::init_mod_a(level, facade, options)?;
        Ok(())
    }
}

impl LogKey for AppKeys {
    fn log_key(&self) -> usize {
        match self {
            Self::AA => 0,
            Self::AB => 1,
            Self::AC => 2,
            Self::AD => 3,
        }
    }
}

fn main() {
   AppKeys::init("MyApp", Level::Debug1, FacadeVariant::StdOut, Options::default()).unwrap();

   // moda::do_log();     // logs in the scope of the library
   hclog::lI!(AA, "this is a info message in App Scope");
}

Applications or executables also define their own set of LogKeys which are used to log messages within the scope of the application. The initialization of the application is done by calling the init function of the Scope implementation of the LogKey type. The application can also initialize the loggers of the libraries it uses.

If a user of this Application wants to get just the logging output of librarys ModA LogKey MAA he can either initialize the library with a desired level and disable logging in the application or provide a commandline option to filter the output of the application which might look like this:

[foo ~]# ./myapp -l "MAA:debug1"

This would print the lI! message in the library but not the lD10! message.

§Usage with crate hclog_macros

The hclog_macros crate provides an attributable derive macro for the usage with this crate. It allows you to derive all traits necessary to initialize the library and configure default values at compile time. For more informations consult the documentation of the hclog_macros crate.

use hclog::{Level, FacadeVariant};
use hclog_macros::HCLog;

#[derive(HCLog, Copy, Clone)]
#[hclog(default_level = Level::Info, default_facade = FacadeVariant::StdOut)]
enum ModBKeys {
  #[hclog(name = "A", level = Level::Debug1)]
  MBA,
  #[hclog(name = "B", facade = FacadeVariant::StdErr)]
  MBB,
  #[hclog(name = "C")]
  MBC,
  #[hclog(name = "D")]
  MBD,
}

use ModBKeys::*;

fn main() {
   ModBKeys::init_with_defaults("MyLogKeys").unwrap();
   hclog::lD1!(MBB, "this won't get logged because of lvl Info for Key DB");
   hclog::lD1!(MBA, "this will get logged because of lvl Debug1 for Key DA");
   hclog::lE!(MBB, "this will be logged to stderr");
}

§Warning

The library internal context may be init only once.

§Crate Feature Flags

The following feature flags are available for this crate. They are configured in your Cargo.toml:

  • std: Enabled by default. This flag does not enable any additional features.
  • derive: This flag enables the derive macro for the HCLog trait.
[dependencies]
hclog = { version = "0.1", features = ["derive"] }

§Version compatibility

This crate is currently compatible with Rust 1.74.1 and later. We will try to keep this compatibility as long as possible. If you need a specific version of Rust please open an issue on the GitHub repository. Hence this is still a version 0.1.0 crate the API is not stable and might change in the future. We will try to keep the changes as minimal as possible and provide a migration guide or compability layer if necessary.

Modules§

options
Available Options to configure the log output of this crate

Macros§

hclog
Log a message with severity $lvl via LogKey
lA
Log a message with severity Alert via LogKey
lC
Log a message with severity Crit via LogKey
lD1
Log a message with severity Debug1 via LogKey
lD2
Log a message with severity Debug2 via LogKey
lD3
Log a message with severity Debug3 via LogKey
lD4
Log a message with severity Debug4 via LogKey
lD5
Log a message with severity Debug5 via LogKey
lD6
Log a message with severity Debug6 via LogKey
lD7
Log a message with severity Debug7 via LogKey
lD8
Log a message with severity Debug8 via LogKey
lD9
Log a message with severity Debug9 via LogKey
lD10
Log a message with severity Debug10 via LogKey
lE
Log a message with severity Error via LogKey
lEM
Log a message with severity Emerg via LogKey
lI
Log a message with severity Info via LogKey
lN
Log a message with severity Notice via LogKey
lW
Log a message with severity Warn via LogKey
tA
Test if severity Alert is enabled for LogKey
tC
Test if severity Crit is enabled for LogKey
tD1
Test if severity Debug1 is enabled for LogKey
tD2
Test if severity Debug2 is enabled for LogKey
tD3
Test if severity Debug3 is enabled for LogKey
tD4
Test if severity Debug4 is enabled for LogKey
tD5
Test if severity Debug5 is enabled for LogKey
tD6
Test if severity Debug6 is enabled for LogKey
tD7
Test if severity Debug7 is enabled for LogKey
tD8
Test if severity Debug8 is enabled for LogKey
tD9
Test if severity Debug9 is enabled for LogKey
tD10
Test if severity Debug10 is enabled for LogKey
tE
Test if severity Error is enabled for LogKey
tEM
Test if severity Emerg is enabled for LogKey
tI
Test if severity Info is enabled for LogKey
tN
Test if severity Notice is enabled for LogKey
tW
Test if severity Warn is enabled for LogKey
tX
Test if a given Level is enabled for a given LogKey

Enums§

ErrorKind
A list of possible errors that can occur in this library
FacadeVariant
Declaration of the different available log facacdes (log targets).
InternalLogKeys
Internal keys for the hclog library.
Level
Declaration of available Log Levels which describe the importance of a log message.
ScopeKey
Identifier of the Scope

Traits§

LogKey
Trait for the LogKey
Scope
Initialization trait for the log scope

Functions§

add_submodules
Add a list of LogKeys to the a Scope
dump
Dump the internal state of the logging system to the supplied writer
has_module
Check if a module is initialized in a given Scope
init
Initialize the logging system.
init_log_compat
Initialize the compatibility layer to the [crate-log] crate
init_modules
Initialize a Scope with a list of provided LogKeys.
list_modules
Print a list of all available modules to the supplied writer w
log
reset_module_options
Reset the options of a given LogKey K
scope
set_level
Set a Level for a single LogKey K
set_logdest
Set the log destination FacadeVariant for a given LogKey K
set_mod_level
Set the log level for a list of modules.
set_module_options
Set one or multiple Options for a given LogKey
test_log
unset_module_options
Unset one or multiple Options for a given LogKey

Type Aliases§

ContextKey
Alias for the Index of the Scope in the context
Result
Result type for this crate