
I Tried Collecting Claude Code Event Logs Using OpenTelemetry
This page has been translated by machine translation. View original
Introduction
When using Claude Code, there are times when I want to record the execution history of the Bash tool, which is invoked in large numbers, so that I can review it later.
For example, even if you specify permissions.deny in settings.json, some commands may slip through unspecified.
Also, I have set up hooks.PreToolUse so that Bash tool executions containing a pipe (|) are confirmed, but I feel that this process has become a mere formality and that things may be overlooked.
If there were an audit log that lets you verify whether Claude Code is behaving in ways the user does not want, you could check for gaps in permissions.deny, and even if Claude Code were compromised, it would help investigate the scope of the impact.
Considering security risks such as supply chain attacks, having an audit log is also important from a self-defense perspective.
Now, Claude Code supports metrics and event log output using OpenTelemetry.
This time, I set up a pipeline using OpenTelemetry and was able to easily record Claude Code's event logs, so I will summarize the details here.
Since this is my first time building OpenTelemetry infrastructure, I will also summarize OpenTelemetry itself as I go.
What is OpenTelemetry
OpenTelemetry (hereafter OTel) is a framework and toolkit for achieving application observability.
It defines standard specifications for handling three kinds of application data: Traces, Metrics, and Logs.
OTel provides specifications, SDKs, and reference implementations of tools so that this data can be collected and forwarded in a unified format.
For details, see the official documentation https://opentelemetry.io/ja/.
Components of an OpenTelemetry pipeline
A pipeline using OTel has the following components.
Instrumentation
- This is the entity embedded in the application side that generates data such as logs from the application's behavior
- In this case, it is the OTel SDK built into Claude Code
Components (Collector)
- These are the components of the pipeline that process data sent from the SDK; the following two are relevant this time
- Receiver: receives data
- Exporter: outputs data
- In this case, they are included in the Collector (the pipeline processing proxy provided by OTel)
Backend
- This is the external service that ultimately stores and visualizes data; Grafana Loki and Prometheus are well known
- This time I will not prepare a Backend service; instead, I will make the Collector's output destination a file and use viewing and searching that file as a substitute for a Backend
Architecture diagram
This time, I will collect Claude Code's event logs with the following configuration and test whether it functions as an audit log.
ホスト (macOS)
┌─────────────────────────────────────────────────────────────────────────┐
│ │
│ [Instrumentation] Claude Code セッション群 │
│ │ │
│ │ OTLP/gRPC (localhost:4317) │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ [Components] Podman コンテナ (opentelemetry-collector-contrib) │ │
│ │ │ │
│ │ ┌─────────────────`┐ ┌───────────────────────┐ │ │
│ │ │ Receiver │ ──────────────▶ │ Exporter │ │ │
│ │ │ (otlp/grpc:4317) │ │ (file: /data/*.jsonl) │ │ │
│ │ └──────────────────┘ └───────────────────────┘ │ │
│ │ │ │ │
│ │ /data/audit.jsonl │ │
│ └───────────────────────────────────────────────────┼───────────────┘ │
│ │ バインドマウント │
│ [Backend] ~/claude-audit-logs/audit.jsonl │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Prerequisites
- macOS (M2 Mac)
- I am referring to the Claude Code Docs as of May 17, 2026
- I am using Claude Code
2.1.133
Practice
Enable log output in Claude Code
Set the required environment variables for Claude Code. To enable it for all Claude Code sessions, set the following variables in ~/.claude/settings.json.
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317",
"OTEL_LOG_TOOL_DETAILS": "1"
}
CLAUDE_CODE_ENABLE_TELEMETRYenables sending logs and metrics via OTel- For the purpose of recording Bash tool events, setting only
OTEL_LOGS_EXPORTERis sufficient, but since I want to check metrics in the future as well, I have also enabledOTEL_METRICS_EXPORTER- Specify
otlp(OpenTelemetry Protocol) as the Exporter
- Specify
- Because
grpcis specified forOTEL_EXPORTER_OTLP_PROTOCOL, port4317needs to be forwarded in the Collector container to be created later - To keep the Bash tool's execution history this time, I enable
OTEL_LOG_TOOL_DETAILS- This records the arguments passed to the Bash tool, that is, the commands being executed
Prepare the pipeline
Prepare a pipeline that receives and records the logs output by Claude Code.
I will prepare an opentelemetry-collector-contrib container as the Collector.
There are the following two kinds of images for the Collector, but this time I will use opentelemetry-collector-contrib, which bundles the fileexporter that performs file output.
- opentelemetry-collector: the official image, containing only core features
- opentelemetry-collector-contrib: the community extended image, with abundant extensions and additional components
Prepare the configuration file
Prepare the Collector configuration.
First, here are the contents of the local directory ~/claude-audit-logs/ that will be the bind mount source for the container.
$ tree ~/claude-audit-logs/
/Users/xxx/claude-audit-logs/
└── config.yaml
1 directory, 1 file
audit.jsonl will be added after the container starts.
I used the following for config.yaml.
# Data receiving settings
# Supports both grpc and http
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# How to output the received data
exporters:
file:
path: /data/audit.jsonl
rotation:
max_megabytes: 100 # Log rotation threshold (default: 100)
localtime: true
format: json
# Extension settings
# This time, enable the health check extension
extensions:
health_check:
endpoint: 0.0.0.0:13133
# Settings for services that run on the Collector
# Specify which of the features configured so far to start
service:
extensions: [health_check]
pipelines:
logs:
receivers: [otlp]
exporters: [file]
metrics:
receivers: [otlp]
exporters: [file]
- For log rotation, I only set the rotation threshold in
.exporters.file.rotation, and left the retention period of rotated files unset- For default values, see opentelemetry-collector-contrib/tree/main/exporter/fileexporter/README.md
- Enabling the health_check extension makes it possible to check the Collector's status
Start the container
Start the container with the following command.
In this example I use Podman as the container management tool, but if you use Docker, please read it accordingly.
podman run -d \
--name claude-audit-collector \
--restart=always \
-p 127.0.0.1:4317:4317 \
-p 127.0.0.1:4318:4318 \
-p 127.0.0.1:13133:13133 \
-v ~/claude-audit-logs:/data:rw \
ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:latest \
--config=/data/config.yaml
- Ports are forwarded for grpc, http, and health_check
The container's status can be checked from the health_check endpoint.
$ curl -s http://localhost:13133/ | jq .
{
"status": "Server available",
"upSince": "2026-05-17T02:04:48.718180489Z",
"uptime": "2h16m1.596349326s"
}
Check the log contents
So far, the log output settings on the Claude side and the pipeline preparation are done.
Let's actually have Claude Code run the Bash tool and check the event logs.
Use the following claude -p command to ask it to run ls .. By specifying json as the output format, we obtain the session ID.
The session ID is a standard attribute common to metrics and events, and you can search logs using it as a key.
claude -p "カレントディレクトリのファイル一覧を ls コマンドで取得して答えて" \
--output-format json
Claude Code outputs the answer, the session ID, and so on.
(I am running the claude -p command under ~/claude-audit-logs/.)
{
"type": "result",
"subtype": "success",
"is_error": false,
"api_error_status": null,
"duration_ms": 11333,
"duration_api_ms": 5210,
"num_turns": 2,
"result": "カレントディレクトリ `/Users/xxx/claude-audit-logs` には以下の2ファイルがあります。\n\n- `audit.jsonl`\n- `config.yaml`",
"stop_reason": "end_turn",
"session_id": "d618ca8f-7942-4123-9056-29b600b30b18",
... <以下省略>
}
- The
resultanswers with the two files under~/claude-audit-logs/. - We obtained
d618ca8f-7942-4123-9056-29b600b30b18as the session ID.
Search ~/claude-audit-logs/audit.jsonl with this session ID.
The file contains not only logs but also metrics, so this time I will search only for tool_use event logs.
grep "d618ca8f-7942-4123-9056-29b600b30b18" audit.jsonl \
| grep "tool_use" \
| jq .
The result is the following JSON. Unnecessary fields have been removed.
{
"resourceLogs": [
{
"resource": {
},
"scopeLogs": [
{
"scope": {
"name": "com.anthropic.claude_code.events",
"version": "2.1.133"
},
"logRecords": [
{
"timeUnixNano": "1778992043664000000",
"observedTimeUnixNano": "1778992043664000000",
"body": {
"stringValue": "claude_code.tool_result"
},
"attributes": [
{
"key": "session.id",
"value": {
"stringValue": "d618ca8f-7942-4123-9056-29b600b30b18"
}
},
{
"key": "event.name",
"value": {
"stringValue": "tool_result"
}
},
{
"key": "tool_name",
"value": {
"stringValue": "Bash"
}
},
{
"key": "success",
"value": {
"stringValue": "true"
}
},
{
"key": "tool_parameters",
"value": {
"stringValue": "{\"bash_command\":\"ls\",\"full_command\":\"ls /Users/xxx/claude-audit-logs\",\"description\":\"List files in current directory\"}"
}
},
{
"key": "tool_input",
"value": {
"stringValue": "{\"command\":\"ls /Users/xxx/claude-audit-logs\",\"description\":\"List files in current directory\"}"
}
}
]
}
]
}
]
}
]
}
- The commands passed to the Bash tool can be seen in
tool_parametersandtool_input- Since the Claude Code Docs contain the following description for
tool_input, it seems better to look attool_parametersfor audit purposes
JSON-serialized tool arguments. Individual values exceeding 512 characters are truncated, and the overall payload is limited to about 4 K characters.
- Since the Claude Code Docs contain the following description for
- Since I ran this with
claude -pthis time, I was also able to confirm that the history is captured even for Bash tool executions that do not go through an approval step
Because the time, session ID, and success or failure are output along with the command, you can verify "when, in which session, and what kind of command was invoked."
Summary
In this article, I described how to create an OTel-compatible pipeline in a local environment and save the event logs output by Claude Code.
I found that Claude Code event logs can be recorded easily without any particularly complex processing in the pipeline, and that they can be used as an audit log.
This time I stopped at output to a local file, but by changing the Exporter settings, it can also be used for visualization with Grafana Loki and the like.
Please make use of Claude Code's logs and metrics to build a safe and secure development environment.
