You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
19 KiB

// Package rlog
// A simple Golang logger with lots of features and no external dependencies
//
//
// Rlog is a simple logging package, rich in features. It is configurable 'from
// the outside' via environment variables and/or config file and has no
// dependencies other than the standard Golang library.
//
// It is called "rlog", because it was originally written for the
// [Romana project](https://github.com/romana/romana).
//
//
// FEATURES
//
// * Logging configuration of a running process can be modified, without needing
// to restart it. This allows for on-demand finer level logging, if a process
// starts to experience issues, for example.
//
// * Is configured through environment variables or config file: No need to call a
// special init function of some kind to initialize and configure the logger.
//
// * A new config file can be specified and applied programmatically at any time.
//
// * Offers familiar and easy to use log functions for the usual levels: Debug,
// Info, Warn, Error and Critical.
//
// * Offers an additional multi level logging facility with arbitrary depth,
// called Trace.
//
// * Log and trace levels can be configured separately for the individual files
// that make up your executable.
//
// * Every log function comes in a 'plain' version (to be used like Println)
// and in a formatted version (to be used like Printf). For example, there
// is Debug() and Debugf(), which takes a format string as first parameter.
//
// * Can be configured to print caller info (process ID, module filename and line,
// function name). In addition, can also print the goroutine ID in the caller
// info.
//
// * Has NO external dependencies, except things contained in the standard Go
// library.
//
// * Fully configurable date/time format.
//
// * Logging of date and time can be disabled (useful in case of systemd, which
// adds its own time stamps in its log database).
//
// * By default logs to stderr or stdout. A logfile can be configured via
// environment variable. Output may happen exclusively to the logfile or in
// addition to the output on stderr/stdout. Also, a different output stream
// or file can be specified from within your programs at any time.
//
//
// DEFAULTS
//
// Rlog comes with reasonable defaults, so you can just start using it without any
// configuration at all. By default:
//
// * Log level set to INFO.
//
// * Trace messages are not logged.
//
// * Time stamps are logged with each message.
//
// * No caller information.
//
// * Output is sent to stderr.
//
// All those defaults can easily be changed through environment variables or the
// config file.
//
//
// CONTROLLING RLOG THROUGH ENVIRONMENT OR CONFIG FILE VARIABLES
//
// Rlog is configured via the following settings, which may either be defined as
// environment variables or via a config file.
//
// * RLOG_LOG_LEVEL: Set to "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" or
// "NONE". Any message of a level >= than what's configured will be printed. If
// this is not defined it will default to "INFO". If it is set to "NONE" then
// all logging is disabled, except Trace logs, which are controlled via a
// separate variable. In addition, log levels can be set for individual files
// (see below for more information). Default: INFO - meaning that INFO and
// higher is logged.
//
// * RLOG_TRACE_LEVEL: "Trace" log messages take an additional numeric level as
// first parameter. The user can specify an arbitrary number of levels. Set
// RLOG_TRACE_LEVEL to a number. All Trace messages with a level <=
// RLOG_TRACE_LEVEL will be printed. If this variable is undefined, or set to -1
// then no Trace messages are printed. The idea is that the higher the
// RLOG_TRACE_LEVEL value, the more 'chatty' and verbose the Trace message
// output becomes. In addition, trace levels can be set for individual files
// (see below for more information). Default: Not set - meaning that no trace
// messages are logged.
//
// * RLOG_CALLER_INFO: If this variable is set to "1", "yes" or something else
// that evaluates to 'true' then the message also contains the caller
// information, consisting of the process ID, file and line number as well as
// function name from which the log message was called. Default: No - meaning
// that no caller info is logged.
//
// * RLOG_GOROUTINE_ID: If this variable is set to "1", "yes" or something else
// that evaluates to 'true' AND the printing of caller info is requested, then
// the caller info contains the goroutine ID, separated from the process ID by a
// ':'. Note that calculation of the goroutine ID has a performance impact, so
// please only enable this option if needed.
//
// * RLOG_TIME_FORMAT: Use this variable to customize the date/time format. The
// format is specified either by the well known formats listed in
// https://golang.org/src/time/format.go, for example "UnixDate" or "RFC3339".
// Or as an example date/time output, which is described here:
// https://golang.org/pkg/time/#Time.Format Default: Not set - formatted
// according to RFC3339.
//
// * RLOG_LOG_NOTIME: If this variable is set to "1", "yes" or something else
// that evaluates to 'true' then no date/time stamp is logged with each log
// message. This is useful in environments that use systemd where access to the
// logs via their logging tools already gives you time stamps. Default: No -
// meaning that time/date is logged.
//
// * RLOG_LOG_FILE: Provide a filename here to determine if the logfile should
// be written to a file, in addition to the output stream specified in
// RLOG_LOG_STREAM. Default: Not set - meaning that output is not written to a
// file.
//
// * RLOG_LOG_STREAM: Use this to direct the log output to a different output
// stream, instead of stderr. This accepts three values: "stderr", "stdout" or
// "none". If either stderr or stdout is defined here AND a logfile is specified
// via RLOG_LOG_FILE then the output is sent to both. Default: Not set -
// meaning the output goes to stderr.
//
// There are two more settings, related to the configuration file, which can only
// be set via environment variables.
//
// * RLOG_CONF_FILE: If this variable is set then rlog looks for the config
// file at the specified location, which needs to be the path of the
// file. If this variable is not defined, then rlog will look for the config
// file in /etc/rlog/<your-executable-name>.conf. Therefore, by default every
// executable has its own config file. By setting this variable, you could
// force multiple processes to share the same config file.
// Note that with the SetConfFile() function you can specify a new config file
// programmatically at any time, even with a relative path.
//
// * RLOG_CONF_CHECK_INTERVAL: Number of seconds between checking whether the
// config file has changed. By default, this is set to 15 seconds. This means
// that within 15 seconds a changed logging configuration in the config file
// will take effect. Note that this check is only performed when a log message
// is actually written. If the program does nothing or doesn't log messages, the
// config file won't be read. If there is no config file or it has been removed
// then the configuration from the environment variables is used. Set this value
// to 0 in order to switch off the regular config file checking: The config file
// will then only be read once at the start.
//
// Please note! If these environment variables have incorrect or misspelled
// values then they will be silently ignored and a default value will be used.
//
//
// USING THE CONFIG FILE
//
// A config file for rlog is entirely optional, since rlog works just fine even
// without it. However, it does provide you with a very neat feature: You can
// change the logging configuration of a running program from the outside and
// without having to restart it!
//
// When rlog is imported it starts out with the defaults described above. It then
// takes an initial configuration from environment variables, which may override
// the default values. Next, it looks for the rlog config file. If it cannot find
// the config file it will quietly continue without error. If the config file is
// found then the configuration from environment variables is combined with the
// configuration from the config file. More about how this combination works, and
// what takes precedence, in a moment.
//
// CONFIG FILE LOCATION
//
// The path for the config file can be set via the RLOG_CONF_FILE
// environment variable. Absent that, rlog looks for a config file in
// /etc/rlog/<your-executable-name>.conf. This means that you can easily provide
// different logging configurations for each of your processes.
//
// A new config file location can also be specified at any time via the
// SetConfFile() function. An absolute or relative path may be specfied with that
// function.
//
// CONFIG FILE FORMAT
//
// The format of the config file is simple. Each setting is referred to by the
// same name as the environment variable. So, your config file may look like this:
//
// # Comment lines start with a '#'
// RLOG_LOG_LEVEL = WARN
// RLOG_LOG_STREAM = stdout
// RLOG_TIME_FORMAT= UnixDate
// RLOG_LOG_FILE = /var/log/myapp.log
//
// A few notes about config file formatting:
//
// * Empty lines, or lines starting with '#' are ignored.
//
// * Leading and trailing spaces in lines are removed.
//
// * Everything after the first '=' will be taken as the value of the setting.
//
// * Leading and trailing spaces in values are removed.
//
// * Spaces or further '=' characters within values are taken as they are.
//
// COMBINING CONFIGURATION FROM ENVIRONMENT VARIABLES AND CONFIG FILE
//
// Generally, environment variables take precedence. Assume you have set a log
// level of INFO via the RLOG_LOG_LEVEL variable. This value will be used,
// even if you specified DEBUG in the config file, since an explicitly set
// environment variable takes precedence.
//
// There are only two cases when a config file value takes precedence:
//
// 1. If you do not have an explicit value set in the environment variable. For
// example, if you do not have the RLOG_LOG_LEVEL environment variable defined
// at all, or if it is set to the empty string.
//
// 2. If you apply a '!' as prefix in the config file. That marks this value as
// higher priority than the environment variable. Consider the following config
// file as example. Here RLOG_LOG_LEVEL and RLOG_TIME_FORMAT will take
// precedence over whatever was defined in the environment variables.
//
// An example of using '!' in the config file:
//
// !RLOG_LOG_LEVEL=WARN
// RLOG_LOG_STREAM=stdout
// !RLOG_TIME_FORMAT=UnixDate
// RLOG_LOG_FILE=/var/log/myapp.log
//
//
// UPDATING LOGGING CONFIG FROM THE OUTSIDE: BY MODIFYING THE CONFIG FILE
//
// Every time you log a message and at least RLOG_CONF_CHECK_INTERVAL seconds have
// elapsed since the last reading of the config file, rlog will automatically
// re-read the content of the conf file and re-apply the configuration it finds
// there over the initial configuration, which was based on the environment
// variables.
//
// You can always just delete the config file to go back to the configuration
// based solely on environment variables.
//
// UPDATING LOGGING CONFIG FROM THE INSIDE: BY MODIFYING YOUR OWN ENVIRONMENT VARIABLES
//
// A running program may also change its rlog configuration on its own: The
// process can use the os.Setenv() function to modify its own environment
// variables and then call rlog.UpdatEnv() to reapply the settings
// from the environment variables. The examples/example.go file shows how this
// is done. But in short:
//
// // Programmatically change an rlog setting from within the program
// os.Setenv("RLOG_LOG_LEVEL", "DEBUG")
// rlog.UpdateEnv()
//
// Note that this will not change rlog behaviour if the value for this config
// setting was specified with a '!' in the config file.
//
//
// PER FILE LEVEL LOG AND TRACE LEVELS
//
// In most cases you might want to set just a single log or trace level, which is
// then applied to all log messages in your program. With environment variables,
// you would set it like this:
//
// export RLOG_LOG_LEVEL=INFO
// export RLOG_TRACE_LEVEL=3
//
// However, with rlog the log and trace levels can not only be configured
// 'globally' with a single value, but can also independently be set for the
// individual module files that were compiled into your executable. This is useful
// if enabling high trace levels or DEBUG logging for the entire executable would
// fill up logs or consume too many resources.
//
// For example, if your executable is compiled out of several files and one of
// those files is called 'example.go' then you could set log levels like this:
//
// export RLOG_LOG_LEVEL=INFO,example.go=DEBUG
//
// This sets the global log level to INFO, but for the messages originating from
// the module file 'example.go' it is DEBUG.
//
// Similarly, you can set trace levels for individual module files:
//
// export RLOG_TRACE_LEVEL=example.go=5,2
//
// This sets a trace level of 5 for example.go and 2 for everyone else.
//
// More examples:
//
// # DEBUG level for all files whose name starts with 'ex', WARNING level for
// # everyone else.
// export RLOG_LOG_LEVEL=WARN,ex*=DEBUG
//
// # DEBUG level for example.go, INFO for everyone else, since INFO is the
// # default level if nothing is specified.
// export RLOG_LOG_LEVEL=example.go=DEBUG
//
// # DEBUG level for example.go, no logging for anyone else.
// export RLOG_LOG_LEVEL=NONE,example.go=DEBUG
//
// # Multiple files' levels can be specified at once.
// export RLOG_LOG_LEVEL=NONE,example.go=DEBUG,foo.go=INFO
//
// # The default log level can appear anywhere in the list.
// export RLOG_LOG_LEVEL=example.go=DEBUG,INFO,foo.go=WARN
//
// Note that as before, if in RLOG_LOG_LEVEL no global log level is specified then
// INFO is assumed to be the global log level. If in RLOG_TRACE_LEVEL no global
// trace level is specified then -1 (no trace output) is assumed as the global
// trace level.
//
//
// USAGE EXAMPLE
//
// import "github.com/romana/rlog"
//
// func main() {
// rlog.Debug("A debug message: For the developer")
// rlog.Info("An info message: Normal operation messages")
// rlog.Warn("A warning message: Intermittent issues, high load, etc.")
// rlog.Error("An error message: An error occurred, I will recover.")
// rlog.Critical("A critical message: That's it! I give up!")
// rlog.Trace(2, "A trace message")
// rlog.Trace(3, "An even deeper trace message")
// }
//
// For a more interesting example, please check out 'examples/example.go'.
//
//
// SAMPLE OUTPUT
//
// With time stamp, trace to level 2, log level WARNING, no caller info:
//
// $ export RLOG_LOG_LEVEL=WARN
// $ export RLOG_TRACE_LEVEL=2
// $ go run examples/example.go
//
// 2017-11-16T08:06:56+13:00 WARN : Warning level log message
// 2017-11-16T08:06:56+13:00 ERROR : Error level log message
// 2017-11-16T08:06:56+13:00 CRITICAL : Critical level log message
// 2017-11-16T08:06:56+13:00 DEBUG : You can see this message, because we changed level to DEBUG.
// 2017-11-16T08:06:56+13:00 TRACE(1) : Trace messages have their own numeric levels
// 2017-11-16T08:06:56+13:00 TRACE(1) : To see them set RLOG_TRACE_LEVEL to the cut-off number
// 2017-11-16T08:06:56+13:00 TRACE(1) : We're 1 levels down now...
// 2017-11-16T08:06:56+13:00 TRACE(2) : We're 2 levels down now...
// 2017-11-16T08:06:56+13:00 INFO : Reached end of recursion at level 10
// 2017-11-16T08:06:56+13:00 INFO : About to change log output. Check /tmp/rlog-output.log...
// 2017-11-16T08:06:56+13:00 INFO : Back to stderr
//
// With time stamp, log level WARN, no trace logging (switched off by unsetting
// the variable), but with caller info ('23730' in the example below is the
// process ID):
//
// $ export RLOG_CALLER_INFO=yes
// $ export RLOG_LOG_LEVEL=WARN
// $ export RLOG_TRACE_LEVEL=
// $ go run examples/example.go
//
// 2017-11-16T08:07:57+13:00 WARN : [21233 examples/example.go:31 (main.main)] Warning level log message
// 2017-11-16T08:07:57+13:00 ERROR : [21233 examples/example.go:32 (main.main)] Error level log message
// 2017-11-16T08:07:57+13:00 CRITICAL : [21233 examples/example.go:33 (main.main)] Critical level log message
// 2017-11-16T08:07:57+13:00 DEBUG : [21233 examples/example.go:42 (main.main)] You can see this message, because we changed level to DEBUG.
// 2017-11-16T08:07:57+13:00 INFO : [21233 examples/example.go:17 (main.someRecursiveFunction)] Reached end of recursion at level 10
// 2017-11-16T08:07:57+13:00 INFO : [21233 examples/example.go:52 (main.main)] About to change log output. Check /tmp/rlog-output.log...
// 2017-11-16T08:07:57+13:00 INFO : [21233 examples/example.go:61 (main.main)] Back to stderr
//
// Without time stamp, no trace logging, no caller info:
//
// $ export RLOG_LOG_NOTIME=yes
// $ export RLOG_CALLER_INFO=no
// $ go run examples/example.go
//
// WARN : Warning level log message
// ERROR : Error level log message
// CRITICAL : Critical level log message
// DEBUG : You can see this message, because we changed level to DEBUG.
// INFO : Reached end of recursion at level 10
// INFO : About to change log output. Check /tmp/rlog-output.log...
// INFO : Back to stderr
//
// With time stamp in RFC822 format.
//
// $ export RLOG_LOG_NOTIME=no
// $ export RLOG_TIME_FORMAT=RFC822
// $ go run examples/example.go
//
// 2017-11-16T08:08:49+13:00 WARN : Warning level log message
// 2017-11-16T08:08:49+13:00 ERROR : Error level log message
// 2017-11-16T08:08:49+13:00 CRITICAL : Critical level log message
// 2017-11-16T08:08:49+13:00 DEBUG : You can see this message, because we changed level to DEBUG.
// 2017-11-16T08:08:49+13:00 INFO : Reached end of recursion at level 10
// 2017-11-16T08:08:49+13:00 INFO : About to change log output. Check /tmp/rlog-output.log...
// 2017-11-16T08:08:49+13:00 INFO : Back to stderr
//
// With custom time stamp:
//
// $ export RLOG_TIME_FORMAT="2006/01/06 15:04:05"
// $ go run examples/example.go
//
// 2017-11-16T08:09:08+13:00 WARN : Warning level log message
// 2017-11-16T08:09:08+13:00 ERROR : Error level log message
// 2017-11-16T08:09:08+13:00 CRITICAL : Critical level log message
// 2017-11-16T08:09:08+13:00 DEBUG : You can see this message, because we changed level to DEBUG.
// 2017-11-16T08:09:08+13:00 INFO : Reached end of recursion at level 10
// 2017-11-16T08:09:08+13:00 INFO : About to change log output. Check /tmp/rlog-output.log...
// 2017-11-16T08:09:08+13:00 INFO : Back to stderr
//
//
//
//
package rlog