Uvlog

Quickstart


pypi docs codecov tests Checked with mypy Code style: black

uvlog is yet another Python logging library built with an idea of a simple logger what ‘just works’ without need for extension and customization.

  • Single package, no dependencies

  • JSON and contextvars out of the box

  • Less abstraction, better performance

  • Pythonic method names and classes

Installation

With pip and python 3.8+:

pip3 install uvlog

Use

Our main scenario is logging our containerized server applications, i.e. writing all the logs to the stderr of the container, where they are gathered and sent to the log storage by another service. However, you can use this library for any application as long as it doesn’t require complicated things like filters, adapters etc.

from uvlog import get_logger

logger = get_logger('app')

logger.info('Hello, {name} {surname}!', name='John', surname='Dowe')

Note that you can use extras directly as variable keys in log calls (variable positional args are stored in a log record but not supported by the formatter).

To write an exception use exc_info as in the standard logger.

try:
    ...
except ValueError as exc:
    logger.error('Something bad happened', exc_info=exc)

Log configuration is similar to dictConfig. It updates the default configuration.

from uvlog import configure

logger = configure({
    'loggers': {
        '': {'level': 'DEBUG', 'handlers': ['./log.txt']}
    },
    'handlers': {
        './log.txt': {'formatter': 'json'}
    }
})

You can use context variables to maintain log context between log records. This can be useful for log aggregation. See the documentation on contextvars for more info.

from uvlog import LOG_CONTEXT, get_logger

app_logger = get_logger('app')

async def handler_request(request):
    LOG_CONTEXT.set({'request_id': request.headers['Request-Id']})
    await call_system_api()

async def call_system_api():
    # this record will have 'request_id' in its context
    app_logger.info('Making a system call')

When using the JSONFormatter you should consider providing a better json serializer for better performance (such as orjson).

import orjson
from uvlog import JSONFormatter

JSONFormatter.serializer = orjson.dumps

Never say never

The library adds support for additional log level - NEVER. The idea behind this is to use such logs in places of code which should never be executed in production and monitor such cases. ‘NEVER’ logs have the maximum priority. They cannot be suppressed by any logger and are always handled.

The use of NEVER is straightforward.

def handle_authorization(username, password) -> bool:
    if DEBUG and username == debug_login:
        logger.never('skip authorization for {username}', username=username)
        return True
    return check_password_is_valid(username, password)

Why not just use a DEBUG or WARNING level here? The reason is low priority of such records, which allows them to be mixed with less significant logs or even be skipped by loggers.

Loggers are weak

Unlike the standard logging module, loggers are weak referenced unless they are described explicitly in the configuration dict or created with persistent=True argument.

It means that a logger is eventually garbage collected once it has no active references. This allows creation of a logger per task, not being concerned about running out of memory eventually. However, this also means that all logger settings for a weak logger will be forgotten once it’s collected.

In general this is not a problem since you shouldn’t fiddle with logger settings outside the initialization phase.

Sampling

The library implements internal log sampling. In shorts, it allows you to specify the sample_rate, a probability at which a logger will pass a record to the handlers. It allows to release some load due to extensive logging.

from uvlog import get_logger

logger = get_logger()
logger.sample_rate = 0.25

… or via a config dict

from uvlog import configure

configure({
    'loggers': {
        '': {'sample_rate': 0.25}
    }
})

See the documentation on sampling for more info.

Customization

You can create custom formatters and handlers with ease. Note that inheritance is not required. Just be sure to implement Handler / Formatter protocol.

See the extension guide for more info. There’s an example of HTTP queue logger using requests library there.

Performance

Benchmark results are provided for the M1 Pro Mac (16GB). The results are for the StreamHandler writing same log records into a file. The QueueStreamHandler provides similar performance, but has been excluded from the test since Python threading model prevents results from being consistent between runs. However, I’d still recommend using the QueueStreamHandler for server applications.

name

total (s)

logs/s

%

python logger

0.085

117357

100

uvlog text

0.022

455333

388

uvlog json

0.015

665942

567

Compatibility

There’s a certain compatibility between this logger and the standard logger. However, it’s impossible to preserve full compatibility because of certain design decisions.

See the compatibility guide if you want to migrate from the standard python logger to this one.

License

MIT License

Copyright (c) 2024 violet-black

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.