Reference

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
      }
    }
  }
}
ParameterTypeDefaultDescription
skipInt!0Number of records to skip (offset pagination). A negative value reads as 0. Ignored when afterId is provided.
takeInt!25Number of records to return, from 1 to 500. See Page size
metadataIdLongnullFilter to logs for a single execution
minimumLevelLogLevelnullIncludes the supplied level and anything more severe. LogLevel follows Microsoft.Extensions.Logging: TRACE, DEBUG, INFORMATION, WARNING, ERROR, CRITICAL, NONE
categoryStringnullExact-match filter on the logger category (e.g. MyApp.Trains.Billing.ChargeCustomerTrain)
afterIdLongnullKeyset cursor. Returns records with id < afterId, or id > afterId when order: OLDEST
messageContainsStringnullOnly records whose message contains this text anywhere, ignoring case. %, _ and \ match themselves
categoryContainsStringnullOnly records whose logger category contains this text, matched as messageContains is
orderSortOrder!NEWESTNEWEST (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

FieldTypeDescription
idLong!Database ID (monotonic, used as the keyset cursor)
metadataIdLong!The execution this log line belongs to
eventIdInt!EventId from the ILogger call site
levelLogLevel!Severity
categoryString!Logger category, typically the originating type name
messageString!Truncated to 4000 chars at write time
exceptionStringException message if any (truncated to 2000 chars)
stackTraceStringStack trace if any (truncated to 4000 chars)

See also