Skip to Content
Audit Logging

Audit Logging

gestaltd emits audit records as structured logs with log.type=audit.

Audit logging covers both invocation traffic and security-sensitive management actions, so you can forward one stream to a SIEM, warehouse, or compliance backend without having to reconstruct behavior from mixed application logs.

Coverage

Audit events cover HTTP and MCP operation invocations, other guarded runtime surfaces such as binding hooks, failed auth on protected routes, HTTP authorization denials rejected before GuardedInvoker runs, login and logout flows, plugin connect and disconnect flows, and API token inventory and lifecycle events. Each event carries a source field (http, mcp, or a surface-specific value like binding:...) so consumers can filter by surface.

gestaltd resolves the authenticated canonical subject before emitting user-backed audit events, so both session requests and API-token requests produce stable subject_id values such as user:usr_123. Emitted audit records do not include a separate user_id field.

Fields

Every audit record includes:

FieldMeaning
log.typeAlways audit
event_timeEvent timestamp
request_idRequest-scoped correlation ID
sourceSurface that emitted the event, such as http, mcp, or binding:...
providerPlugin key for provider-scoped events
operationOperation or event name
depthInvocation depth for guarded invocation events
allowedWhether gestaltd allowed or completed the audited action

These fields are emitted when available:

FieldMeaning
auth_sourceAuthentication mechanism for the caller: session, api_token, or env.
subject_idCanonical caller subject such as user:<id> or service_account:<id>
subject_kindCanonical subject type, such as user or service_account
target_kindGeneric kind for the object the event acted on, such as api_token or connection
target_idStable identifier for the affected object when one exists
target_nameHuman-readable label for the affected object
errorDenial or failure reason
client_ipClient IP, preferring X-Forwarded-For
remote_addrImmediate peer address
user_agentHTTP user agent
trace_idActive OpenTelemetry trace ID
span_idActive OpenTelemetry span ID

For platform-level events such as failed auth or API-token inventory and lifecycle, provider is empty because the event is not tied to a specific plugin.

Management events populate the generic target fields when they can identify a single object. API-token create and revoke events use target_kind=api_token with the token ID and, for creates, the token name. Connection lifecycle events use target_kind=connection with target_id and target_name derived from the provider, connection, and instance being changed. Bulk revocations use target_kind=api_token_collection because no single token ID applies.

Event Names

Invocation events use the catalog operation ID in operation.

Non-invocation audit events use explicit operation names:

  • auth.authenticate
  • auth.login.start
  • auth.login.complete
  • auth.logout
  • connection.oauth.start
  • connection.oauth.complete
  • connection.manual.connect
  • connection.pending.select
  • connection.disconnect
  • api_token.list
  • operations.list
  • api_token.create
  • api_token.revoke
  • api_token.revoke_all

Denied requests to /api/v1/{integration}/{operation} that are blocked before GuardedInvoker still use the attempted catalog operation ID in operation, matching the guarded invocation audit shape. api_token.create is also emitted when the CLI login callback successfully mints a token. auth.authenticate can be emitted from both HTTP and MCP request paths when shared auth middleware rejects the request before invocation.

Metrics Vs Audit

gestaltd intentionally does not mirror every metric as an audit record.

SurfaceMetricsAudit LogsNotes
Provider operation invocationYes: gestaltd.operation.*YesGuarded invocations are both metered and audited.
Platform login start and completionYes: gestaltd.auth.*YesLogin is both operational telemetry and a security event.
Platform token validationYes: gestaltd.auth.* with action=validate_tokenPartiallySuccessful per-request validation is metrics-only; denied auth in shared middleware emits auth.authenticate.
HTTP pre-invoker authorization denialsNo dedicated semantic metric familyYesDenied subject access before invocation dispatch is audited with the attempted operation or operations.list.
Connection auth start and completionYes: gestaltd.connection.auth.*YesCovers OAuth start and completion plus manual connect completion.
Connection credential refreshYes: gestaltd.connection.auth.* with action=refreshNoRefresh remains metrics-only to avoid audit noise.
Credentialed HTTP catalog discoveryYes: gestaltd.discovery.* with action=list_operationsNoOperation listing that resolves a session catalog stays metrics-only to avoid audit spam.
IndexedDB operationsYes: db.client.operation.durationNoDatastore calls are internal implementation telemetry, not audit events.
API token inventory readNo dedicated semantic metric familyYesapi_token.list is audited because it exposes stored API-token inventory.
API token lifecycleNo dedicated semantic metric familyYesAudit records exist for explicit token create/revoke flows and CLI token minting.
Logout, pending selection, disconnectNo dedicated semantic metric familyYesThese remain audit-only workflow events.

See Observability for the metric families and attribute definitions.

Example

{ "log.type": "audit", "event_time": "2026-04-06T18:23:11.842Z", "request_id": "2a7c1d7f-2b25-4b7a-8ae8-02f4d8f6f2b2", "source": "mcp", "auth_source": "api_token", "subject_id": "user:usr_123", "subject_kind": "user", "provider": "github", "operation": "issues_list", "depth": 0, "allowed": true, "trace_id": "0af7651916cd43dd8448eb211c80319c", "span_id": "b7ad6b7169203331" }

Routing

By default, audit records follow the main telemetry log pipeline. With providers.telemetry.default.source: stdout they appear in local structured logs; with providers.telemetry.default.source: otlp they export through the OTLP log pipeline.

If you need a dedicated audit route, configure providers.audit separately. Set providers.audit.<name>.source: stdout to keep audit logs on stdout even when telemetry uses a different source, providers.audit.<name>.source: otlp to export only audit records to a dedicated OTLP collector, or providers.audit.<name>.source: noop to disable audit output explicitly.

With the default providers.audit.default.source: inherit, you can still split audit records downstream by filtering on log.type=audit in your collector. See Config File for the config surface and Observability for export options.

Metrics Notes

Audit logs are a good source for invocation-based activity metrics. In particular, source, auth_source, subject_id, provider, operation, and allowed are enough to derive distinct-user activity over guarded invocations.

For DAU, WAU, or MAU built from tool usage, filter to successful top-level invocations where allowed = true, depth = 0, and subject_id is non-empty. That produces a clean “subjects who actually invoked a tool” metric. If you need a broader product-activity definition, treat the non-invocation audit events as a separate metric family rather than mixing them into invocation counts.