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/name of 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: object

The lines of one component that have the same level and signature: one problem, and how often it happened.

error_groups() creates the groups, and calls add() for each line. summarize_log_errors shows 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_seen and last_seen, and the latest line is kept as the example. A line as late as the current example replaces it.

Parameters:

line (LogLine) – A line with the group’s component, level and signature.

Return type:

None

component: str

The component that wrote the lines, e.g. smarter-worker.

count: int = 0

How many lines the group has.

example: str = ''

The latest line’s text, cut with short().

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().

level: LogLevel | None

The lines’ level, or None if they name none.

pods: set[str]

The names of the pods that wrote the lines.

signature: str

The lines’ signature().

class smarter.apps.mcpserver.services.logs.LogErrors(lines, truncated, groups)[source]

Bases: object

The 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.

lines: int

The number of lines that were grouped.

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: object

The components and pods that wrote to the logs in a period, as log_sources() returns them.

__init__(components, missing, pods)
components: list[str]

The components that wrote to the logs.

missing: list[str]

The components of COMPONENTS that wrote nothing, e.g. because they are down.

pods: list[str]

The pods that wrote to the logs.

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.

Parameters:

lines (list[LogLine]) – The lines, e.g. the warnings and errors of the last hour.

Return type:

list[ErrorGroup]

Returns:

The groups, the most frequent first, then the most recent.

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().

Parameters:

line (LogLine) – The line.

Return type:

str

Returns:

e.g. 2026-11-02 14:00:01 smarter-worker/smarter-worker-6f9c-x2: <text>.

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:
  • minutes (int) – The period, in minutes, up to now.

  • min_level (LogLevel) – LogLevel.ERROR for errors and critical lines, or LogLevel.WARNING to include warnings.

  • components (tuple[str, ...]) – The components to read, or an empty tuple for all of them.

Return type:

LogErrors

Returns:

The number of lines, whether they were cut at SUMMARY_LINES, and the groups.

Raises:
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.

LogQuery validates what a caller’s arguments may not, e.g. that a pattern compiles, or that a pod is a Kubernetes name.

Parameters:

kwargs – The query’s fields, for last(), starting with period.

Return type:

LogQuery

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 component and pod labels.

Parameters:

minutes (int) – The period, in minutes, up to now.

Return type:

LogSources

Returns:

The components, the components that wrote nothing, and the pods.

Raises:
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:

list[LogLine]

Returns:

The lines, newest first unless oldest_first.

Raises:
smarter.apps.mcpserver.services.logs.short(line, limit=500)[source]

Return a line, cut to a limit.

Parameters:
  • line (str) – The line’s text.

  • limit (int) – The most characters to return.

Return type:

str

Returns:

The line, if it is no longer than the limit, and otherwise its first limit - 1 characters followed by …, which is limit characters in all.

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 of VARIABLE_PARTS is replaced with its placeholder, e.g. 2026-11-02 14:00:01 - ERROR - timeout after 30s for 'gpt-4o' becomes ERROR - timeout after Ns for <str>.

Parameters:

text (str) – The line.

Return type:

str

Returns:

The signature, at most 200 characters.