Skip to content
benliao1 edited this page Jul 30, 2020 · 25 revisions

This page will attempt to explain the motivation behind the logger, the expected behavior of the logger, and how it works.

Motivation

There are several reasons why having a logger is important. In general, in any software project, it's a good idea to have some sort of record of the events that happened, which is why logs are useful. Second, not all print statements are created equal. Some print statements are only useful for finding bugs, others are extremely important and notify the user that something terrible happened and the system is about to crash. You can imagine that, when a piece of software is deployed, debugging logs shouldn't be made visible to the user, but fatal crash logs should be made visible to the user! An organized way of emitting these log statements can help do that. Lastly, and perhaps more importantly for us, when Runtime is running, there is literally no way for us to tell what's going on if we don't have a logger and are just connected to Runtime through Dawn. We don't have a screen attached to the Raspberry Pi unless we ssh into the computer, so we can't see logs. Additionally, in production, Runtime isn't attached to a terminal while running (it's a systemd service running in the background, basically), so even if we do printf in our code, it wouldn't appear on any screen even if we ssh into it. Allowing the logger to emit logs over the network to Dawn for us view in the Dawn console is an absolutely critical feature for us to debug student robots efficiently.

Expected Behavior

The logger is a tool that allows any process within Runtime to submit log messages which are then emitted on standard out (the terminal), to a file, over the network to Dawn, or some combination of the three. The logger supports five different log levels, which are, in order of least critical to most critical: DEBUG, INFO, WARN, ERROR, and FATAL. Information about how to choose which of these levels to use for a given log message can be found in the style guide docs/style_guide.c. The logger needs to be able to let only logs submitted at or above the level specified for the given output locations to be emitted to that output location. Finally, changing the settings for the logger should be fast. You shouldn't have to recompile all of Runtime just to get the logger to run with different output settings. So, there is a configuration file that is parsed by the logger when a process starts that configures the settings dynamically. This means that all you need to do in order to change the output settings is to edit logger.config, save it, and restart Runtime.

For an explanation of the different settings in logger.config, see the comments in that file itself.

Relevant Files

logger.h: the (very simple) interface presented to the other Runtime processes for using the logger logger.c: the implementation of the logger interface logger.config: the configuration file for configuring the output settings in the

Detailed Explanation

Reading the Config File

All of the logic described below is contained in the function read_config_file() in logger.c.

The first step is we need to find the logger.config file in the file system. The absolute path of the logger.config file is different for each system (for example, on my computer, the path is /Users/ben/Desktop/My\ desktop\ files/Berkeley/Misc/c-runtime/logger/logger.config but on the Raspberry Pi it's /home/pi/c-runtime/logger/logger.config), which makes it annoying to have to change the path each time you test Runtime on a different system. So instead, we construct the relative path of the config file, which we know will be in the same directory as logger.c (the file that is trying to find the config file). We use the special macro __FILE__ to obtain the absolute path of logger.c, which, on the Raspberry Pi, is /home/pi/c-runtime/logger/logger.c. Then, we find the location of the last / in that path and append logger.config to the string starting at that location, to get the path of the configuration file.

The rest of the function first opens the configuration file, then reads its contents line by line, skipping comment lines and blank lines, until it has parsed all seven of the configuration settings and finished setting the appropriate global variables. It sets the variable OUTPUTS, which is a bitmask representing which of the three output locations logs will be emitted. It sets the three output levels stdout_level, file_level, and `network_level, which set the minimum levels of logs that will be emitted to standard out, a log file, or the network, respectively.

Creating the Log Message Header

The Log FIFO