Forward audit logs to your SIEM
The AI Gateway emits structured JSON audit events to pod standard output for request admission, detection decisions, virtual key changes, and authentication outcomes.
Every event carries the fields required by NIST SP 800-53 controls AU-2, AU-3, and AU-8: a unique audit ID, a UTC timestamp, the subject, the component, the outcome, an optional target, and an extra block for forensic correlation.
Enable audit events
Emission is off by default and is enabled per gateway:
apiVersion: ai-gateway.stacklok.dev/v1alpha1
kind: AIGateway
metadata:
name: ai-gateway
spec:
audit:
enabled: true
Enabling audit events captures every supported event without sampling. Filter and route events in your log pipeline.
What gets captured
| Event type | Fires when | Outcome |
|---|---|---|
aigw.processor.request.blocked | A detection blocks a request | denied |
aigw.processor.request.redacted | A request body was masked before forwarding | success |
aigw.processor.response.blocked | Response masking failed, so the client got a synthetic error | denied |
aigw.processor.response.redacted | A response body was masked before delivery | success |
aigw.processor.detection.logged | A passive detector fired, nothing mutated | success |
aigw.processor.body.truncated | A request or response body exceeded the size limit | failure |
aigw.processor.config.reloaded | Configuration was reloaded | success/failure |
aigw.apikey.key.created | A virtual key was created | success/error |
aigw.apikey.key.rotated | A virtual key was rotated | success/error |
aigw.apikey.key.revoked | A virtual key was revoked | success/error |
aigw.apikey.key.updated | A key was enabled, disabled, or edited | success/error |
aigw.apikey.authn.failed | Token validation failed on an API call | failure |
aigw.apikey.authz.denied | The caller lacked the required role | denied |
aigw.apikey.validation.failed | An invalid or unknown virtual key was presented | failure |
aigw.apikey.ratelimit.exceeded | A per-key request rate limit was exceeded | denied |
Event types use aigw.<component>.<resource>.<verb>.
Event schema
{
"audit_id": "b3d4f7a2-8c11-4f6e-9b0a-1c2d3e4f5a6b",
"type": "aigw.processor.request.blocked",
"logged_at": "2026-04-20T14:22:31.418Z",
"outcome": "denied",
"component": "main-processor",
"source": { "type": "network", "value": "10.42.0.17" },
"subjects": { "user": "alice", "virtual_key_id": "a7Hj3kLm" },
"target": {
"type": "llm_request",
"model": "gpt-4o",
"provider": "openai",
"route": "chat-completions"
},
"metadata": {
"extra": {
"request_id": "c0ffee01-2a3b-4c5d-6e7f-8091a2b3c4d5",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"client_ip": "203.0.113.42",
"pod": "ai-gateway-main-processor-7f6b9c-x2k4q",
"detection_types": ["CREDIT_CARD", "US_SSN"],
"action": "block",
"body_bytes": 1842
}
}
}
Subjects come from authenticated tokens on API events or a trusted upstream
filter on request-path events. The gateway adds virtual_key_id after key
validation.
Targets use {type: llm_request, model, provider, route} on request events
and {type: virtual_api_key, key_id, prefix} on key events. Key events omit
secret material. Match target.provider against these lowercase values:
openai, anthropic, awsbedrock, azureopenai, gcpvertexai, and
geminiaistudio.
Common extras are request_id, trace_id and span_id for OpenTelemetry
correlation, client_ip, user_agent, pod, and the gateway's name and
namespace.
Detection events add detection_types and action. action is block,
redact, or log-only. Passive detections use shadow_types for detectors in
shadow mode and log_only_types for log-only policy.
Request-path events carry body_bytes; response-path events carry
chunk_bytes, which is per-chunk for streaming responses and must not be summed
with body_bytes to estimate traffic.
Separate audit records from application logs
Each event is one line of JSON on the pod's standard output. Every audit record
carries "logger":"audit" and "msg":"audit_event", so shippers should filter
on that pair to separate audit records from ordinary application logs on the
same stream.
Ship them
Each example below filters on those same two keys. Adapt hostnames, credentials,
and <NAMESPACE> for your environment.
Fluent Bit to Splunk
[INPUT]
Name tail
Path /var/log/containers/*<NAMESPACE>*.log
Parser docker
Tag kube.audit.*
Refresh_Interval 5
[FILTER]
Name grep
Match kube.audit.*
Regex log .*"logger":"audit".*"msg":"audit_event".*
[FILTER]
Name parser
Match kube.audit.*
Key_Name log
Parser json
Reserve_Data On
[OUTPUT]
Name splunk
Match kube.audit.*
Host <SPLUNK_HEC_HOST>
Port 8088
Splunk_Token ${SPLUNK_HEC_TOKEN}
TLS On
event_source aigw-audit
event_index aigw_audit
Fluent Bit to Elastic
Keep the input and both filters from the Splunk example above, which tag records
kube.audit.*, and swap the output. The Match value has to be that same tag,
or the output receives nothing:
[OUTPUT]
Name es
Match kube.audit.*
Host <ELASTICSEARCH_HOST>
Port 9200
HTTP_User ${ES_USER}
HTTP_Passwd ${ES_PASSWORD}
tls On
Logstash_Format On
Logstash_Prefix aigw-audit
Replace_Dots On
Vector to Kafka
sources:
kubernetes_logs:
type: kubernetes_logs
extra_namespace_label_selector: 'kubernetes.io/metadata.name=<NAMESPACE>'
transforms:
parse_json:
type: remap
inputs: [kubernetes_logs]
source: |
. = parse_json!(.message)
if .logger != "audit" || .msg != "audit_event" { abort }
sinks:
kafka:
type: kafka
inputs: [parse_json]
bootstrap_servers: '<KAFKA_BROKERS>'
topic: aigw-audit
encoding:
codec: json
compression: snappy
Promtail to Loki
scrape_configs:
- job_name: aigw-audit
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
regex: <NAMESPACE>
action: keep
pipeline_stages:
- json:
expressions:
logger: logger
msg: msg
type: type
outcome: outcome
component: component
- match:
selector: '{logger!="audit"}'
action: drop
- labels:
type:
outcome:
component:
CloudWatch and rsyslog
The CloudWatch agent cannot filter on a JSON key natively. Either ingest everything and apply a log-group metric filter:
{ $.logger = "audit" && $.msg = "audit_event" }
or run Fluent Bit alongside it with the cloudwatch_logs output, using the
filters above so only audit lines reach the dedicated log group.
For rsyslog, match both markers before forwarding:
if $msg contains '"logger":"audit"' and $msg contains '"msg":"audit_event"' then {
action(type="omfwd"
target="<SIEM_HOST>"
port="514"
protocol="tcp"
template="RSYSLOG_SyslogProtocol23Format")
stop
}
Retention, tamper evidence, and GDPR
Configure retention, tamper evidence, and privacy controls in your ingest and storage systems. Use lifecycle policies and write-once storage where your compliance requirements call for them.
Apply field-level redaction before events reach long-term storage. The gateway does not retain or replay emitted audit events.
Related logs
The Kubernetes API audit log covers control-plane activity such as kubectl
calls and operator reconciles. Configure it through the API server audit policy.
Interaction journaling, configured under spec.journaling, captures prompt
and response content. Audit events capture decisions and reasons without the
full interaction content.
Next steps
- PCI/PII controls to configure the detections these events report.
- Screen prompts for injection, whose verdicts also land in this stream.