LLMOpsThe LLMOps lifecycle · MLflow 3 · FastAPI · Docker · AWS/Azure
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
41 small wins to finish your pathNext lesson →

Logging you can operate on

Operational logging is logging written for the person running the app, not the person writing it: one configured logger, structured lines carrying the fields an operator filters on, sent to a place a log system can read.

Last updated: 15 Oct, 2026 · LLMOps

The Config, not hard-coded constants lesson moved settings into a file. Logging is the next concern the layout gives its own folder. A print is fine while you build; in production you need lines an operator can search, filter by level and feed to a dashboard. The repo keeps this in a logger/ package so every component shares one configured logger, which is the practice this lesson builds.

A formatter that writes structured lines

A plain log line is hard to query. This formatter writes each record as one JSON object, with the level, the logger name, the event text and any extra fields the caller attached. One line is one object a log store can index.

python
import json, logging, sys

class JsonFormatter(logging.Formatter):
    def format(self, record):
        # one JSON object per line, easy to grep or ship to a log store
        line = {"level": record.levelname, "logger": record.name,
                "event": record.getMessage()}
        line.update(getattr(record, "fields", {}))
        return json.dumps(line)

One logger the app shares

get_logger builds that logger once: it sets the level, attaches a single handler that writes to standard output, and turns off propagation so a line is not printed twice. Clearing the handlers first keeps a re-import from stacking a second handler on.

python
def get_logger(name="assistant"):
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)

    logger.handlers.clear()                # one handler, even on re-import
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(JsonFormatter())
    logger.addHandler(handler)

    logger.propagate = False
    return logger

Logging a request and a warning

The assistant logs an ordinary request and a warning, each with structured fields. The fields ride in extra under a fields key, which the formatter folds into the JSON.

ExampleTwo structured log lines, one per event
from applog import get_logger

log = get_logger()

# structured context travels as fields, not baked into the message text
log.info("chat request received", extra={"fields": {"user": "C-5", "intent": "refund"}})
log.warning("retrieval returned few hits", extra={"fields": {"query": "refund", "hits": 1}})

What the structured lines give an operator

  • Each line is one JSON object, so a log system can index it and a query can filter by level or by any field without a regular expression over free text.
  • The context is in fields, not in the sentence, so intent=refund is a value to group and count on, rather than words to parse out of a message.
  • The level is explicit, so the warning about thin retrieval stands apart from the ordinary request and can raise an alert on its own.
One structured log line drawn as a single bar holding three parts: a level of WARNING, an event reading retrieval returned few hits, and fields giving query refund and hits one. Each part has a dashed arrow down to what an operator does with it: the level sorts an ordinary request from a warning worth an alert, the event says what happened in words that stay the same every time, and the fields are queryable so you can filter by user, group by intent and count the hits. A closing warning says never to log the prompt, the customer's message or the key, because a field is also a leak.

A print against an operational log

A printAn operational log line
Goes toStandard output onlyA handler you choose, here stdout
Has a levelNoINFO, WARNING, ERROR and more
Carries fieldsBaked into the stringStructured, queryable fields
Can be filteredBy text searchBy level and by field

Where operational logging helps

  • Finding one request in a flood, by filtering on a field such as the user or the session.
  • Raising an alert, where a rising count of WARNING or ERROR lines trips a monitor.
  • Measuring behaviour, by counting a field such as the chosen tool or the retrieval hit count over time.
Watch out. A structured line is only as useful as the fields you put on it, and a field can leak. Do not log a full prompt, a customer's message or an API key, because the log store is a second copy of whatever you write. Log identifiers and counts, and keep the sensitive text out.
Try it yourself
  • Add a session field to one of the log calls and watch it appear in the JSON.
  • Lower the level to logging.WARNING and confirm the INFO line stops printing.
  • Swap the handler for a FileHandler and read the same JSON lines from a file.

Every expert started right here.