feat(logs): add datumctl logs for project-scoped log queries - #289
Draft
0xmc wants to merge 1 commit into
Draft
Conversation
Adds a top-level `logs` command that queries the queryapi aggregated
APIService for the active project, plus the URL helper it needs.
The command sends a LogQL-subset query (label matchers and line filters)
to queryapi's Loki-shaped /query_range endpoint and renders the returned
streams. Server-side project scoping means the caller never supplies a
tenant identifier.
ProjectLogsAPIPrefix is pinned to o11y.miloapis.com/v1alpha1/logs to
match the APIService registration. The group, the version and the /logs
segment are all load-bearing: queryapi derives the paths it serves and
the permission it reviews (logs.query) from the same constants, so any
mismatch is a 404 from the aggregator rather than a fallback.
Rendering notes:
- Label sets print in sorted key order. Ranging over the map directly
emitted a different permutation on every run, so two identical
queries produced output that did not compare equal.
- Lines from all streams are flattened and re-sorted by timestamp
before printing. queryapi returns one stream per distinct label set,
so printing stream by stream walked the clock backwards at every
stream boundary -- most of the output for a service spread over
several label sets.
- Unparseable timestamps sort together at the end rather than as the
epoch, so a malformed value does not masquerade as 1970 data.
Errors surface the response body, unwrapping either the Loki envelope or
a Kubernetes Status, with a hint per status code. Without it a rejected
query and a permission denial were indistinguishable.
--since and --start are mutually exclusive rather than silently
preferring one, and a query argument is required: queryapi rejects a
selector with no label matchers, so the previous "{}" default could only
ever produce a 400.
Live tailing is not offered; queryapi does not implement
/loki/api/v1/tail yet.
Relates to milo-os/telemetry#77
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
@0xmc this should be a datumctl "telemetry" plugin that's exported from the o11y repo |
0xmc
marked this pull request as draft
September 10, 2026 13:44
Contributor
Author
|
Leaving this PR open (as Draft) until the work is moved over to https://github.com/milo-os/telemetry. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a top-level
datumctl logscommand that queries telemetry logs for the active project through the queryapi aggregated APIService.Queries use the LogQL subset queryapi accepts — label matchers and line filters. Project scoping is resolved server-side, so the client never supplies a tenant identifier.
Closes #288, which is tracked as a sub-issue of the milo-os/telemetry#90 epic. The server-side counterpart is milo-os/telemetry#77.
Endpoint wiring
ProjectLogsAPIPrefix()is pinned too11y.miloapis.com/v1alpha1/logs, matching queryapi'sAPIServiceregistration. All three parts are load-bearing — queryapi derives both the paths it serves and the permission it reviews (logs.query) from the same constants, so a mismatch is a 404 from the aggregator rather than a graceful fallback. Appending the prefix to the project control-plane host reproduces the server URL declared in queryapi's ownopenapi.yaml.Verification against staging
Confirmed against
api.staging.env.datum.net:logs.queryauthorization both resolvelogql: metric queries and aggregations are not supportedA real end-to-end query returns zero rows in staging today, for reasons outside this repo: every row in
o11y.logscarriesProjectId = 'internal'.ProjectIdisMATERIALIZED ResourceAttributes['milo.project.id']and the row policy isProjectId = getSetting('telemetry_project_id'), so no customer project can match until the edge pipeline stamps that attribute with the owning project. Verified directly in ClickHouse, and by generating real traffic through anHTTPProxyin a test project — the Envoy access logs were ingested correctly but taggedinternal. Details in #288.Rendering details
Errors and flags
Failures surface the response body, unwrapping either the Loki envelope or a Kubernetes
Status, with a per-status hint. Previously a rejected query and a permission denial were indistinguishable.--sinceand--startare now mutually exclusive rather than silently preferring one. A query argument is required: queryapi rejects a selector with no label matchers, so the previous{}default could only ever produce a 400.Not included
/loki/api/v1/tailyet./labels,/label/{name}/valuesand/seriesare served by queryapi but not yet wrapped as subcommands.timestamp | labels | line. A kubectl-style default of the bare line with opt-in--timestamps/--labelsis worth considering, and would pair with routing output throughIOStreamsinstead offmt.Printf— which would also make the renderer unit-testable.🤖 Generated with Claude Code