log
import log
This module implements a simple and flexible event logging system for all Zuri applications and modules. With support for multiple transport systems as well as custom transports, this module allows easy application logging and log shipping.
The module selects defaults that is familiar for most end use-cases in order to allow for a minimal need for configurations so that you can start logging right out of the box.
Below is a very simple but powerful and complete usage of this module:
%> import log
%> log.info('Starting my application...')
'2025-03-07T08:00:33+01:00 INFO [.]: Starting my application...'
IMPORTANT!
Did you notice that
[.]? That’s because we are running in a REPL. By default, thelogmodule provides information regarding the source of the log i.e. the application from which the log came from thereby allowing multiple applications log into the same transport pool without ambiguity.You can customize this name by setting the name on the transport via [[log.Transport.set_name]].
This module provides all functionalities at the module level, allowing configurations to be carried across all files and modules in the lifetime of an application.
While allowing creation of custom transports for logs, the module provides transports for logging to the console ([[log.ConsoleTransport]]) and files ([[log.FileTransport]]) out of the box. This covers the most simple use-cases for most applications.
The default transport enabled is the [[log.ConsoleTransport]] known as the [[log.default_transport()]] and need no extra work to enable unless you have previously disabled it. The example below shows how to enable the file transport to log to a file on disk.
import log
var transport = log.FileTransport('mylog.log')
log.add_transport(transport)
log.info('Finished setting up file log...')
If you check the file mylog.log now, you should see something like
this:
2025-03-01T12:00:00+01:00 INFO [tmp]: Finished setting up file log...
In addition to the log appearing on the console, you can now see the log in a persistent file. There are many ways to turn off the console output and log to file alone.
Firstly, you can simple disable the default transport.
log.default_transport().disable()
The advantage to this approach is that while it disables the default transport, the transport is still registered and you can simple enable it at any time during the lifetime of the application by doing the reverse:
log.default_transport().enable()
The same strategy applies to all transports as the enable() and
disable() method will be inherited from the [[log.Transport]] class.
The second approach is to completely remove the transport from the list of registered transports.
log.remove_transport(log.default_transport())
With this second approach, you’ll need to register the transport again should you want to continue logging to the console. The same applies to all transport types.
For more complex uses, the process of creating a custom transport is
really simple. To create a custom transport, you’ll need to create a
class that inherits from [[log.Transport]] and implement the write()
method at a minimum.
The example below shows the creation of a custom transport that outputs structured JSON data to the console.
# my_custom_transport.zu
import log { Transport }
import json
import enum
class JsonConsoleTransport < Transport {
format(records, level, context) {
return {
records,
level,
}
}
write(message, level) {
echo json.encode({
logtime: time(),
records: message.records,
name: self.get_name(),
level: enum.to_value_dict(log.LogLevel)[message.level]
})
}
}
import log
import .my_custom_transport { JsonConsoleTransport }
log.add_transport(JsonConsoleTransport())
log.info('Finished setting up json console log transport...')
You should be seeing something similar to the below if you run the code:
{"logtime":1741376459,"records":["Finished setting up json console log transport..."],"name":"tmp","level":"Info"}
You can set multiple transports at the same time as well as set them to only work at different log levels. Every transport inherits the method [[log.Transport.set_level]] which allows us set the minimum level at which a transport is available.
There is also a global [[log.set_level]] function that allows us to set the minimum log level at which all transports can start logging.
import log
log.set_level(log.Warning)
log.info('Finished setting up json console log transport...')
If you try the above code, you won’t be seeing anything in the console. This is because level [[log.Info]] is lower than the minium required [[log.Warning]].
While every Transport implements the format() method, users can
override the format method by providing a format function via the
[[log.Transport.set_formatter]] method. This allows using the same
formatter across different transports.
For example, in the previous example, if we had wanted to serialize all
logging irrespective of the transport into the JSON format, a more
appropriate solution would have been to create a log format function and
reuse it in all transports instead of implementing a
JsonConsoleTransport. The next example shows one such implementation.
import log
import json
import date
def json_format(records, level, transport) {
return json.encode({
time: date().format(transport.get_time_format()),
level: log.get_level_name(level),
name: transport.get_name(),
records,
})
}
var file_transport = log.FileTransport('mylog.log')
log.add_transport(file_transport)
log.default_transport().set_formatter(json_format)
file_transport.set_formatter(json_format)
log.debug('This is a debug information')
Transport implementations should take note of this critical information.
IMPORTANT!
Because the
logmodule exports the a function, if you are not interested in all the shenanigans of logging level and simply want to do some quick logging, you can ignore the whole logging levels altogether and log at level [[log.None]] by simply calling the log module itself.import log log('An anonymous log!')This bypasses every level filter (including the global [[log.set_level]] threshold) on every enabled transport; only [[log.Transport.disable]] can still silence it.
Structured and namespaced logging
A trailing dict argument on any log call is treated as structured fields
rather than a plain message, rendered as key=value pairs after the
message:
log.info('user signed in', { user_id: 42 })
# ... INFO [.]: user signed in user_id=42
For a whole subsystem that should tag every one of its own log lines with a name (and, optionally, a fixed set of fields), use [[log.get_logger]] instead of the flat module-level functions:
var db_log = log.get_logger('db').bind({ pool: 'primary' })
db_log.warn('slow query', { duration_ms: 850 })
# ... WARNING [db]: slow query pool=primary duration_ms=850
The log API
Every public name in log, wherever it is declared. Each links to the
page that documents it.
| Name | Kind | Summary |
|---|---|---|
log.ConsoleTransport | class | ConsoleTransport is a log transport that facilitates sending log streams to the console. |
log.Critical | constant | Module level declaration of LogLevel.Critical |
log.Debug | constant | Module level declaration of LogLevel.Debug |
log.Error | constant | Module level declaration of LogLevel.Error |
log.FileTransport | class | FileTransport is a log transport that facilitates sending log streams to an on-disk file. |
log.Info | constant | Module level declaration of LogLevel.Info |
log.LogLevel | constant | The Log levels in order |
log.Logger | class | A namespaced logger that tags every record it writes with a name and, optionally, a set of bound structured… |
log.None | constant | Module level declaration of LogLevel.None |
log.Transport | class | The Transport class acts as the base class for log transports and handle the actual logging of the specified… |
log.Warning | constant | Module level declaration of LogLevel.Warning |
log.add_transport | function | Adds a new transport service to the list of registered transports. |
log.critical | function | Logs a message with level [[log.Critical]] on all registered transports. |
log.debug | function | Logs a message with level [[log.Debug]] on all registered transports. |
log.default_transport | function | Returns the instance [[log.ConsoleTransport]] which is used as the default transport by the module. |
log.error | function | Logs a message with level [[log.Error]] on all registered transports. |
log.exception | function | Logs an exception with level [[log.Error]] on all registered transports, including its message and stacktrace. |
log.get_level_name | function | Returns the name of a log level as a string. |
log.get_logger | function | Returns a [[log.Logger]] namespaced under the given name. |
log.info | function | Logs a message with level [[log.Info]] on all registered transports. |
log.log | function | Logs a message with level [[log.None]] on all registered transports. |
log.remove_transport | function | Removes the given transport service from the list of registered transports. |
log.set_level | function | Sets the threshold level for the default transport to handle. |
log.set_name | function | Sets the name of the default transport. |
log.warn | function | Logs a message with level [[log.Warning]] on all registered transports. |
Submodules
| Module | Reached as | Summary |
|---|---|---|
log.console | log.* | ConsoleTransport: the default transport, printing to stdout (or stderr for Error/Critical) with optional… |
log.dispatch | log.* | The default transport instance and the module-level functions that configure it. |
log.file | log.* | FileTransport: sends log streams to a file on disk, with optional size-based rotation. |
log.level | log.* | The LogLevel enum, its module-level constant exports, and the single shared “default level” that every… |
log.logger | log.* | The flat, module-level logging functions (log(), info(), debug(), warn(), error(), critical(),… |
log.transport | log.* | The Transport base class every log sink (console, file, or a custom subclass) is built on, plus the… |
2025, Richard Ore and The Zuri Contributors