Skip to main content

Send DynamoGuard Moderation Logs To A SIEM

The DynamoGuard API writes every logged moderation decision to standard output as one JSON line. Collect the API container's output with the log collector you already run, keep the lines where message.logType is moderationLogs, and forward them to your SIEM. DynamoAI does not ship a SIEM connector, and the DynamoAI chart does not install a log collector.

Scope

Applies to self-managed deployments on release 3.26. For history from before your collector started, or when no collector can run on the cluster, pull moderation logs from the API.

Prerequisites​

  • A log collector that reads Kubernetes container logs on the DynamoGuard cluster, such as the Splunk Distribution of the OpenTelemetry Collector, Fluent Bit, or your cloud provider's container log agent
  • Network access from the collector to your SIEM's ingestion endpoint. If the cluster restricts egress, allow that host and port.
  • Access to the DynamoAI Helm values, if you change the redaction setting in step 1

1. Decide Whether Prompt Text May Leave The Cluster​

Each moderation line carries the analyzed text and the guardrail outputs unless SENSITIVE_USER_REQUEST_DATA_REDACTION_IN_LOGS is "true" on the API.

warning

The default is "false". Prompts, model responses, and guardrail outputs reach your SIEM in plain text. Field encryption of moderation logs in the database does not apply to this line.

SettingReplaced with [REDACTED]Kept
"true"analyses[].text, analyses[].error, analyses[].appliedPolicies[].outputs, and the text fields of chatUser and AI system IDs, text types, actions, policy IDs, metadata, multiturnMetadata

The setting also redacts the messages, content, text, and ragContext fields of request bodies in the API's request log lines. It does not redact metadata or multiturnMetadata. Keep sensitive data out of request metadata.

Set it in the API environment through your DynamoAI values, then apply with helm upgrade:

# dynamoai chart
api:
env:
SENSITIVE_USER_REQUEST_DATA_REDACTION_IN_LOGS:
value: "true"
# dynamoai-base chart (legacy three-chart layout)
dynamoai-api:
dynamoai-common:
deployment:
env:
SENSITIVE_USER_REQUEST_DATA_REDACTION_IN_LOGS: {value: "true"}

Verify the running value:

kubectl get deployment API_DEPLOYMENT -n DYNAMOAI_NAMESPACE \
-o jsonpath='{.spec.template.spec.containers[*].env[?(@.name=="SENSITIVE_USER_REQUEST_DATA_REDACTION_IN_LOGS")].value}'

2. Collect The API Container's Standard Output​

Point your collector at the pods of the DynamoAI API deployment. Only the API writes moderation lines.

Check that the lines are there:

kubectl logs deployment/API_DEPLOYMENT -n DYNAMOAI_NAMESPACE --tail=1000 | grep '"logType":"moderationLogs"'

No output means no request was logged in that range. Send one with step 5.

3. Keep Only Moderation Lines​

Keep the lines where message.logType equals moderationLogs. The record is under message.log; see Moderation Log Record for every field. The API writes compact JSON. A match on the string "logType":"moderationLogs" also works before your collector parses the line.

4. Take The Timestamp From The Collector​

The line's own timestamp field is DD-HH:mm:ss.SSS: day of month and time, with no month, year, or time zone. Use the timestamp the container runtime writes with each line. Collectors that read container log files, including the OpenTelemetry Collector's container parser, set the event time from it.

5. Verify With One Request​

  1. Send an analyze request and note the X-Request-Id response header:

    curl -sS -D - -o /dev/null \
    -H "Authorization: Bearer TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"messages":[{"role":"user","content":"Test message"}],"textType":"MODEL_INPUT","modelId":"AI_SYSTEM_ID"}' \
    https://API_HOST/v1/moderation/analyze | grep -i '^x-request-id'
  2. In your SIEM, find the event whose metadata.reqId equals that value. Its message.logType is moderationLogs.

A request that arrives with its own X-Request-Id header keeps that value in metadata.reqId. A caller can set the join key itself and match its own records against the moderation line. Trust a caller-supplied value only where you control every caller.

Example: Splunk​

The Splunk Distribution of the OpenTelemetry Collector for Kubernetes reads container logs and sends them to Splunk HTTP Event Collector (HEC).

Dedicated Collector Release​

Create values.yaml:

clusterName: CLUSTER_NAME
splunkPlatform:
endpoint: https://SPLUNK_HEC_HOST:8088/services/collector
index: SPLUNK_INDEX
secret:
create: false
name: SPLUNK_HEC_SECRET
logsCollection:
containers:
useSplunkIncludeAnnotation: true
extraOperators:
- type: filter
expr: 'not (body matches "\"logType\":\"moderationLogs\"")'
clusterReceiver:
enabled: false
  • useSplunkIncludeAnnotation: true collects only pods and namespaces annotated splunk.com/include: "true".
  • The filter operator drops every container log line that does not match.
  • clusterReceiver.enabled: false turns off the chart's cluster receiver. Left on, it sends Kubernetes events and object snapshots, including full Pod specs, to the same index. With both settings, this release sends only moderation lines.
  • secret.create: false with secret.name points the release at a Kubernetes secret you already manage. The chart reads the token from the key splunk_platform_hec_token in that secret, and its startup check fails the collector when the key is missing. For a throwaway test, set splunkPlatform.token in the values file instead. The chart then creates the secret itself.
  • If HEC uses a certificate from a private CA, provide that CA with splunkPlatform.caFile.

Install the release and include the DynamoAI namespace:

helm repo add splunk-otel-collector-chart https://signalfx.github.io/splunk-otel-collector-chart
helm install dynamoguard-moderation-logs splunk-otel-collector-chart/splunk-otel-collector \
-n SPLUNK_COLLECTOR_NAMESPACE --create-namespace -f values.yaml
kubectl annotate namespace DYNAMOAI_NAMESPACE splunk.com/include=true

Existing Collector Release​

If you already run this chart for other logs, do not add the filter operator to it: the filter applies to every pod that release collects. Route the DynamoAI namespace to its own index instead, and select the moderation lines at search time:

kubectl annotate namespace DYNAMOAI_NAMESPACE splunk.com/index=SPLUNK_INDEX

Search The Moderation Lines​

index=SPLUNK_INDEX "moderationLogs"
| spath output=logType path=message.logType
| search logType=moderationLogs
| spath output=user path=message.log.source.user
| spath output=aiSystem path=message.log.source.model
| spath output=finalAction path=message.log.analyses{}.finalAction
| table _time user aiSystem finalAction

finalAction is multivalue on requests that carry two analyses, such as chat traffic and MODEL_RESPONSE calls. The request's decision is the most severe of those values.

Behavior That Affects Completeness​

  • Callers can opt out per request. On POST /v1/moderation/analyze, loggingControl.enabled: false suppresses both the line and the database record. loggingControl.forceLogOnFinalAction lists final actions that are logged anyway, for example ["BLOCK"]. Chat and streaming requests always log.
  • Failed analyses are not logged. When a guardrail call fails, the request returns an error and writes no line and no database record.
  • The line comes before the database write. If the save fails, the caller receives an error, but the line is already written.