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:
| Field | Meaning |
|---|---|
log.type | Always audit |
event_time | Event timestamp |
request_id | Request-scoped correlation ID |
source | Surface that emitted the event, such as http, mcp, or binding:... |
provider | Plugin key for provider-scoped events |
operation | Operation or event name |
depth | Invocation depth for guarded invocation events |
allowed | Whether gestaltd allowed or completed the audited action |
These fields are emitted when available:
| Field | Meaning |
|---|---|
auth_source | Authentication mechanism for the caller: session, api_token, or env. |
subject_id | Canonical caller subject such as user:<id> or service_account:<id> |
subject_kind | Canonical subject type, such as user or service_account |
target_kind | Generic kind for the object the event acted on, such as api_token or connection |
target_id | Stable identifier for the affected object when one exists |
target_name | Human-readable label for the affected object |
error | Denial or failure reason |
client_ip | Client IP, preferring X-Forwarded-For |
remote_addr | Immediate peer address |
user_agent | HTTP user agent |
trace_id | Active OpenTelemetry trace ID |
span_id | Active 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.authenticateauth.login.startauth.login.completeauth.logoutconnection.oauth.startconnection.oauth.completeconnection.manual.connectconnection.pending.selectconnection.disconnectapi_token.listoperations.listapi_token.createapi_token.revokeapi_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.
| Surface | Metrics | Audit Logs | Notes |
|---|---|---|---|
| Provider operation invocation | Yes: gestaltd.operation.* | Yes | Guarded invocations are both metered and audited. |
| Platform login start and completion | Yes: gestaltd.auth.* | Yes | Login is both operational telemetry and a security event. |
| Platform token validation | Yes: gestaltd.auth.* with action=validate_token | Partially | Successful per-request validation is metrics-only; denied auth in shared middleware emits auth.authenticate. |
| HTTP pre-invoker authorization denials | No dedicated semantic metric family | Yes | Denied subject access before invocation dispatch is audited with the attempted operation or operations.list. |
| Connection auth start and completion | Yes: gestaltd.connection.auth.* | Yes | Covers OAuth start and completion plus manual connect completion. |
| Connection credential refresh | Yes: gestaltd.connection.auth.* with action=refresh | No | Refresh remains metrics-only to avoid audit noise. |
| Credentialed HTTP catalog discovery | Yes: gestaltd.discovery.* with action=list_operations | No | Operation listing that resolves a session catalog stays metrics-only to avoid audit spam. |
| IndexedDB operations | Yes: db.client.operation.duration | No | Datastore calls are internal implementation telemetry, not audit events. |
| API token inventory read | No dedicated semantic metric family | Yes | api_token.list is audited because it exposes stored API-token inventory. |
| API token lifecycle | No dedicated semantic metric family | Yes | Audit records exist for explicit token create/revoke flows and CLI token minting. |
| Logout, pending selection, disconnect | No dedicated semantic metric family | Yes | These 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.