Technical Resources
Educational Resources
APM Integrated Experience
Connect with Us
Logging is one of your most important means for monitoring what’s going on inside your code. Everything, even the most trivial script, should communicate what’s happening inside. Fortunately, there’s an easy way to make those scripts write to your system logs: Python Syslog. With Python’s native syslog support, you can add system logging to your scripts with just a few lines of code, replace printing to the console, and redirecting to files with powerful tooling that works with syslog, rsyslog, syslog-ng, and of course, SolarWinds® Loggly®.
Let’s get started with Python Syslog.
Make sure you’re on a system with one of the flavors of syslog daemon up and running and Python 3.x installed.
Start iPython and enter the following lines.
$ ipython
Python 3.6.9 (default, Jul 17 2020, 12:50:27)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.6.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import logging
In [2]: import logging.handlers
In [3]: from logging.handlers import SysLogHandler
In [4]: logger = logging.getLogger('mylogger')
In [5]: logger.setLevel(logging.DEBUG)
In [6]: handler = logging.handlers.SysLogHandler(facility=SysLogHandler.LOG_DAEMON, address='/dev/log')
In [7]: logger.addHandler(handler)
In [8]: logger.debug('Foo!')
Now, check the file /var/log/debug. The last line (or at least a line near the bottom) should look like this:
Sep 7 14:41:50 hala ipython[11357]: Foo!
If your system lacks a debug log in /var/log, find out where your syslog daemon is sending debug logs.
What happened in this example? Let’s do a quick walkthrough before going over some essential concepts.
First, you imported three Python libraries.
In [1]: import logging
In [2]: import logging.handlers
In [3]: from logging.handlers import SysLogHandler
Python 3.x has native support for syslog, so you don’t need to install additional libraries. But you need to import the Python logging library into your code, which is on line #1. Then, you need Python logging handlers on line #2. Finally, you imported Python SysLogHandler on line #3. This isn’t required, but it will make line #6 more readable.
Next, you created a logger and set it up for debug level.
In [4]: logger = logging.getLogger('mylogger')
In [5]: logger.setLevel(logging.DEBUG)
We’ll take a closer look at this below, but like most Python code, it’s pretty self-explanatory.
Then you created a handler. This is the important part because the handler connects your code to syslog.
In [6]: handler = SysLogHandler(facility=SysLogHandler.LOG_DAEMON, address='/dev/log')
In [7]: logger.addHandler(handler)
Since you imported the SysLogHandler name, you can refer to it without a fully qualified reference. You can also set the facility the same way in the first argument. (We’ll discuss logging facility below.) The last argument tells the handler where to send syslog messages: /dev/log works on almost all Linux systems. Then, you added the handler to the logger.
Finally, you sent a message at debug level.
In [8]: logger.debug('Foo!')
That’s it. Let’s go over some basic concepts before we put this into a script.
A logger is the class you use to publish log messages. It can be used to log to many different types of logging systems, including the console, files, and logging infrastructure like syslog, syslog-ng, and Loggly. In many cases, including with Python syslog, you create the logger and then provide it a handler.
Loggers have a logging level or priority that’s either explicitly specified by the application or defaults to WARNING. (Python loggers have a notion of a logging hierarchy controlling the default priority. For this tutorial, thinking of the default as WARNING is good enough.)
If you pass the logger a message lower than the default level, it won’t be logged. So, set the default to DEBUG above to ensure your message would be logged.
Python supports these logging levels, from the lowest level to the highest.
– DEBUG: detailed and often verbose information, typically for debugging problems
– INFO: expected messages, usually confirming things are working as expected
– WARNING: indicates something unexpected happened but the app should be able to continue working
– ERROR: more serious issues and will interfere with the application’s operation
– CRITICAL: serious errors and implies the program itself may be unable to continue
The handler routes log messages to their final destination. We’re using the SysLogHandler for this tutorial, but Python offers a wide variety of handlers for sending messages. That’s how it’s able send them to the terminal, files, and other log management systems.
We’ll discuss formatters in more depth below, but basically, the formatter does exactly what it says on the tin: it formats messages. You enrich your log messages with additional information about your code and its current state when the message is generated.
Facility is a syslog concept. It indicates which program sent the log message. The syslog server (or daemon) uses the facility to decide how to process the message. You’re using SysLogHandler.LOG_DAEMON, which is for system daemon programs. (I selected it for the tutorial because it was one of the fastest ways to log a message without modifying syslog message routing rules.)
The syslog standards define several different logging facilities. Python maps each one to an enumeration in the SysLogHandler class. Here is a partial list.
– auth (LOG_AUTH): for security/authorization events
– authpriv ( LOG_AUTHPRIV): for security/authorization events, usually related to privilege escalation
– cron (LOG_CRON): cron events
– daemon (LOG_DAEMON): system daemon events
– ftp (LOG_FTP): ftp service events
– kern (LOG_KERN): UNIX/Linux kernel messages
– syslog (LOG_SYSLOG): syslog internal messages
– user (LOG_USER): user messages
– local0–local7 (LOG_LOCAL[0-7]): eight facilities (local0 to local7) reserved for user-specified applications
As you can see, the facilities cover many broad and often overlapping categories of messages.
Finally, SysLogHandler needs an address for your syslog daemon. For most situations, the address is /dev/log, the path to a special file descriptor the daemon reads for messages. But you can paste in the IP address and port of a remote server and send your log messages to a different host if you wish, too. SysLogHandler implements the complete syslog protocol, including messages for UDP or TCP.
Let’s put these concepts together into a script to better use SysLogHandler.
Put this code in a file named syslog.py.
import logging
import logging.handlers
from logging.handlers import SysLogHandler
from logging import Formatter
def main():
logger = logging.getLogger('BetterLogger')
logger.setLevel(logging.DEBUG)
handler = SysLogHandler(facility=SysLogHandler.LOG_DAEMON, address='/dev/log')
logger.addHandler(handler)
log_format = '[%(levelname)s] %(filename)s:%(funcName)s:%(lineno)d \"%(message)s\"'
handler.setFormatter(Formatter(fmt=log_format))
logger.debug('Foo!')
if __name__ == '__main__':
main()
This script starts out like the code you ran in iPython earlier. It imports the logging library, logging handlers, and SysLogHandler, but it also imports the formatter. Then, inside a main() function you created a logger, set its default level to DEBUG, and then added the same SysLogHandler you added in the earlier code. Next, you created a formatter and add it to the logger, too.
Let’s run this and see what your log message looks like.
$ python3.6 syslog.py
The log message should be similar to this:
Sep 8 16:45:32 hala python3.6[7192]: [DEBUG] syslog.py:main:18 "Foo!"
If you compare this message to the formatting string, you can see how the formatter works.
Here’s the string:
'[%(levelname)s] %(filename)s:%(funcName)s:%(lineno)d \"%(message)s\"'
And here’s the message:
[DEBUG] syslog.py:main:18 "Foo!"
Formatter strings are assembled using log records. A record is surrounded with a percent sign and parentheses, and then a single character for datatype. ‘S’ indicates a string and ‘d’ indicates an integer or decimal. Here’s an example:
%(levelname)s == 'DEBUG'
At the start of the message you see the message level inside square brackets. So, [%(levelname)s] was replaced with [DEBUG], the string representation of a log message level.
In the rest of the message you can see filename, funcName, and lineNo told you exactly where the log message was sent from.
Logging to syslog from Python is a simple and straightforward process. Python native support means you can log directly to your Unix/Linux infrastructure with just a few lines of code. Add logging to your scripts and tweak your priority settings and formatting strings, so you can use the log to isolate problems or simply verify normal operations.
And of course, if you log to syslog, your scripts can log to SolarWinds Loggly. Sign up for a free trial here.
This post was written by Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!).