I Tried Collecting Claude Code Event Logs Using OpenTelemetry

I Tried Collecting Claude Code Event Logs Using OpenTelemetry

I tried collecting Claude Code event logs through an OpenTelemetry pipeline to use as audit logs. This walks through the implementation, from a simple local setup to checking the actual logs.
2026.05.18

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.
https://code.claude.com/docs/ja/monitoring-usage

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
    1. Receiver: receives data
    2. 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

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_TELEMETRY enables sending logs and metrics via OTel
  • For the purpose of recording Bash tool events, setting only OTEL_LOGS_EXPORTER is sufficient, but since I want to check metrics in the future as well, I have also enabled OTEL_METRICS_EXPORTER
    • Specify otlp (OpenTelemetry Protocol) as the Exporter
  • Because grpc is specified for OTEL_EXPORTER_OTLP_PROTOCOL, port 4317 needs 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.

  1. opentelemetry-collector: the official image, containing only core features
  2. 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]

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 result answers with the two files under ~/claude-audit-logs/.
  • We obtained d618ca8f-7942-4123-9056-29b600b30b18 as 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_parameters and tool_input
    • Since the Claude Code Docs contain the following description for tool_input, it seems better to look at tool_parameters for 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 I ran this with claude -p this 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.


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

Share this article

AI白書