Loggers

Logger is the main interface to the library. It has the responsibility of taking user messages and interpolating them, and other context values, with a log entry template to create a message that is then passed to the attached handlers.

Default Logger

The default logger will lazily create a handler that writes to STDOUT with a minimum verbosity of INFO. Its output template is a simple {timestamp} {level} {name}: {message}. For example:

>>> from logging2 import Logger
>>> logger = Logger('app')
>>> logger.info('Hello, world!')
2017-04-29T17:08:23.156795+00:00 INFO app: Hello, world!

Basic Logger Usage

Of course that’s not all to using the logger interface. There are numerous ways of configuring a logger. You can set verbosity level, add one or more handlers (thus disabling the default STDOUT handler), set the timestamp’s timezone (timestamps are ISO 8601 formatted and default to UTC) and provide a custom template.

Setting Verbosity for the Default Handler

If you only need the default handler but want to change its verbosity, all you need to do is set the level parameter of the logger on instantiation:

>>> from logging2 import Logger, LegLevel
>>> logger = Logger('app', level=LogLevel.error)
>>> logger.info('Hello, world!')  # nothing
>>> logger.error('Hello, world!')
2017-04-29T17:08:23.156795+00:00 ERROR app: Hello, world!

Adding Handlers

Handlers can be set up a couple different ways:

  • there’s a default StdOutHandler lazily created when a logging method is called if no handlers are present
  • there’s a handler parameter to set a different default handler for the logger
  • there’s a handlers (note the s) to pass a list of handlers to the logger
  • you can use the add_handler method of the logger as well at any time

Using the handler kwarg on init:

>>> from logging2 import Logger, FileHandler
>>> fh_handler = FileHandler(file_path='/var/log/app.log')
>>> logger = Logger('app', handler=fh_handler)

Using the handlers kwarg on init:

>>> from logging2 import Logger, FileHandler, StdOutHandler, LogLevel
>>> fh_handler = FileHandler(file_path='/var/log/app.log')
>>> stdout = StdOutHandler(level=LogLevel.debug)
>>> logger = Logger('app', handlers=[fh_handler, stdout])

Advanced Logger Usage

Beyond all of this there’s some advanced work you can do with loggers as well. You can inject context into the message at any time: either during initialization or at log time.

Using the ``additional_context`` Kwarg

There’s another keyword available for the logger’s init - additional_context. It take a dictionary of values to inject into the context of the log entry that will override any system default. You can also use it to provide context values for ony non-default template keys (such as injecting request ids into the log entry).

Let’s say we have a machine id that we want logged. The id is pulled from an environment variable that uniquely identifies the server (pretending we have a distributed application and are using some sort of networked log collector):

>>> import os
>>> from logging import Logger
>>> from logging.handlers import UdpHandler

>>> machine_id = os.environ.get('MACHINE_ID')
>>> template = '{timestamp} {machine} {level}: {message}'
>>> logger = Logger(name='app', template=template, additional_context={'machine': machine_id})
>>> handler = UdpHandler(host='10.2.1.99', port=5000)
>>> logger.add_handler(handler)

Now when we send logs, they will include this machine’s identifier.

But we don’t always have static information that we want to inject - sometimes we need to be able to get information on the fly such as a request id. Easy enough, the value parts of the dictionary can also be functions (the system will check if the value is static or callable). Let’s pretend we have a flask app with a request_id being set on the global g object:

>>> def get_request_id():
...      return g.request_id
...
>>> template = '{timestamp} {req_id} {level}: {message}'
>>> logger = Logger(name='app', template=template, additional_context={'req_id': get_request_id})

Overloading the Logging Methods

There’s one last way to provide any final overload to a template key - providing the kwarg directly in the logging calls (debug, info, etc.):

>>> logger.info('Hello, world!', timestamp='whenever')
whenever INFO app: Hello, world!

API