Pyoslog allows you to send messages to the macOS unified logging system using Python.
from pyoslog import os_log, OS_LOG_DEFAULT
os_log(OS_LOG_DEFAULT, 'Hello from Python!')
Pyoslog requires macOS 10.12 or later and Python 3.6 or later.
Install using pip
:
python -m pip install pyoslog
The module will install and import without error on earlier macOS versions, or on unsupported Operating Systems or incompatible Python versions.
Use pyoslog.is_supported()
if you need to support incompatible environments and want to know at runtime whether to use pyoslog.
Please note that if is_supported()
returns False
then none of the module's other methods or constants will exist.
import pyoslog
if pyoslog.is_supported():
pyoslog.log('This is an OS_LOG_TYPE_DEFAULT message via pyoslog')
Pyoslog provides the following methods from Apple's unified logging header:
os_log_create
os_log_type_enabled
(andinfo
/debug
variants)os_log_with_type
os_log
(andinfo
/debug
/error
/fault
variants).
All the pyoslog methods have the same signatures as their native versions, except for where a method requires a format
parameter.
The os_log
system requires a constant (static) format specifier, and it is not possible to achieve this via Python.
As a result, all instances of format strings use "%{public}s"
, and all messages are converted to a string before passing to the native methods.
Pyoslog also offers a helper method – log
– that by default posts a message of type OS_LOG_TYPE_DEFAULT
to OS_LOG_DEFAULT
.
For example, the shortcut log('message')
is equivalent to os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEFAULT, 'message')
.
The Handler
class is designed for use with Python's inbuilt logging module.
It works as a drop-in replacement for other Handler varieties.
See pyoslog's method documentation for a full reference.
Create a log object using os_log_create
and pass it to any of the log methods to add your own subsystem and category labels:
import pyoslog
log = pyoslog.os_log_create('ac.robinson.pyoslog', 'custom-category')
pyoslog.os_log_with_type(log, pyoslog.OS_LOG_TYPE_DEBUG, 'Message to log object', log, 'of type', pyoslog.OS_LOG_TYPE_DEBUG)
Log output can be enabled or disabled globally by switching between the desired log object and pyoslog.OS_LOG_DISABLED
:
import pyoslog
log = pyoslog.OS_LOG_DEFAULT
pyoslog.os_log(log, 'Log output enabled') # will appear in the unified log
log = pyoslog.OS_LOG_DISABLED
pyoslog.os_log(log, 'Log output disabled') # will not appear in the unified log
It is also possible to check whether individual log types are enabled for a particular log object:
import pyoslog
pyoslog.os_log_type_enabled(pyoslog.OS_LOG_DEFAULT, pyoslog.OS_LOG_TYPE_DEBUG)
It is not possible to directly set a log object's mode from Python, but see the config
section of man log
for documentation about doing this in sudo
mode.
Use the pyoslog Handler
to direct messages to pyoslog:
import logging, pyoslog
handler = pyoslog.Handler()
handler.setSubsystem('org.example.your-app', 'filter-category')
logger = logging.getLogger()
logger.addHandler(handler)
logger.error('message')
Logger levels are mapped internally to the OS_LOG_TYPE_*
values – for example, logger.debug('message')
will generate a message of type OS_LOG_TYPE_DEBUG
.
Logs can be viewed using Console.app or the log
command.
For example, messages sent using the default configuration can be streamed using:
log stream --predicate 'processImagePath CONTAINS [c] "python"'
Messages sent using custom log objects can be filtered more precisely. For example, to receive messages from the labelled subsystem used in the example above:
log stream --predicate 'subsystem == "ac.robinson.pyoslog"' --level debug
See the log
tool's manpages (man log
or online) for further details about the available options and filters.
When labelling subsystem and category using the native C methods there is a requirement to free the log object after use (using os_release
).
The pyoslog module handles this for you – there is no need to del
or release these objects.
As noted above, while the macOS os_log
API allows use of a format string with many methods, this parameter is required to be a C string literal.
As a result, pyoslog hardcodes all format strings to "%{public}s"
.
The pyoslog module's tests require the pyobjc OSLog framework wrappers and the storeWithScope initialiser in order to verify output so, as a result, can only be run on macOS 12 or later.
After installing the OSLog wrappers (via python -m pip install pyobjc-framework-OSLog
), navigate to the tests directory and run:
python -m unittest
All of pyoslog's code is covered by tests, but please note that if Console.app is live-streaming messages, some tests may fail.
See test_logging.py
for discussion about why this is the case.
At the time this module was created there were no alternatives available on PyPI.
Since then, the macos-oslog module has been released, with broadly equivalent functionality to pyoslog, except for the need to manually release the log object.
There is also os-signpost, which uses cython
to provide the OSSignposter
API, and could easily be extended to provide os_log
functionality.
In addition, there are other options available if PyPI access is not seen as a constraint:
Note that the pyobjc module OSLog is for reading from the unified logging system rather than writing to it (and as a result is used for testing pyoslog).
A log.h
binding is on that project's roadmap, but not yet implemented.