// 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/.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/.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