Skip to main content

Not-co-logger

Not-co-logger writes application logs to stdout as one JSON object per line, in a fixed format. It is meant for services running in cloud environments, where a log collector reads stdout and logs from many services end up in one place. Consistent fields make these logs easy to search, filter and correlate.

The library is deliberately small and opinionated:

  • One JSON line per event, written to stdout. No handlers, formatters or log files to configure.
  • A fixed set of fields. Every event has a type, a human-readable message and a group. Anything else goes in a free-form meta object.
  • A request ID on every line, so all events from one request or job can be found together.
  • One setting: the minimum log level, read from an environment variable.

Install

pip install notcologger

Requires Python 3.11 or newer.

Quick start

from notcologger import info

info("orders.checkout.start", "Checkout started", "request",
     user="u-42", meta={"path": "/checkout"})
{"timestamp": "2026-10-28T09:59:36.891114", "level": "info", "rid": "e5c4be97-7ae7-4da4-a114-d0128d0c5616", "type": "orders.checkout.start", "message": "Checkout started", "group": "request", "user": "u-42", "meta": {"path": "/checkout"}}

The module-level functions are debug, info, warn and error. They all take (logtype, message, group, user=None, meta=None). Outside a request context, each call gets a new request ID.

Log format

Field Description
timestamp Local time in ISO 8601 format, added automatically.
level debug, info, warning or error.
rid Request ID that ties related events together. Generated (UUID4) if not given.
type What happened, as a stable identifier, e.g. orders.checkout.start.
message Human-readable description.
group Broad category of the event, see Groups.
user User identifier, such as a user ID or IP address. Defaults to SYSTEM.
meta Free-form object with any extra data. Omitted when empty.

Conventions

Log types

A log type should be unique, or at least easy to grep for. Name types hierarchically, such as component.module.subtask, and make them informative but short.

Levels

  • error: something failed and needs attention from administrators.
  • warning: something went wrong, but nobody needs to be alerted urgently.
  • info: normal operation.
  • debug: detailed developer output that helps answer "what just happened?".

Groups

  • request: incoming requests
  • response: outgoing responses
  • session: events related to an active session
  • technical: technical state or problems

Usage

LogSpan

A LogSpan logs events under one request ID. It has the same debug, info, warn and error methods as the module. annotate() adds metadata to every event the span logs after it; meta given to an individual event takes precedence.

from notcologger import LogSpan

log = LogSpan(request_id)
log.annotate(session="s-1234")
log.info("auth.login.success", "User logged in", "session", user="u-42")

Request context

log_context() binds module-level logging calls to a request ID and metadata, so you don't have to pass a LogSpan through every function:

from notcologger import info, log_context

async def handle_request(request_id):
    with log_context(requestId=request_id, meta={"service": "billing"}):
        info("request.start", "Handling request", "request")
        await process_payment()  # its module-level log calls use the same rid
  • If no requestId is given, a new ID is generated. In a nested context, the ID is inherited from the enclosing context.
  • Metadata is merged one level deep: values from inner contexts override outer ones, and an event's own meta overrides both.
  • Context metadata must be JSON-serializable. A snapshot is taken on entry, so later changes to the dictionary have no effect.
  • Asyncio tasks and asyncio.to_thread() inherit the active context. Threads started in other ways do not; use contextvars.copy_context().run to pass it.
  • LogSpan and ExceptionSpan objects ignore the context. Pass the request ID to them directly if you need it.

ExceptionSpan

ExceptionSpan logs an error only if an exception leaves the with block. The exception is not caught: it still propagates.

from notcologger import ExceptionSpan

with ExceptionSpan("orders.payment.failed", "Payment failed", "technical") as log:
    charge_card()
    log.info("orders.payment.success", "Payment succeeded", "technical")

The error event includes the exception type and arguments in meta. ExceptionSpan also accepts requestId, level, user and meta.

Configuration

Set NOTCO_LOGLEVEL to debug, info, warning or error to choose the minimum level that is logged. If the variable is missing or has another value, the level is debug.

Metadata and errors

  • Bytes and exceptions at the top level of an event's meta are converted to strings. Everything else must be JSON-serializable.
  • NaN and infinite floats raise ValueError, because they are not valid JSON.
  • Logging does not modify the dictionaries you pass in.
  • If an event can't be serialized or written, the logging call raises an exception. The exception is when ExceptionSpan is already logging an error for an exception leaving its block: then logging errors are suppressed so the original exception propagates unchanged.

See CHANGELOG.md for release notes.

Metadata

Release files for notcologger 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for notcologger 0.3.0
File Size Uploaded Provenance Actions
notcologger-0.3.0.tar.gz 6.2 kB Without provenance Details for notcologger-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for notcologger 0.3.0
File Interpreter ABI Platform Actions
notcologger-0.3.0-py3-none-any.whl Python 3 none any Details for notcologger-0.3.0-py3-none-any.whl

Total release size: 13.1 kB

Release files / notcologger-0.3.0.tar.gz

Download URL notcologger-0.3.0.tar.gz
Size 6.2 kB
Tags Source
SHA-256 checksum
How to use checksums
734d7546155cb1fda04ccc3f44fb6e8113ca7ec1380f947c721fa1a75e84b87c
BLAKE2b-256 checksum
How to use checksums
193a4a5e35a2369986c29085c1f621e0e94cf525f0a1ab4566f5011f445d5091
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.15.2

Release files / notcologger-0.3.0-py3-none-any.whl

Download URL notcologger-0.3.0-py3-none-any.whl
Size 6.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dcd01bd2d2787ffd157287c126f75d83a3a86f60c6b8b1cee3fc023bf441122f
BLAKE2b-256 checksum
How to use checksums
03746a189a9de783fcf2a9d574e431caeaaf4be0e959a0c432b11d9717783244
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.15.2

Security for notcologger 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page