runtime.log

Logging configuration.

This module largely wraps the structlog framework to provide flexible structured logging for Runtime services. A chain of “processors” (callables) filters or transforms events produced by log statements.

To the greatest extent possible, this module favors native structlog functionality over integration with the standard logging module for performance reasons. structlog also provides an async interface that logging does not.

Note

structlog also has a notion of bound and unbound loggers. An unbound logger is a proxy that borrows its configuration from the global configuration set by runtime.log.configure(). Once a logger is bound by calling structlog.BoundLogggerBase.bind(), the global configuration is copied into the logger’s local state and frozen. Further changes in the global configuration no longer affect the bound logger.

Because unbound loggers are proxies that perform some introspection on every call, prefer bound loggers, which are much more performant because their methods are concrete.

runtime.log.AsyncLogger

alias of structlog.stdlib.AsyncBoundLogger

class runtime.log.LogPublisher(node, concurrency=1, logger=<factory>, requests=<factory>, send_queue_capacity=512)[source]

Bases: runtime.remote.Client

A client for publishing log events over a network.

A LogPublisher instance is a threadsafe structlog processor (callable). The processor feeds incoming events into an internal queue, which a worker task drains. For each event, the worker issues a notification call, where the method name is the log level and the only argument is the event dictionary.

The worker and queue are not initialized until the async context is entered. The worker is attached to the context’s running loop.

Parameters

send_queue_capacity (int) – The maximum size of the event queue. A nonpositive capacity indicates the queue size should be unbounded. When the queue is full, any additional events are dropped.

runtime.log.Logger

alias of structlog.stdlib.BoundLogger

runtime.log.configure(publisher=None, /, *, fmt='json', level='INFO')[source]

Configure structlog with the desired log format and filtering.

Parameters
  • publisher (Optional[runtime.log.LogPublisher]) – A publisher added into the processor chain just before rendering.

  • fmt (Literal['json', 'pretty']) – The format of events written to standard output.

  • level (str) – The minimum log level (inclusive) that should be processed. Severities are compared using runtime.log.get_level_num().

For development, we recommend the 'pretty' log format, which is human-readable and renders exception tracebacks but cannot be parsed:

2021-06-29T21:01:22.301992Z [info     ] Router connected to endpoint
2021-06-29T21:01:22.304771Z [info     ] Health check                   threads=4
Traceback (most recent call last):
  File "/usr/lib/python3.9/asyncio/tasks.py", line 492, in wait_for
    fut.result()
asyncio.exceptions.CancelledError

In production, we recommend the 'json' format, which produces events in jsonlines format (required entries shown):

{"event":"Started","level":"info","timestamp":"2021-06-29T21:04:15.507057Z"}
{"event":"Started","level":"info","timestamp":"2021-06-29T21:04:15.510914Z"}
runtime.log.get_level_num(level_name, /, *, default=10)[source]

Translate a logging level name into its numeric value.

Parameters
  • level_name (str) – A case-insensitive name, such as 'DEBUG'.

  • default (int) – The numeric level to return if the name is invalid.

Example

>>> get_level_num('INFO')
20
>>> assert get_level_num('DNE') == logging.DEBUG == 10
runtime.log.get_logger(*factory_args, **context)[source]

Get an unbound async-compatible logger.

Parameters
  • factory_args (Any) – Positional arguments passed to the logger factory.

  • context (Any) – Contextual variables added to every event produced by this logger.

runtime.log.get_null_logger()[source]

Get an async-compatible logger that drops all events unconditionally.

Useful for objects that emit unimportant or noisy log events. LogPublisher also uses a null logger internally, since using a real logger would result in a feedback loop.

runtime.log.LEVELS: list[str] = ['debug', 'info', 'warn', 'error', 'critical']

Log severity levels, in ascending order of severity.

These levels correspond to those used by the built-in logging library:

Level

Description

Example

debug

Frequent, low-level tracing.

A Smart Device message is received.

info

Normal operation (default level).

A Smart Device connects.

warn

Unusual or anomalous events.

Referencing a nonexistent device.

error

Failure mode.

A subprocess returns a nonzero exit code.

critical

Cannot continue running.

Emergency stop.