Pod Logs
The platform’s pod logs: reading them, formatting their lines for an LLM, and grouping errors by their signature.
The logs come from smarter.apps.infrastructure.services.infrastructure .logs: the last
72 hours of the app (smarter), the Celery workers (smarter-worker,
smarter-worker-infrastructure), beat (smarter-beat) and MariaDB (mariadb), kept in Loki.
Every query is built from structured filters (log_query()), never from LogQL that a caller
wrote, so an LLM cannot inject a query of its own.
log_sources(): the components and pods that wrote to the logs in a period.search_logs(): the lines that match a set of filters.log_errors(): the errors, or warnings and errors, of a period, grouped by their signature.
The MCPServer’s staff-only log tools (smarter.apps.mcpserver.protocol.tools.logs) call them,
and show log lines with format_line(), which puts the time, component and pod before each
line, and cuts a long line with short(), so that one stack trace cannot fill the LLM’s context.
log_errors() groups lines with error_groups(). A signature is a line’s message with
what varies between occurrences removed: its timestamp, numbers, ids, hashes and quoted values.
Lines with the same signature are the same problem, so error_groups() counts them, and keeps
one example of each. A thousand timeouts of one provider then read as one group, with a count,
rather than a thousand lines.
Side effects: log_sources(), search_logs() and log_errors() make one or two
HTTP GETs to Loki (in the tests, to the in-memory log service). Nothing is cached or written.
Note
Experimental. The MCPServer was designed and coded by Claude Code (Anthropic’s Claude Opus 5.5), with Lawrence McDaniel as co-author. It is experimental, and will be documented.
- smarter.apps.mcpserver.services.logs.COMPONENTS = ('smarter', 'smarter-worker', 'smarter-worker-infrastructure', 'smarter-beat', 'mariadb')
the
app.kubernetes.io/nameof their pods.( 'smarter', 'smarter-worker', 'smarter-worker-infrastructure', 'smarter-beat', 'mariadb', )
- Type:
The components whose logs are kept
- class smarter.apps.mcpserver.services.logs.ErrorGroup(component, level, signature, count=0, first_seen=None, last_seen=None, example='', pods=<factory>)[source]
Bases:
objectThe lines of one component that have the same level and signature: one problem, and how often it happened.
error_groups()creates the groups, and callsadd()for each line.summarize_log_errorsshows each group as a heading, its signature and its example.- __init__(component, level, signature, count=0, first_seen=None, last_seen=None, example='', pods=<factory>)
- add(line)[source]
Count a line in the group.
The line’s time widens
first_seenandlast_seen, and the latest line is kept as theexample. A line as late as the current example replaces it.
- first_seen: datetime | None = None
When the earliest line was written, in UTC, or None before the first
add().
- last_seen: datetime | None = None
When the latest line was written, in UTC, or None before the first
add().
- signature: str
The lines’
signature().
- class smarter.apps.mcpserver.services.logs.LogErrors(lines, truncated, groups)[source]
Bases:
objectThe errors of a period, grouped by their signature, as
log_errors()returns them.- __init__(lines, truncated, groups)
- groups: list[ErrorGroup]
The groups, the most frequent first.
- truncated: bool
Whether there were more lines than
SUMMARY_LINES, of which only the latest were grouped.
- class smarter.apps.mcpserver.services.logs.LogSources(components, missing, pods)[source]
Bases:
objectThe components and pods that wrote to the logs in a period, as
log_sources()returns them.- __init__(components, missing, pods)
- missing: list[str]
The components of
COMPONENTSthat wrote nothing, e.g. because they are down.
- smarter.apps.mcpserver.services.logs.MAX_LINE_CHARACTERS = 500
The most characters of one log line that a tool shows.
Longer lines are cut by
short(), and end with an ellipsis.
- smarter.apps.mcpserver.services.logs.MAX_MINUTES = 4320
the log store’s retention.
- Type:
The longest period that can be read
- smarter.apps.mcpserver.services.logs.SUMMARY_LINES = 5000
The most warning and error lines that
log_errors()groups.
- smarter.apps.mcpserver.services.logs.error_groups(lines)[source]
Group log lines by component, level and signature.
The same message from two components is two groups, since it is usually two problems.
- smarter.apps.mcpserver.services.logs.format_line(line)[source]
Return a log line as a tool shows it: when, where, and its text.
The time is UTC, to the second, and the text is cut with
short().
- smarter.apps.mcpserver.services.logs.log_errors(minutes, min_level, components=())[source]
Return the errors, or the warnings and errors, of the last minutes, grouped by their signature.
Side effects: one HTTP GET to Loki’s
query_range.- Parameters:
- Return type:
- Returns:
The number of lines, whether they were cut at
SUMMARY_LINES, and the groups.- Raises:
SmarterMCPServerServiceError – If the query is not valid.
LogServiceError – If Loki could not be reached, or rejected the query.
InfrastructureNotReadyError – If there is no log store.
- smarter.apps.mcpserver.services.logs.log_query(**kwargs)[source]
Return a query of the most recent period, with a validation error that the caller can correct.
LogQueryvalidates what a caller’s arguments may not, e.g. that apatterncompiles, or that apodis a Kubernetes name.- Parameters:
kwargs – The query’s fields, for
last(), starting withperiod.- Return type:
- Returns:
The query, of the most recent period.
- Raises:
SmarterMCPServerServiceError – If the query is not valid.
- smarter.apps.mcpserver.services.logs.log_sources(minutes)[source]
Return the components and pods that wrote to the logs in the last minutes.
Side effects: two HTTP GETs to Loki, for the values of the
componentandpodlabels.- Parameters:
minutes (
int) – The period, in minutes, up to now.- Return type:
- Returns:
The components, the components that wrote nothing, and the pods.
- Raises:
LogServiceError – If Loki could not be reached.
InfrastructureNotReadyError – If there is no log store.
- smarter.apps.mcpserver.services.logs.search_logs(minutes, limit, components=(), pod=None, contains=(), pattern=None, min_level=None, oldest_first=False)[source]
Return the lines of the last minutes that match every filter.
Side effects: one HTTP GET to Loki’s
query_range.- Parameters:
minutes (
int) – The period, in minutes, up to now.limit (
int) – The most lines to return.components (
tuple[str,...]) – The components to read, or an empty tuple for all of them.pod (
str|None) – One pod’s name, or None for every pod of the components.contains (
tuple[str,...]) – Texts that every line must contain, case insensitively.pattern (
str|None) – A regular expression, in RE2 syntax, that every line must match, or None.min_level (
LogLevel|None) – The least severe level to return, or None for lines of every level.oldest_first (
bool) – Whether to return the oldest lines of the period first, rather than the newest.
- Return type:
- Returns:
The lines, newest first unless
oldest_first.- Raises:
SmarterMCPServerServiceError – If the query is not valid, e.g. its pattern does not compile.
LogServiceError – If Loki could not be reached, or rejected the query.
InfrastructureNotReadyError – If there is no log store.
- smarter.apps.mcpserver.services.logs.signature(text)[source]
Return a log line’s signature: its message, without what varies between occurrences.
The leading timestamp is removed with
TIMESTAMP_PREFIX, then each ofVARIABLE_PARTSis replaced with its placeholder, e.g.2026-11-02 14:00:01 - ERROR - timeout after 30s for 'gpt-4o'becomesERROR - timeout after Ns for <str>.