Logging infrastructure#

SimGear provides a centralised logging infrastrucutre with runtime configuration of logging levels according to categories. Multiple log consumers can be installed, each with their own logging settings.

Log consumers (simgear::LogCallback) run in a dedicated thread, so that their IO doesn’t block threads creating log entries. This allows for safe disk or network IO in a log consumer. Messages are passed from arbitrary threads via a thread-safe queue, so the logging consumers see atomic, time-stamped entries. All consumers process an entry, before the next entry is processed.

Various console consumers are defined, linked to standard output. The exact behaviour of these is platform-dependent: the POSIX platforms (incuding macOS) simply output to stderr, while on Windows output to the debug console (OutputDebugStringA) is supported by setting the environment variable SG_WINDEBUG to something true-ish, and otherwise, the Windows terminal console is used.

By default FlightGear also logs to a file in FG_HOME, called fgfs.log, which is rotated on each startup. This file logs by default at level INFO, to balance size of the files, with usefulness of post-hoc investigation of problems.

Runtime Configuration#

The log-level command allows changing logging settings dynmically. The follows arguments are supported:

tag

indicates which callback(s) to modify. Default value is console

all

priority value for the SG_ALL category

<name>

priority value for the category <name>. This can be repeated for multiple categories.

Additional file logs can be created using the log-to-file command.

The property /sim/log-file-line configures if log entries include the file and source line of the log entry or not.

Implementation Details#

To avoid locking overhead on the common path, the log consumer thread is stopped and restarted when configuration changes occur. This is effective since configuration changes are very rare, and the logging data is otherwise read-only and hence thread-safe.

The logging frontend is the SG_LOG macro, which calls the logstream::would_log() predicate, and if this passes, the logstream::log() method. logstream::would_log() allows fast rejection of non-logged entries before the expensive formatting incurred by std::ostringstream takes place.

Internally, logstream::would_log() uses the lowest (most permissive) log level of all the registered callbacks. This means that if no callback is interested in say SG_DEBUG messages, they will be skipped before the work of building the message occurs.

The current filtering state of a log callback is tracked via the simgear::LogLevels class, which has a threshold priority for each defined category. Categories with no explicit priority use the SG_ALL priority automatically.

Startup Logging#

During startup, log messages are buffered in the logging system, so that the full log contents are available when additional log consumers are registered. Once the main loop reaches initialization stage 1000, startup logging is disabled via a call to logstream::setStartupLoggingEnabled(), and the buffer is cleared.

Buffered log callbacks#

Various places created a simgear::BufferedLogCallback to collect messages of a certain category, and show them in a UI. This is used for the TerraSync log (collects TerraSync and scenery messages), and for the Nasal console. The callback handles thread-safety, and the standard filtering mechanism means only the requested subset of messages is collected.

Log Delta#

Todo

Document and discuss the log delta system.