Workshop o mikrokontrolérech na SKSP 2024.
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.

155 lines
7.0 KiB

3 months ago
Logging
=======
LibUCW contains a powerful system for logging of messages. Depending on your
needs, it can be used either as a very simple logger which writes all messages
to stderr or to a single file, or as a multi-stream logger in which different
messages can be directed to different streams and the streams can be combined
in various ways.
Simple logging
--------------
The basic logging functions are defined in <<basics:logging,lib.h>>.
To log a message, call `msg(L_xxx,@fmt,@args)`, where `L_xxx` is a category of the log
message (`L_INFO`, `L_WARN`, `L_ERR` etc.), @fmt is a format string as for printf,
and @args are additional arguments to be substituted to the format string.
A newline character is automatically appended; the message should not contain
any control characters.
The first argument of `msg` can be OR'ed with additional flags. Most notably, you can
add `L_SIGHANDLER` if you wish to log a message from a signal handler (see below
for discussion on signals and reentrancy in general).
By default, all messages are logged to stderr. If you wish to use a log file,
call `log_file(@name)`. All subsequent logging will use this file and stderr
will be redirected there, too.
Names of log files can contain strftime() escapes, which are expanded on the fly.
This makes it easy to start a new log file every day.
Example
~~~~~~~
#include <ucw/lib.h>
int main(int argc, char **argv)
{
log_init(argv[0]);
log_file("/var/log/utterances");
msg(L_INFO, "This program does nothing, but successfully.");
return 0;
}
Log streams
-----------
More generally, the logger can use multiple log streams. Each stream can be directed
to a logging back-end (log file, syslog, ...) and equipped with a filter which
selects a subset of the messages received. A stream can also have substreams
attached, which are passed a copy of all log messages sent to the parent stream.
Streams are identified by <<struct_log_stream,struct log_stream>> and also by
their registration number. Messages can be directed to a stream by OR'ing the
registration number to the first argument of msg().
When a log stream receives a message, it is processed as follows:
1. If the log level of the message does not match the set of accepted
levels of the stream (@levels), the message is dropped.
2. The filter hook of the stream is consulted and if it returns a non-zero
value, the message is dropped.
3. The message is passed to all substreams of the stream.
4. The message is formatted according to the formatting flags (@msgfmt) of the stream.
5. The handler hook of the stream is called (if it exists).
When no stream is explicitly selected, msg() uses the default stream, which
has registration number 0 and which is also returned by log_default_stream().
This stream has no explicit destination, but it can have substreams. (When
a program starts, the default stream is connected to stderr. A call to log_file()
establishes a file logging stream and links it as the only substream of the
default stream. If you want to do that with any other log stream, call log_set_default_stream().)
Streams are reference-counted. When a stream is created, it gets reference count 1.
When it is linked as a substream of another stream, its reference count is incremented.
Closing the stream by log_close_stream(), unlinking it or closing a parent stream
(which causes an unlink) decrements the reference count and when it drops to zero,
the stream is removed and all its substreams unlinked.
Example
~~~~~~~
#include <ucw/lib.h>
#include <ucw/log.h>
int main(int argc, char **argv)
{
log_init(argv[0]);
struct log_stream *ls = log_new_file("/var/log/utterances", 0);
msg(L_INFO | ls->regnum, "Aye captain, we have a log file");
msg(L_INFO, "Alas, stderr still works");
return 0;
}
Message types
-------------
Messages can also have types, which can be used for further filtering inside streams.
By default, there is only the default message type. To obtain an identifier of a new
type (again to be OR'ed to the log level when calling <<msg()>>), use <<log_register_type()>>.
The number of types is currently limited to 32.
If you want non-default types to be visible, enable the `LSFMT_TYPE` format flag.
Processes, threads and signals
------------------------------
When you fork a new process, it automatically inherits all currently configured log
streams. You should however call <<log_fork()>> to update the logger's notion
of the current PID (at least when you use PID's in your log messages). Also, if you
plan to exec() a process after fork(), do not forget to call <<log_close_all()>>,
so that all file descriptors used for log files (except for stderr) are closed.
The <<basics:msg()>> function itself can be called from multiple threads in parallel
and it is atomic by design. The functions for setting up the logging machinery
are however not reentrant (they follow our general rule about functions that
affect global state).
Logging from signal handlers is problematic, as is doing almost anything in signal
handlers, because almost all libc functions are not signal-safe. Most importantly,
functions for converting time to a human-readable representation aren't safe.
LibUCW therefore offers only limited logging services in such situations and
you must use the `L_SIGHANDLER` flag to request it. Otherwise, deadlocks get
ready to happen.
Messages logged with `L_SIGHANDLER` set are written directly to stderr (which
is usually an alias for the main log file, at least if you use <<log_file()>>)
and they do not carry a timestamp. Logging of sighandler messages to general
log streams or to syslog is therefore not supported.
ucw/log.h
---------
!!ucw/log.h
Limiting rate: ucw/tbf.h
------------------------
LibUCW also offers simple means of limiting the rate of log messages (or of any other
events) by means of a so called 'Token Bucket Filter.' The idea behind this filter is
simple: To log a message, we need a token. The available tokens are accumulated in
a bucket which has a fixed 'filling rate' (the number of tokens arriving in the bucket
per second, which may be a fractional number) and fixed 'maximum capacity.' The
bucket receives the tokens continuously with the given rate and when it reaches
the maximum capacity, the extra tokens are dropped on the floor. When a message
has to be sent, we take a single token from the bucket and if there wasn't any,
we drop the message.
The filling rate therefore describes the maximum sustained rate of messages,
while the bucket capacity tells the filter the maximum length of a short burst,
which can temporarily exceed the rate.
A general bucket filter is available in `ucw/tbf.h`. The usual way of using it
to limit logging is to set up a filter hook of a stream which asks the TBF for
every message. (Remember, though, that if your program is multithreaded, the
filter hook can be run in multiple threads in parallel, so it has to guard the
TBF by a lock.) The configuration interface for log streams described above
is able to attach rate limiters to streams per user's request, so you usually
need not take any extra care.
!!ucw/tbf.h