Loki logql-range/v1alpha1
Rendered from adapters/loki/contracts/logs/v1alpha1/semantics.md by connectors-docs; the source owns every rule on this page.
- Status: proposed; the unpaged library binding of §11.3 is implemented, without executable composition.
- Family: datasources. Siblings: records, series, relational (in service v1alpha1).
- Recorded: 2026-09-08; ownership relocated 2026-09-09.
- Shared dependency: datasource.logs/v1alpha1. This file owns all Loki-specific semantics.
1. Identity
| Field | Value |
|---|---|
| Contract | datasource.logs/v1alpha1 |
| Profile | logql-range |
| Shared with records | provenance, completeness, cursor binding rules (docs/design.md:434-446); not the record page shape |
More columns: swipe horizontally, or focus the table and use the arrow keys.
A log read returns a bounded observation of the provider's available logs, with explicit selection, order and truncation. Loki preserves native LogQL and returned stream labels. A result proves neither archival retention, absence of late arrivals, a provider-wide snapshot nor continuity across provider rotation/restart.
Loki continuation pages one immutable retained observation. It never reissues a provider query with an adjusted timestamp edge. The selected provider API supplies neither occurrence identity nor a tie cursor; timestamp/text/label hashes cannot distinguish two genuine identical entries. A saturated provider response therefore ends with a truthful non-resumable partial result after its retained entries have been delivered. The provider evidence record identifies the exact sources and selection limits.
3. Types
Input, logql-range:
{ "query": "{app=\"api\"} |= \"error\"", "start_unix_ns": "1788822000000000000", "end_unix_ns": "1788825600000000000", "direction": "backward", "limit": 200, "collection_limit": 1000, "max_line_bytes": 8192 }
Only query and start are required. Direction defaults to backward; limit and collection_limit default to 1,000; max_line_bytes defaults to 8,192. limit is the public page size; collection_limit is the single provider query's entry cap. Both are integers in 1–1,000 with limit <= collection_limit; max_line_bytes is an integer in 1–8,192. This explicit distinction permits small public pages while keeping total collection bounded.
Resume input is exactly {"cursor":"opaque value"}. Mixed initial/resume fields, explicit null aliases, unknown members, provider interval/step/since overrides and caller origin/route/header selectors are InvalidInput. Resume cannot change the original query, time window, page size or projection.
Output:
{
"lines": [ { "timestamp_unix_ns": "1788825599000000000", "stream": { "app": "api" }, "line": "…", "line_truncated": false, "redacted": false, "source": null } ],
"selection": { "kind": "loki-range", "start_unix_ns": "…", "end_unix_ns": "…", "direction": "backward" },
"order": "timestamp-backward",
"complete": false,
"truncation": { "causes": ["provider_limit"], "occurrences_dropped": null, "stream_groups_dropped": null },
"next_cursor": null,
"provenance": { "instance": "…", "resource": "loki:…", "observed_at_unix_ms": 0, "source_revision": null }
}
| Field | Rule |
|---|---|
timestamp_unix_ns | canonical nonnull decimal string; conversion preserves actual source precision |
stream | returned Loki labels |
source | null |
redacted / line_truncated | required Boolean flags for configured redaction and UTF-8-safe line clipping, independently |
selection / order | closed profile variants below; no fabricated universal window |
complete | source exhaustion AND no omitted occurrences AND final retained page; line-content clipping is separate (§5) |
truncation | all applicable causes, plus exact counts only where the total is known (§5) |
next_cursor | Loki only, iff another retained offset exists; null at a terminal partial result even when unseen provider entries may remain |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Loki selection is {kind:loki-range,start_unix_ns,end_unix_ns,direction} and uses order=timestamp-forward/backward. An output label changed by native LogQL is a value, not proof of an original selector or authority. Safe errors and byte accounting are in §5.
4. Rules
Current host principal, visible operation, source/connection, scope and applicable credential/route evidence are admitted before dispatch. Known revocation and current binding fences also govern publication and result disclosure. These are host ordering guarantees, not an atomic transaction with future provider permission changes. No stale-on-error, cross-principal cache reuse, live follow or automatic read redispatch is selected. F15 owns any later refresh/retry profile under its original shared budget.
4.1 Provider tenant binding
The direct Loki connection owns its admitted provider tenant. When required by the
deployment, configure http.extra_headers: {"X-Scope-OrgID":"tenant-a"} under the
shared HTTP header rules.
tenant_header was an earlier descriptive label, not a supported key. No caller
input may choose or override the tenant. Header placement and revision fencing are
future binding obligations; current HttpConfig does not implement the extension.
A mediated connection consumes the shared mediated-HTTP capability and requires a parent route that establishes the configured provider tenant. It sends no direct header override through that capability and refuses a missing guarantee without fallback. Loki knows no concrete parent implementation.
4.2 Native LogQL scope
Configuration selects query_scope.required_equalities, a duplicate-free array of exact {label,value} pairs. Missing scope configuration is invalid; an explicitly admitted empty array means the entire configured provider tenant is allowed. Valid provider label names and nonempty required values avoid empty-label/missing-label ambiguity.
A reviewed parser conforming to Loki v3.7.0 must accept the complete native log-selector or log-pipeline expression and inspect every initial stream selector. Each must contain every required equality with the exact decoded label/value and equality operator. Extra matchers and native filters/parsers/formatters remain supported. A regex, text inside a string, a pipeline label filter or a rewritten output label cannot discharge an initial equality. Metric/sample syntax is not a log query: literal/vector expressions also implement the provider's LogSelectorExpr interface, so interface membership alone is insufficient. The selected root must actually be a stream selector or pipeline.
The native parser evidence retains the exact AST/parser sources used to distinguish those roots. It supplies source evidence, not an implemented or verified local parser.
Parse failure, unsupported/non-log syntax or exceeding 16 KiB UTF-8, 4,096 syntax nodes or depth 64 is InvalidInput. A valid log query missing the required scope is Forbidden. Neither dispatches. The admitted original query bytes are URL-encoded and forwarded unchanged: no reserialization, injected filters, fixed-query substitute or remote parsing probe. A missing/unverified parser cannot advertise the scoped profile. The same predicate applies through mediation and to declared verification queries.
4.3 Loki collection and ordering
Input nanoseconds are canonical 0 or [1-9][0-9]*, at most 9,223,372,036,854,775,807. Reject signs, whitespace, leading zeroes, decimal/exponent forms and overflow. Resolve omitted end once from the trusted receiver clock; require start < end and checked end-start <=86,400,000,000,000. The single GET fixes both exact decimal times, direction, limit=collection_limit and JSON output. No provider default time or sampling interval is used. Start is inclusive and end exclusive in both directions.
Validate the entire bounded JSON response before publishing. Require successful status, resultType=streams, string labels, valid two-element timestamp/text tuples, in-window representable timestamps and provider order within each group. Reject duplicate JSON keys, missing result type, metric results, malformed entries, unsupported tuple extensions, wrong ordering/window and counts above collection_limit as Unavailable. No malformed-entry skipping or incomplete-JSON prefix may establish an observation. This selected plain Loki success format has no generic partial field; unknown warning/partial extensions must fail safely instead of being ignored.
Retain the first 500 provider stream groups in response order, counting all omitted groups and occurrences from the fully validated response. Groups with identical returned labels remain separate groups for this bound; native pipelines can make their labels equal. Flatten retained groups with private (group ordinal,entry ordinal) coordinates. Sort by timestamp in the requested direction; ties use ascending coordinates. Never deduplicate equal timestamp/labels/text, even after redaction or clipping. Coordinates identify positions in this captured observation only.
Apply any admitted deterministic redaction once, flag changes, and then take the longest UTF-8 prefix within max_line_bytes. Clipping shortens content, not the occurrence count. Intern group labels and retain the immutable resulting occurrences, index and original private context under the storage ceiling. If the whole observation cannot fit, return Unavailable before the first page; do not drop stored pieces while claiming complete continuation.
Provider exhaustion requires a complete successful validated response with fewer than collection_limit occurrences, no known partial execution, and a provider/proxy binding verified to honor that effective limit and surface incomplete execution. A hidden smaller limit invalidates this proof. Exact-cap responses remain conservative partial even if exactly that many source entries existed. A validated empty response can establish exhaustion under the same conditions. Stats and emitted page size cannot establish it.
An unverified effective limit or incomplete-execution guarantee refuses the read as Unavailable. A smaller emitted count cannot repair that missing binding proof.
4.4 Retained paging and current authority
The host's logical ReadCursorCachePort owns this bounded ephemeral observation and context; it is not another provider entity or discovery publication. Offset zero starts the initial page. A page is the longest consecutive slice fitting the fixed public limit and complete serialized response ceiling, including repeated labels, escaping, selection, cursor and provenance. Reserve sufficient cursor/envelope bytes when deciding the slice. If even metadata plus one occurrence cannot fit, refuse without a cursor. Only a verified empty observation yields an empty page; a nonterminal page is never empty.
A next cursor exists iff a later retained offset exists. It authenticates an unpredictable observation reference and offset, bound to instance, operation/profile/contract/projection revision, connection and configured provider authority/tenant, direct or mediated source and parent/route/credential/private fences, principal/tenant/scope/policy, exact query/window/direction, collection/page/line limits and redaction configuration. No credential, origin or private route locator is exposed in the opaque token. Current admission precedes disclosure of cursor validity and cached content; then every context coordinate must match. A cursor grants no authority.
Repeating an admitted cursor returns the same slice without provider I/O, with a strictly advancing next offset when present. All pages preserve the original provider observation time and source_revision:null. Each request has its own ordinary handling deadline but cannot extend the fixed observation expiry, at most 300 seconds from initial observation. Expiry, eviction, restart/key loss or changed context is StaleCursor after current admission; unavailable authority/cache infrastructure is Unavailable. Never regenerate a view, resolve a fresh now, query a different edge or fall back to direct access. A new initial read is a separate observation and may repeat or change entries.
Cross-request query caching remains disabled by default; the retained observation is the explicitly selected exception needed for its own continuation. Current known revocation and binding fences govern publication and disclosure, including every cached page. Successful earlier admission or unchanged query text cannot override them.
5. Limits and completeness
| Bound | Value | Source |
|---|---|---|
| Loki occurrences / stream groups | 1,000 / 500 per observation | selected from old projection |
| Line bytes | 8 KiB maximum after redaction, before result JSON escaping | old projection ceiling |
| Loki labels | <=128 labels/group, <=128 UTF-8 bytes/name, <=4 KiB/value and <=32 KiB serialized group; violation refuses, never clips labels | selected metadata bound |
| Loki provider response | independently <=4 MiB encoded entity and decoded/decompressed JSON; <=16 KiB headers | selected bounded full decode |
| Retained observation | <=4 MiB accounted storage including interned labels, occurrences/index and private context | selected ephemeral retention |
| Retained log pool | <=64 MiB / 256 observations per service instance, <=16 MiB / 64 observations per admitted principal/tenant partition; smaller configured limits allowed | selected host capacity |
| Public response | <=4 MiB including all framing, escaping and cursor | ordinary service binding |
| Window / relative selector | <=24 h where declared in the selected input | selected profile ceiling |
| Provider work | one log GET; no retry/follow | selected call budget |
| Deadline | existing 20 s outer / 15 s shared provider budget, including admitted mediated work | ordinary service binding |
| Cursor TTL | <=300 s from the original observation, never renewed by pages | selected retention |
More columns: swipe horizontally, or focus the table and use the arrow keys.
These are receiver ceilings, not vendor defaults or measured throughput. Finite reads/cancellation do not prove immediate remote cancellation or bound a provider's internal scan/CPU work. Deployment-side query limits remain independently admitted requirements.
Count any admitted mediated metadata/permission work inside the same 15 s provider / 20 s execution budgets, including at-most-5 s connects; the selected route binding must declare its own finite call budget. No activation/identity probe or refresh is silently added to the read. Retention admission accounts backing allocations and context/index overhead atomically across concurrent requests; expire first, then least-recently-used eviction or refuse a new observation if it still cannot fit. Eviction makes affected cursors stale and never regenerates or extends their observations.
complete = source_exhausted AND no_occurrences_omitted AND final_retained_page. Line clipping is separate and may coexist with complete:true.
Truncation causes are duplicate-free in this order: provider_limit, provider_partial, stream_limit, page_limit, response_bytes, source_bytes, line_bytes. The selected Loki binding uses these independent predicates:
| Cause | Trigger |
|---|---|
| provider_limit | The fully validated provider occurrence count equals collection_limit |
| stream_limit | The fully validated response contains more than 500 stream groups, even if an omitted group is empty |
| page_limit | A retained remainder exists and the emitted page contains limit occurrences |
| response_bytes | A retained remainder exists and appending its next occurrence to this page, with the required envelope/cursor for that candidate page, would exceed 4 MiB |
| line_bytes | At least one emitted line was clipped, including clipping after admitted redaction |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Evaluate page_limit and response_bytes independently; both appear when both predicates hold. Provider/group causes describe the whole observation and persist on every page; page/result/line causes are recomputed for the emitted page. No provider_partial or source_bytes success is selected here: unknown partial extensions and incomplete or oversized source JSON refuse under §4.3. Retained remainders are not dropped occurrences. A terminal incomplete observation may have no cursor while provider_limit/stream_limit persists.
Occurrences_dropped and stream_groups_dropped are exact nonnegative totals only when fully observed evidence establishes them, otherwise null for each affected total. Provider saturation leaves unseen totals unknown, not zero; an exact local omission count is not the total including unknown provider losses. Line clipping alone drops zero occurrences. Redaction has its own flag and does not guarantee secret-free content.
Safe base errors apply. Provider 400 for an admitted native query is InvalidInput without raw provider text. Exhausting the original execution or shared provider budget is Timeout, before or during collection; timeout is not clean EOF or a byte-cutoff success. Other failed, malformed, oversized or interrupted provider observations are Unavailable. No raw provider errors, query text, origins or credentials enter ordinary diagnostics.
6. Conformance scenarios
- 1,001 occurrences at timestamp T, collection_limit=1,000 and public limit=200: retain every returned occurrence including identical lines, deliver five pages, finish incomplete with no cursor and unknown unseen totals. No timestamp-edge query follows.
- 999 source occurrences under collection_limit=1,000 and public limit=200: four full pages and one 199-entry page; only the final page is complete when nothing was omitted. Repeating a cursor returns its identical slice.
- Exact-cap source is conservatively partial; validated empty source can be complete. A provider returning 1,800 entries against a requested cap of 1,000 is a protocol failure, not a basis for promising recovery of 800 unseen entries.
- 501 fully observed groups below the occurrence cap: preserve 500 groups, count known omissions, report stream_limit and final incomplete. Byte/page/line/provider causes accumulate without relabeling unknown losses as zero.
- Fixture line of 20 KiB →
line_truncated: true,lineis an 8 KiB UTF-8-safe prefix. - Window exceeding the maximum →
InvalidInput, no dispatch. - Loki 400 (bad LogQL) →
InvalidInput; the error message contains no provider body text. - Tenant header configured → present on the fixture request; caller input cannot set or override it.
- Required equality appears only in quoted line text, regex or a pipeline filter → scope refusal before dispatch. A native admitted selector/pipeline remains byte-for-byte unchanged.
- Metric result, missing type, malformed tuple, wrong window/order, oversized JSON or hidden lower provider cap cannot become complete empty logs.
- Expiry, eviction, policy/credential/parent-route/redaction changes or key loss refuse cursor reuse without a requery. A denied caller learns no private cursor status.
These are specification scenarios, not implemented fixture results. Detailed dispositions and executable shape evidence must record their actual verification boundary; ESS shape acceptance does not execute provider, ordering or authority decisions.
The F12 disposition and scenario record maps every initial finding to these rules and records manual expected outcomes. The model check record states the separate executed ESS boundary.
7. Compatibility
- New profile binding; no old wire contract for logs existed beyond operation results.
- Service compatibility is authoritative for the binding. Log schemas can inhabit the ordinary result slot, but require explicit profile support; framing compatibility does not confer log or cursor semantics on existing typed consumers. Tenant headers remain receiver configuration.
8. SDK and host obligations
| Obligation | Where |
|---|---|
LogLine, LogRead public codecs | future connectors-contracts binding; the current crate has no such implemented types |
| Immutable observation retention and admitted offset cursor | future SDK/host binding; the existing crates/connectors-sdk/src/lib.rs:161 helper authenticates a token but proves neither retention, progress, exhaustion nor scope |
| Native LogQL admission, bounded decoders and current disclosure fences | future adapter/host binding; no implementation provided by this contract |
Receiver-owned http.extra_headers placement and validation (§4.1) | future HttpConfig / HttpCapability binding; current host configuration does not implement this extension |
More columns: swipe horizontally, or focus the table and use the arrow keys.
9. ESS entities
LogQL range selection, direction and required selector equalities are typed in the
Loki-owned model. Shared collection/page facts and
decisions have no shared ESS model; the proposed one is in history at commit 6498a881c. Adapter
predicates and runtime enforcement are not supplied by these private shapes.
| Entity / value | Notes |
|---|---|
OperationDeclaration.profile = logql-range | value |
| Streams, lines | not entities; payload |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Proposed provider-independent collection/page facts and completeness decisions have no shared ESS model today; native selections remain adapter-owned as above. Provider grammar, full context equality, occurrence preservation/order, decoding, clocks, numeric/byte bounds and atomic current publication remain explicitly UNMAPPED binding obligations. No persistent provider entity, parser, public codec or cache implementation is introduced.
10. Open decisions
| Decision | Default taken |
|---|---|
| Redaction | host filter, opt-in, flagged per line; not a contract guarantee |
| Window maximum | 24 h |
| Durable/archival continuation and live follow | separate unselected profiles; finite native queries and retained Loki paging remain selected |
More columns: swipe horizontally, or focus the table and use the arrow keys.
11. Metric queries, label discovery and the unpaged binding
Recorded: 2026-10-08. These profiles close the two limits the 2026-09-09 baseline
recorded: §4.2 refuses metric expressions as log queries, and no label discovery was
selected. They are typed in the Loki-owned model
(MetricSelection, MetricResultType, LabelSelection) and declared in
the adapter specification.
| Operation | Contract | Profile | Native request |
|---|---|---|---|
logs.query_range | datasource.logs/v1alpha1 | logql-range | GET loki/api/v1/query_range |
logs.query_metric | datasource.series/v1alpha1 | logql-metric | GET loki/api/v1/query (instant) or GET loki/api/v1/query_range (range) |
logs.labels | datasource.logs/v1alpha1 | loki-labels | GET loki/api/v1/labels or GET loki/api/v1/label/<name>/values |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Paths are relative to the configured base URL. Every operation is one GET with no retry, under the shared provider bounds of §5: at most 4 MiB of provider body and the existing transport deadline. A body over that bound, a transport failure or a malformed answer is Unavailable; a deadline is Timeout. Provider 400 and 422 are InvalidInput, 401 is Unauthorized, 403 is Forbidden, 429 is RateLimited and any other non-200 status is Unavailable. No provider text, query text, origin or credential enters an error.
Every success envelope must be exactly {"status":"success","data":…}; any other
top-level member, including a warnings extension, is Unavailable (§4.3).
11.1 logql-metric
Input is an instant query {query, time_unix_ns?} or a range query
{query, start_unix_ns, end_unix_ns, step_seconds}; the two forms do not mix. query is
1–16,384 bytes. Omitted time_unix_ns is the receiver clock. Timestamps follow §4.3
(canonical decimal nanoseconds); a range requires start < end, end − start ≤ 24 h,
step_seconds in 1–86,400 and at most 11,000 points per series
((end − start) / step + 1). A violation is InvalidInput before dispatch. The range
request sends start, end and step=<n>s exactly; the instant request sends time.
data.resultType must be vector, matrix or scalar. A streams answer means the
query was a log query: InvalidInput, never log lines. Result:
{ "result_type": "matrix", "series": [ { "labels": { "app": "api" }, "samples": [ { "timestamp_unix_ns": "1788825600000000000", "value": "42" } ] } ], "complete": true, "truncation": [], "provenance": { "instance": "…", "profile": "logql-metric", "observed_at_unix_ms": 0 } }
A sample timestamp is the provider's decimal seconds converted exactly to canonical
nanoseconds; an exponent form or more than nine fractional digits is Unavailable. A
value stays the provider's string (NaN, +Inf included), never reparsed. A scalar is
one series with empty labels. Series keep provider order; samples keep provider order
within a series. Labels follow the §5 label bounds; a violation is Unavailable.
At most 500 series and 50,000 samples in total are returned. Series past 500 are
omitted with cause series_limit; samples past 50,000 are omitted with cause
sample_limit, from the first series that reaches it. complete is false exactly when a
cause is present. Loki's own series limit answers an error, not a partial result, so a
complete answer is exhaustive for the query.
11.2 loki-labels
Input {label?, start_unix_ns?, end_unix_ns?, query?}. Without label the operation
lists label names; with label it lists that label's values. label is a Loki label
name ([A-Za-z_][A-Za-z0-9_]*, at most 128 bytes) and is sent as one encoded path
segment. query is an optional stream selector of 1–16,384 bytes, forwarded unchanged.
Omitted end is the receiver clock; omitted start is end minus six hours, Loki's own
default made explicit. The window follows §4.3 and is at most 24 h.
data must be an array of strings, each at most 4 KiB; anything else is Unavailable.
Result {label, values, complete, truncation, provenance} keeps provider order and
returns at most 10,000 values; past that, cause value_limit and complete: false.
11.3 The implemented binding (2026-10-08)
The library binding in adapters/loki/src implements these three operations with this
narrower selection, which the shared logs contract admits ("an unpaged result is its
final page"):
- Unpaged.
logs.query_rangetakeslimit(1–1,000, default 1,000) as both the public page size and the provider entry cap;collection_limitandcursorare not accepted.next_cursoris always null and no observation is retained (§4.4 is not implemented). The emitted result is bounded to 4 MiB serialized; lines past it are omitted with causeresponse_bytes. - Scope. Configuration requires
query_scope.required_equalitiesand admits only the explicit empty array (§4.2: the entire configured tenant). No reviewed LogQL parser exists, so the scoped form is not advertised and a nonempty array is an invalid configuration. - Root classification without a parser. A log query sent to
logs.query_metric, or a metric query sent tologs.query_range, is recognised from the provider'sresultTypeafter dispatch and refused as InvalidInput. §4.2's pre-dispatch refusal needs the parser. - Tenant header.
X-Scope-OrgIDplacement (§4.1) needs the shared HTTP header extension, which the host HTTP port does not carry; the binding sends no tenant header. - Exhaustion proof. §4.3 requires a provider binding verified to honour the effective
limit. The binding treats a validated answer below
limitas exhausted; a deployment whose proxy lowers the limit silently is not detected. - Redaction is not applied;
redactedis always false.
The executable composition that serves this binding through connections connect and
invoke is §11.4.
11.4 The bearer connection (2026-10-08)
The executable connectors-loki (adapters/loki/src/local.rs) advertises one profile,
loki.bearer, and serves the three operations of §11.3 through the local host. Its
configuration, profile, entry and identity probe are modelled in
spec/ess/domains/connection.yaml; tests/ess_model.rs checks the executable against the
schemas and invariants that model generates.
- Profile. Scheme
http_bearer, capabilityhttp-bearer, purposeservice_account, subjectapp. The protected entry is{"token": "..."}(1–8,192 visible ASCII bytes, no spaces), sent asAuthorization: Bearer <token>. Loki grants no scopes, so the profile requires none and every operation is a read. - Identity probe. Connect, repair and revalidate send
GET /loki/api/v1/labelswith the token.200admits it;401and403refuse it as an invalid credential;429and5xxare unavailable; any other status is a protocol failure. The answer's body is not read. Validation evidence lasts 60 seconds. - Identity. Loki has no user or account read, so the identity is the configured
connection (identity source
configuration): kindloki.connection, subject the configuration'sinstance. Any token the deployment accepts is the same identity, and a repair cannot detect a token of another tenant or deployment. Use oneinstanceper deployment and tenant. - Configuration. The owner-only file
connectors-loki-local/1namesinstance(1–128 characters ofA–Z a–z 0–9 _ . -), an HTTPSbase_urlof at most 512 characters written in its canonical form (lowercasehttps://and host, a path ending in/, no credentials, query or fragment), an optional owner-only PEMca_fileat an absolute path that replaces the system roots (absent, nevernull), and the explicitquery_scopeof §11.3. Plaintext HTTP is refused. The configuration revision covers the base URL and the CA file's digest, so a changed CA refuses the cached launch. - Not served.
loki.anonymous: the local host admits only profiles with at least one entry field and a bearer, basic, mTLS or session scheme, and this binding does not widen that.loki.basicandloki.via_parentare not implemented.
12. Configured line and trace profiles
Recorded: 2026-10-14. Two reads whose LogQL the operator writes once, in the connection's
configuration, as a named profile; the caller names the profile and passes only the values
its operation declares. The profiles are typed in the
Loki-owned model (LineProfile, TraceProfile,
ProfileLinesSelection, CallTraceSelection, ProfileRecord, TraceLine) and carried by
connectors_loki.connection.LocalConfiguration; the operations are declared in
the adapter specification.
| Operation | Contract | Profile | Native request |
|---|---|---|---|
logs.profile_lines | datasource.logs/v1alpha1 | loki-line-profile | GET loki/api/v1/query_range, forward |
logs.call_trace | datasource.logs/v1alpha1 | loki-call-trace | GET loki/api/v1/query_range, forward |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Both are reads under the bearer connection of §11.4 and follow §11.3: one GET, no retry,
unpaged, at most limit (1–1,000, default 1,000) provider entries over a window the caller
states with both start_unix_ns and end_unix_ns (§4.3 timestamps, start before end, at
most 24 hours), the provider answer validated whole as §4.3 requires, at most 500 stream
groups retained and the emitted answer bounded to 4 MiB. Truncation causes and dropped totals
follow §5; complete is false exactly when provider_limit, stream_limit or
response_bytes applies. Errors are §11's. A profile name the configuration does not hold is
NotFound before dispatch; any other input the operation does not declare is InvalidInput
before dispatch.
12.1 Why a profile is not the injected filter §4.2 forbids
§4.2 governs caller-authored LogQL. There the caller's query bytes are the request, so the adapter forwards them unchanged and may not inject a filter or substitute a fixed query: the caller would no longer get what it asked for, and scope could only be proven by parsing what it wrote. A profile read takes no caller LogQL at all. The adapter composes the whole query itself from structured parts of operator-authored configuration — label matchers with a declared operator, a declared format and declared field keys — and places the caller's one value, a call identifier or a matched field value, only inside a LogQL string literal in a fixed position. There are no caller bytes to preserve, and no parser is needed to know what the query selects: the adapter built its syntax tree and renders it.
A string literal is double-quoted with ", \, newline, carriage return and tab written as
\", \\, \n, \r and \t and every other control character as \uXXXX, so no caller
text can end the literal or add a matcher, a line filter or a pipeline stage. A matcher value
in the configuration is rendered the same way.
12.2 loki-line-profile
A line profile names a stream selector (1–16 matchers, at least one a nonempty equality), a
format and 1–64 typed fields, one of which is the call identifier. The format is logfmt
(key=value, key="quoted" with the escapes of §12.1, or a bare key) or a pattern of
literal text and <name> captures (<_> discards), matched from the line's start, each
capture ending at the next literal's first occurrence and the last running to the line's end.
A field reads one key and has a type: string; integer (a signed 64-bit decimal);
phone_number (text); or unix_seconds (decimal seconds, at most nine fractional digits),
unix_millis or unix_nanos, each answered as canonical decimal nanoseconds. A field may be
optional (null when its key is absent) and filterable.
Masking is per field: none answers the value, redact answers "<redacted>", and
last_four replaces every character but the last four with * (a value of four characters
or fewer is all *). A phone_number field without a declared mask is last_four; every
other field without one is none. Only text is masked. The call identifier is the join key a
workflow reads, so it is unmasked, required string text.
Input {profile, start_unix_ns, end_unix_ns, limit?, match?}. match maps up to 16 fields
the profile declares filterable to an exact value (1–512 characters). With no match the
composed query is the selector alone. With matches it is the selector, the format's parser
stage — | logfmt, or | pattern "<the configured pattern>" — and one exact label filter
| <key>="<value>" per match, in field-name order, combining with AND: Loki answers only
lines whose parsed key is exactly the value. A match is never a substring line filter. A
filterable field's key is therefore a label name that does not begin with __ (Loki rewrites
any other key and keeps __ names for itself) and is no label of the profile's selector (a
stream label of the key's name would shadow the parsed value in the filter); a profile
breaking either is refused at configuration. The adapter then reads every answered line
itself and checks each match again, exactly and before masking.
No unmasked value reaches the answer, and that includes every count and complete. A record
carries no raw line. Nothing is counted about a line before it is known to hold every matched
value exactly, so the answer to a match on a value no line holds exactly — a prefix of a
masked number, say — is the answer to a match on a value no line holds at all, at every
limit: no records, skipped.unreadable 0, the same complete and truncation.
Result:
{ "profile": "fixture-cdr", "records": [ { "timestamp_unix_ns": "1788822090000000000", "call_id": "fx-leg-0001", "fields": { "direction": "inbound", "caller": "********0001", "call_id": "fx-leg-0001" } } ], "skipped": { "unreadable": 1 }, "selection": { "kind": "loki-profile", "start_unix_ns": "…", "end_unix_ns": "…", "direction": "forward" }, "order": "timestamp-forward", "complete": true, "truncation": { "causes": [], "occurrences_dropped": 0, "stream_groups_dropped": 0 }, "provenance": { "instance": "…", "resource": "loki:query_range", "observed_at_unix_ms": 0, "source_revision": null } }
Records are in timestamp order, ties in provider order. skipped.unreadable counts the
answered lines that do not read as the profile — the format does not match, a required key is
absent, a value is not its type or is over 4 KiB — and, with matches, only those whose format
reads and whose matched keys hold every value exactly. An answered line that does not hold
every matched value exactly on the adapter's reading, or whose format does not read at all
when there are matches, is dropped and counted nowhere: Loki's exact filter should not have
answered it, and counting it would tell the caller about a value it did not name. None of
these is an omitted occurrence. complete, the truncation causes and the dropped totals
describe Loki's answer to the exact query, so they too reflect only lines holding the
caller's exact values.
12.3 loki-call-trace
A trace profile names a stream selector (as §12.2), where the call identifier is carried —
a stream label, or a key of the line read with a declared format — and the label that names
each line's source. Input is {profile, call_id, start_unix_ns, end_unix_ns, limit?} and
nothing else; call_id is 1–512 characters of any text. The composed query is the profile's
matchers, then <source_label>=~".+", then for a label-carried identifier
<label>="<call_id>"; for a line-carried identifier it is followed by the format's parser
stage and the exact label filter | <key>="<call_id>", as §12.2 composes a match, never a
substring line filter. The key is a label name not beginning with __, and neither the
source label nor a label of the selector; a profile breaking that is refused at
configuration.
The answer is the call's lines, oldest first with ties in provider order, each with its
source, its text clipped to 8 KiB (line_truncated, cause line_bytes) and its timestamp:
{ "profile": "fixture-by-label", "call_id": "fx-leg-0001", "lines": [ { "timestamp_unix_ns": "1788822000100000000", "source": "edge-proxy", "line": "INVITE received", "line_truncated": false } ], "selection": { "kind": "loki-call-trace", "start_unix_ns": "…", "end_unix_ns": "…", "direction": "forward" }, "order": "timestamp-forward", "complete": true, "truncation": { "causes": [], "occurrences_dropped": 0, "stream_groups_dropped": 0 }, "provenance": { "instance": "…", "resource": "loki:query_range", "observed_at_unix_ms": 0, "source_revision": null } }
No line of another call is answered. A retained stream without a nonempty source label, or a
label-carried answer whose stream names another call identifier, is not Loki's answer to the
composed equality and is Unavailable, never mixed in. For a line-carried identifier the
adapter reads every answered line itself: a line is answered only when it reads in the
declared format and its key is exactly the identifier. Any other line Loki answered — which
its exact filter should not have — is dropped and counted nowhere, so a trace of an
identifier no call has answers exactly as a trace of one no line holds anywhere: no lines,
the same complete and truncation, nothing about other calls' lines. No unmasked value of
another call reaches the answer, counts and complete included.
The adapter answers only lines on which Loki's parser and its own reader agree, and no
substring pre-filter stands between them: whatever line Loki's parser extracts with the key
exactly the value is fetched, and the reader then decides. The reader reads a pattern as
Loki's pattern parser does (from the line's start, each capture to the next literal's first
occurrence) and a logfmt value unquoted with the escapes of §12.1. Where Loki's parser is more
lenient than the reader on a malformed line, Loki may answer it and the adapter drops it as
above; where the two read one line differently, the line is not answered, and that
disagreement is the limit of what complete can promise. A stream label
outside the selector that has the matched key's name shadows the parsed value in Loki's
filter; naming such a key is the operator's error, and the reader's own check keeps any line
it lets through out of the answer. One invocation reads one connection; merging the traces of
several connections is the workflow's time-ordered join.
12.4 A profile never weakens the configured scope
Every equality of query_scope.required_equalities must appear among each profile's matchers
as an exact = with the same label and value; a profile without one is refused at
configuration, before any read. The adapter only adds to what a profile selects: matchers of
a selector all hold of every stream it selects, so the source-label matcher, the call
equality, the parser stage and every exact label filter narrow and never widen. A trace profile's call label may not
appear in its selector or be its source label, so the composed equality is the only matcher on
it. The configuration still admits only the empty scope (§11.3), because the native reads of
§11 cannot enforce a nonempty one; the profile rule is enforced and tested
(tests/profiles.rs, a_profile_cannot_widen_the_configured_stream_scope) so that it already
holds when a scope is admitted.
12.5 Configuration
line_profiles and trace_profiles are optional members of the configuration file of §11.4:
absent, or a list of 1–16 profiles with distinct names ([a-z0-9_.-], 1–64 characters);
never null. Field names are distinct within a profile ([a-z0-9_], 1–64 characters); label
names are Loki's ([A-Za-z_][A-Za-z0-9_]*, at most 128 bytes). A pattern needs at least one
named capture, no two adjacent captures and no capture named twice, and a pattern profile's
keys are its captures. A matched key — a filterable field's, or a trace profile's
line-carried call identifier's — is a label name not beginning with __ and no selector
label (for a trace profile, nor its source label); any other key is only read and may be any
1–128 characters. A configured profile is part of the effective configuration and so of
its revision: changing one refuses a recorded launch, as a changed CA does. A configuration
without profiles keeps the revision it had before profiles existed. The repository holds the
profile shape, its validation and synthetic fixtures only; the selectors, keys, formats and
masking of a real deployment are the operator's, in that deployment's configuration.