gelf 0.5.0

A library for logging GELF messages to a Graylog compatible server
Documentation
use std::collections::HashMap;

use hostname;
use log;
use log::set_boxed_logger;
use crate::{Backend, Error, Message, WireMessage};
use crate::errors::Result;

/// Logger for sending log-messages
///
/// A `Logger` instance can be either used as a standalone object to log directly
/// to a log-server or it can be installed as a `log`-crate log-handler (with `Logger::install`).
///
/// By default all encountered errors will be silently ignored. If you want the logger
/// to panic when an error occurs, you can change the behaviour with `Logger::enable_panic_on_error`.
pub struct Logger {
    hostname: String,
    backend: Box<dyn Backend>,
    default_metadata: HashMap<String, String>,
    panic_on_error: bool,
}

impl Logger {
    /// Construct a new `Logger` instance
    ///
    /// The backend needs to be boxed for usage as a logger with the `log`-crate.
    /// This constructor tries to determine the local hostname (required by GELF)
    /// with the help of the `hostname`-crate. If you want to set a custom hostname
    /// check out the `Logger::new_with_hostname` constructor.
    pub fn new(backend: Box<dyn Backend>) -> Result<Self> {
        hostname::get_hostname()
            .map(|hostname| Logger::new_with_hostname(backend, &hostname))
            .ok_or_else(|| format_err!("Failed to determine local hostname")
                    .context(Error::LoggerCreateFailed)
                    .into())
    }

    /// Construct a new `Logger` instance with predetermined hostname
    ///
    /// The backend needs to be boxed for usage as a logger with the `log`-crate. It
    /// uses the passed hostname for the GELF `host` field
    pub fn new_with_hostname(backend: Box<dyn Backend>, hostname: &str) -> Logger {
        Logger {
            hostname: String::from(hostname),
            backend: backend,
            default_metadata: HashMap::new(),
            panic_on_error: false,
        }
    }

    /// Install a logger instance as a `log`-Logger
    ///
    /// This method wraps `log::set_logger` as a convenience function as required
    /// by the `log`-crate. `log_level` defines the maximum log-level the logger
    /// should log.
    ///
    /// Note that installing the logger consumes it. To uninstall you need to call
    /// `log::shutdown_logger` which returns the boxed, original `Logger` instance.
    pub fn install<T: Into<log::LevelFilter>>(self, log_level: T) -> Result<()> {
        set_boxed_logger(Box::new(self))?;
        log::set_max_level(log_level.into());

        Ok(())
    }

    /// Log a message via the logger's transport to a GELF server.
    ///
    /// The logger will automatically add `default_metadata` fields to the message
    /// if missing in the passed `Message`.
    pub fn log_message(&self, msg: Message) {
        let result = self.backend.log_message(WireMessage::new(msg, &self));

        if result.is_err() && self.panic_on_error {
            panic!(result.unwrap_err());
        }
    }

    /// Return the hostname used for GELF's `host`-field
    pub fn hostname(&self) -> &String {
        &self.hostname
    }

    /// Set the hostname used for GELF's `host`-field
    pub fn set_hostname<S: Into<String>>(&mut self, hostname: S) -> &mut Self {
        self.hostname = hostname.into();
        self
    }

    /// Return all default metadata
    pub fn default_metadata(&self) -> &HashMap<String, String> {
        &self.default_metadata
    }

    /// Set a default metadata field
    ///
    /// Every logged `Message` is checked for every default_metadata field.
    /// If it contains an entry with the key, the default is ignored. But if
    /// there is no additional information present, the default is added to the message.
    ///
    /// This can be used for example to add a `facility` to every message:
    ///
    /// ```
    /// # use gelf::{Logger, NullBackend, Message};
    /// # let backend = NullBackend::new();
    /// # let mut logger = Logger::new(Box::new(backend)).unwrap();
    /// logger.set_default_metadata(String::from("facility"), String::from("my_awesome_rust_service"));
    ///
    /// logger.log_message(Message::new(String::from("This is important information")));
    /// // -> The message will contain an additional field "_facility" with the value "my_awesome_rust_service"
    /// ```
    pub fn set_default_metadata<S, T>(
        &mut self,
        key: S,
        value: T
    ) -> &mut Self
    where
        S: Into<String>,
        T: Into<String>
    {
        self.default_metadata.insert(key.into(), value.into());
        self
    }

    /// Return a flag whether the logger panics when it encounters an error
    pub fn panic_on_error(&self) -> bool {
        self.panic_on_error
    }

    /// Force the logger to panic when it encounters an error
    pub fn enable_panic_on_error(&mut self) -> &mut Self {
        self.panic_on_error = true;
        self
    }

    /// Force the logger to ignore an encountered error silently
    pub fn disable_panic_on_error(&mut self) -> &mut Self {
        self.panic_on_error = false;
        self
    }
}

impl log::Log for Logger {
    /// Determines if a log message with the specified metadata would be logged.
    ///
    /// See [docs](https://doc.rust-lang.org/log/log/trait.Log.html#tymethod.enabled)
    /// for more details
    fn enabled(&self, _: &log::Metadata) -> bool {
        // The logger does not dicard any log-level by itself, therefore it is
        // always enabled
        true
    }

    /// Logs the `LogRecord`.
    /// See [docs](https://doc.rust-lang.org/log/log/trait.Log.html#tymethod.log)
    /// for more details
    fn log(&self, record: &log::Record) {
        if !self.enabled(record.metadata()) {
            ()
        }

        self.log_message(From::from(record))
    }

    fn flush(&self) {}
}