Logging plays a crucial role in troubleshooting and monitoring software systems, providing valuable insights into the behavior of your application at runtime. It allows you to track the flow of program execution, identify and address runtime errors, and analyze system performance.
One important aspect of logging is log levels, which categorize logged events based on severity. Besides organizing log entries, they also enable you to control the verbosity of log output and filter entries based on their significance.
In most production systems, the default minimum log level is set to INFO, meaning only records with a level higher than or equal to INFO will be logged. However, there are situations where you might need to adjust the log level to increase or decrease the verbosity of the logs.
Ideally, a well-designed application should allow you to modify the minimum log level without requiring a restart. After such change is made, it should also propagate seamlessly across all instances of the application. Therefore, this article will explore various methods to dynamically change log levels at runtime, eliminating the need for application restarts.
1. Creating an API endpoint to change the log level
One of the most straightforward ways to change the log level of your application without restarting is to create an API endpoint that accepts a payload specifying the desired log level. For security reasons, this endpoint should be protected and accessible only to authorized users.
Once a request is sent to the endpoint, you'll need to invoke a function that updates the log level setting of your chosen logging library or framework. If your application runs on multiple servers, you'll also need to propagate the change to all running instances. Depending on your application's architecture, this may involve implementing a mechanism for inter-process communication or using a messaging system, but such a set up is outside the scope of this article.
Here's an example that implements the above steps in a Django application that uses the Loguru framework for logging. The exact method of setting up the endpoint and changing the log level will differ in other languages or frameworks, but the idea remains the same. Please refer to the relevant documentation to learn more.
First, you need to define a URL pattern in your project's urls.py file to map
the API endpoint to a view function:
Next, you need to determine the format of the JSON payload that will be sent to
the endpoint. In this example, we'll use a JSON payload with the log_level
field:
Go ahead and implement the view function in the app's views.py file:
At this point, you must extract the log_level field from the request payload,
validate it, and then change the log level on the logger:
Once you send a request to this endpoint, you should observe that the change
takes effect in your application. For example, assuming the default level is
INFO, you can temporarily switch to the DEBUG level by sending the following
request:
You should observe the following response, confirming that the log level was updated successfully:
A basic way to propagate this change to all application instances is to create a separate program that iterates through the IP address of each server and sends the log level change request to each one:
Don't forget to set up authentication for the route so that only authorized personnel can make such requests to your server.
2. Sending UNIX signals to your program
Signals are a standardized way
to communicate and control processes on UNIX-like operating systems. You can
listen for a specific signal in your application and then take the appropriate
action. For example, you can listen for SIGTERM (the signal for program
termination), and perform a graceful shutdown before exiting the program.
The first step is to identify what signals to listen for. On Linux, there are
two specially designated signals that are not associated with any predefined
behavior. As a result, applications often use them to define custom actions.
These are SIGUSR1 and SIGUSR2. For example, you can use the first signal to
increase the log level, and the second to decrease it. This works best when
your logging framework associates the log level with an
integer.
Here's a Node.js example (using Pino) that listens for both signals and increments or decrements the log level as needed:
Here, the updateLogLevel() function is invoked when the SIGUSR1 and
SIGUSR2 signals are received. The log level is subsequently incremented or
decremented as appropriate. A message is printed to the screen once the bounds
are exceeded in either direction.
Here's how to send either signal to a running application on Linux:
3. Utilizing framework-specific configuration files
Some logging frameworks attempt to make changing log levels
at runtime easier by automatically scanning a configuration file for changes.
For example, Logback, a popular logging framework for
Java applications, allows you to
reload its configurations upon file modification
when the scan attribute of the true.
By default, the file will be scanned for changes once every minute, but you can also specify a custom scanning period:
As an example, here is a simple Java program that logs some messages every 10 seconds using the Logback framework with SLF4J:
Here's the corresponding Logback configuration for the above program. Notice
that its minimum log level is set to INFO, and the scan period is set to 10
seconds:
When you run the program, you will observe that only INFO, WARN, and ERROR
logs are printed:
Keep the application running, and edit the configuration file. Change the log
level into DEBUG as shown below:
Wait for about 10 seconds after saving the file. You should observe that
DEBUG-level messages are now being included in the output without having to
restart the program:
If you're using Log4J2, you can use the
monitorInterval attribute instead to achieve the same results:
Likewise, if your logging framework of choice supports configuration files, do investigate further to see if it provides a way to watch the file for changes and reload its configuration.
Final thoughts
In this article, we explored various approaches to dynamically change log levels in production without restarting your application. We discussed creating APIs, sending signals, and modifying configuration files. Each of these methods provide varying levels of flexibility and can be adapted to different programming languages, so you should definitely find a solution that meets your requirements.
To learn more about log levels, we recommend reading our comprehensive tutorial on log levels and their usage or logging in microservices. It will provide you with a deeper understanding of log levels and their significance in logging. You can also check out our other logging guides here.
Thanks for reading, and happy logging!