Skip to content

feat: add a native logs API to ops.pebble#2660

Draft
tonyandrewmeyer wants to merge 1 commit into
canonical:mainfrom
tonyandrewmeyer:feat/pebble-logs-api
Draft

feat: add a native logs API to ops.pebble#2660
tonyandrewmeyer wants to merge 1 commit into
canonical:mainfrom
tonyandrewmeyer:feat/pebble-logs-api

Conversation

@tonyandrewmeyer

Copy link
Copy Markdown
Collaborator

Pebble has a /v1/logs endpoint, but ops.pebble.Client doesn't expose it. This PR changes adds support, including to Scenario.

The PR adds LogEntry (a frozen dataclass with the parsed RFC 3339 time, service name, and message) and two Client methods mirroring the Go client's Logs/FollowLogs split: get_logs() eagerly returns the buffered entries (n=-1 for the whole buffer), and follow_logs() returns a generator that streams new entries. Container gains get_logs() only, because you probably don't want to block in a hook.

In Scenario, the buffer is modelled as Container.service_logs, holding state.LogEntry entries (time defaults to now, so tests can write LogEntry('redis', 'ready')). The mock client sorts them by time (stably, keeping the state's order for equal times), filters by service, and applies Pebble's n semantics.

Verified by hand against a real Pebble 1.32 daemon (default n, filtering, n=-1, n=0, APIError on bad n, and live streaming via follow_logs).

Pebble has had a /v1/logs endpoint from the start, but ops.pebble.Client
never exposed it, so tools built on ops (for example borescope, see
tonyandrewmeyer/borescope#85) had to shell out to a pebble binary or
write their own socket client to read service logs.

Add LogEntry (a frozen dataclass with the parsed RFC 3339 time, service
name, and message) and two Client methods mirroring the Go client's
Logs/FollowLogs split: get_logs() eagerly returns the buffered entries
(n=-1 for the whole buffer), and follow_logs() returns a generator that
streams new entries. The endpoint streams newline-delimited JSON, so
both go through _request_raw and iterate the response line by line;
following has no natural deadline, so _request_raw gains a timeout
override and the request is made with no socket timeout. Container
gains get_logs() only: blocking a hook indefinitely is a footgun, so
following is reached via container.pebble.

In Scenario, model the buffer as Container.service_logs, holding
scenario.state.LogEntry entries (time defaults to now, so tests can
write LogEntry('redis', 'ready')). The mock client sorts them by time
(stably, keeping the state's order for equal times), filters by service,
and applies Pebble's n semantics; its follow_logs ends after the
buffered entries, since nothing in a test writes new ones. Harness gets
NotImplementedError stubs.

Tested with MockClient unit tests, a streaming /v1/logs route in the
fake_pebble unix-socket server, and Scenario e2e tests; also verified
by hand against a real Pebble 1.32 daemon (default n, filtering, n=-1,
n=0, APIError on bad n, and live streaming via follow_logs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread ops/model.py
service_names: Optional service names to get logs for. If no
service names are specified, get logs for all services.
n: Maximum number of log entries to return, across all requested
services combined. Use -1 to get everything in the buffer.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this probably should be None, and probably entries or count or similar rather than n.

Comment thread ops/pebble.py
services: Names of the services to get logs for. If not specified,
get logs for all services.
n: Maximum number of log entries to return, across all requested
services combined. Use -1 to get everything in the buffer.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here.

Comment thread ops/pebble.py
services: Names of the services to get logs for. If not specified,
get logs for all services.
n: Number of buffered log entries to yield before following. This
defaults to 0 (only new entries); use -1 to first get

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant