operations.logs queries
Package Trax.Api.GraphQLSurface GraphQL schemaSource LogQueries.cs
LogQueries, the type behind operations { logs { ... } }.
The operations.logs namespace returns paginated reads of trax.log, the framework's per-execution log table. The dashboard's Logs page is backed by this query. Logs are written by the framework, never by API consumers, so there are no log mutations.
query {
operations {
logs {
logs(skip: 0, take: 50, minimumLevel: WARNING) {
items { id metadataId eventId level category message exception stackTrace }
totalCount
isEstimatedCount
nextCursor
}
}
}
}A run's log, oldest first as its page reads it, filtered by text:
query {
operations {
logs {
logs(metadataId: 1234, order: OLDEST, messageContains: "timeout", take: 100) {
items { id level category message }
totalCount
isCountCapped
nextCursor
}
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
skip | Int! | 0 | Number of records to skip (offset pagination). A negative value reads as 0. Ignored when afterId is provided. |
take | Int! | 25 | Number of records to return, from 1 to 500. See Page size |
metadataId | Long | null | Filter to logs for a single execution |
minimumLevel | LogLevel | null | Includes the supplied level and anything more severe. LogLevel follows Microsoft.Extensions.Logging: TRACE, DEBUG, INFORMATION, WARNING, ERROR, CRITICAL, NONE |
category | String | null | Exact-match filter on the logger category (e.g. MyApp.Trains.Billing.ChargeCustomerTrain) |
afterId | Long | null | Keyset cursor. Returns records with id < afterId, or id > afterId when order: OLDEST |
messageContains | String | null | Only records whose message contains this text anywhere, ignoring case. %, _ and \ match themselves |
categoryContains | String | null | Only records whose logger category contains this text, matched as messageContains is |
order | SortOrder! | NEWEST | NEWEST (id descending) or OLDEST (id ascending, as a run's log reads). afterId pages in the chosen direction |
Returns: PagedResult<LogEntry>.
The filters, the order and the counts go through IOperationsService in Trax.Scheduler, the same
LogQuery the dashboard's Logs and run pages read with, so both apply one filter.
The count depends on the filter. Unfiltered, it is the same pg_class.reltuples estimate as the
other large-table queries (isEstimatedCount: true), because the log table grows quickly. With
messageContains or categoryContains it counts at most 10,000 matches: past that totalCount is
10000 and isCountCapped is true, so a client shows "10,000+" rather than waiting on a count of
millions of rows. Under any other filter, or a text filter matching 10,000 or fewer, it is exact.
On Postgres the text filters are served by trigram indexes on lower(message) and
lower(category). At 3,000,000 log rows a page with its count takes a few milliseconds for a term
that matches nothing, under 70 ms for one that matches often, and under 200 ms for one that matches a
single row. A term that matches often but only in old rows takes under 50 ms newest first: a first
or cursor page with a text filter reads the 10,000 ids nearest where it starts in order, then finds
the rest of its matches through the index, so it does not walk every newer row.
LogEntry fields
| Field | Type | Description |
|---|---|---|
id | Long! | Database ID (monotonic, used as the keyset cursor) |
metadataId | Long! | The execution this log line belongs to |
eventId | Int! | EventId from the ILogger call site |
level | LogLevel! | Severity |
category | String! | Logger category, typically the originating type name |
message | String! | Truncated to 4000 chars at write time |
exception | String | Exception message if any (truncated to 2000 chars) |
stackTrace | String | Stack trace if any (truncated to 4000 chars) |