GoOLog2 is a Go implementation of a logging framework I have made and used for years in Aveco. The original framework has been written in C++.
The main concept making the framework different to other ordinary logging frameworks is distinction between severity and verbosity:
- severity describes type of a logged message and its severity to logging process.
- verbosity describes importance of a logged message.
Obviously, both concepts are not independent. One can hardly imagine logging of a critical error at a low level of importance or logging of a debugging message at a high level of importance. I guess this dependency is the reason why most of the frameworks don't distinct them.
However, the concepts are actually different. The best example is the information severity (Info). This kind of messages covers entire spectrum of verbosity levels: from highly important messages like server starting and stopping to low important messages ment only for developers.
The framework distinguishes severity levels:
- critical an error which cannot be recovered and which cause immediate ending of the process/service. The critical errors are usually last items in the log because they are logged just before the forced end caused by the error.
- error severity marks errors of the process's state which can be recovered. An example can be loosing of connection to another service. The process can work somehow until the connection is re-established again. A common mistake is logging of errors returned to clients (if the logging process is a server) with the error severity. These conditions are not an errors of the server itself, hence they shouldn't be reported so.
- warning severity reports some suspicious or potentially dangerous conditions. A using of a default for a missing configuration variable is an example.
- info severity is the most usable one. All ordinary information about run of the process are reported at this severity (but at different verbosity levels).
- debug severity is dedicated only for developers. Messages at this level shouldn't be seen in production environment.
The verbosity levels are defined by numbers. In theory, number of levels is not limited. Practically, we have used 6 levels:
- 0 logging disabled,
- 1,2 messages important to operations,
- 3,4 messages important to developers,
- 5 too often periodical messages which would exhaust disk space so they are disabled at standard circumstances.
Every message can be attached to a subsystem. The subsystem can be a library or a module or special kind of log. Loggers attached to a subsystem accept only messages of the subsystem. Loggers not attached to any subsystem accept any message.
A logger is and abstraction of a logging target. Currently there are two loggers implemented: a file logger and a console logger. The original framework implemented two other loggers: logging into an Aveco's proprietary logging system and logging into standard Unix syslog. The last one is going to be implemented in the near future.
Usage of the framework is easy. There is one global logging dispatcher. At the beginning of the process, the dispatcher must be initialized:
import(
olog2 "github.com/Staon/goolog2"
)
olog2.Init("my_process")
The Init function accepts a name called system. The name should be unique allowing distinction of process's messages in a combined log like syslog.
Next step of the initialization is creation of requested loggers:
olog2.AddFileLogger("file", "", olog2.MaskAll, 4, "file.log", false)
olog2.AddConsoleLoggerStderr("console", "", olog2.MaskStd, 2)
The first argument of the Add* functions is a logger name. The name should be unique among all loggers. In the future the name is going to be served as an address for dynamic changing of parameters of loggers.
The second argument is a subsystem. It can be empty if the logger isn't attached to a subsystem.
The third and fourth arguments specify severities (as a mask) and maximal verbosity of messages, which the logger accepts and logs.
Now the framework is ready to log:
olog2.Info1("server started")
olog2.Critical1("No one has programmed this piece of SW!! I cannot work!")
olog2.Info1("server stopped")
There is a set of convenient functions following simple name scheme: SeverityVerbosity. The names can end with s or f - those function allow specification of a subsystem or the message can be formatted by standard printf format description:
olog2.Error2fs("special_module", "connection failed: %s", err)
Most of the time the convenient functions are good enough. However, sometimes there is a need to specify severity and verbosity dynamically. Then the functions LogMessage() and LogMessagef() can be useful.
At the end of the process the framework should be cleaned correctly flushing and closing opened files.
olog2.Destroy()
Warning: the initialization (Init and adding of loggers) phase and destrucion phase are not thread safe! Be careful that all threads have already stopped before you invoke the Destroy() function.