OpenTelemetryでテキスト解析パイプラインのトレーシングの続編。 前回はOpenTelemetry CollectorのexporterにJaegerを設定したが、例えば複数のトレースデータをaggregationしたり、インタラクティブにクエリしながら分析するにはJaegerだと難しい場面もある。 ありがたいことにopentelemetry-collector-contribには他にも様々な外部サービス含むexporterの実装が公開されている。
今回はanalytical databaseのOSSの一つであるClickHouseをexporterとして設定し、トレースデータをClickHouseのDBに蓄積しつつpythonからクエリしてみる。
ClickHouseサーバーの起動
公式からDocker imageが提供されているのでこれ使ってサーバーを起動する。手順も公式ドキュメントが公開されており(参考)、これを元にして前回のdocker composeのファイルに以下のように加筆する。
services: # ... clickhouse-server: image: clickhouse/clickhouse-server:latest container_name: clickhouse-server ports: - "18123:8123" - "19000:9000" environment: - CLICKHOUSE_PASSWORD=changeme networks: - otel-network restart: unless-stopped起動後、テーブルの確認をしたい時などは以下を実行するとClickHouseクライアントのインタラクティブシェルが起動する。
$ docker run -it --rm --network=container:clickhouse-server --entrypoint clickhouse-client clickhouse/clickhouse-serverCollectorの設定
exampleの設定を参考にCollector configを書く。
exporters: clickhouse: database: default endpoint: tcp://clickhouse-server:9000?dial_timeout=10s password: changeme username: default ttl: 720h timeout: 5s retry_on_failure: enabled: true initial_interval: 5s max_interval: 30s max_elapsed_time: 300s
service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, batch] exporters: [debug, otlp/jaeger, clickhouse]トレースデータを流す
docker compose upして試しにリクエストを送ってトレースデータを生成。特に事前にDDLの適用などは必要無い。
$ curl -XPOST --header "Content-Type: application/json" localhost:8000/predict -d'{"text": "OpenAI, Inc. is an American artificial intelligence (AI) organization headquartered in San Francisco, California."}'ClickHouseにクエリ
今回はClickHouse familyの一つで、pythonのin-process dbであるchdbからClickHouse serverにクエリする。
import chdb
query = """SELECT *FROM remote( 'localhost:19000', 'default', 'otel_traces', 'default', 'changeme')"""df = chdb.query(query, "DataFrame")
print(df.iloc[0].to_dict())# {'Timestamp': Timestamp('2025-09-07 05:56:20.722113731+0000', tz='Etc/UTC'), 'TraceId': '6cb804e12bd3e60847a5e2250ac707cf', 'SpanId': '4978923fdac9b43a', 'ParentSpanId': '81cd0b13e48b0b22', 'TraceState': '', 'SpanName': 'POST', 'SpanKind': 'Client', 'ServiceName': 'coordination-service', 'ResourceAttributes': [('service.name', 'coordination-service'), ('service.version', '1.0.0'), ('telemetry.sdk.language', 'python'), ('telemetry.sdk.name', 'opentelemetry'), ('telemetry.sdk.version', '1.36.0')], 'ScopeName': 'opentelemetry.instrumentation.requests', 'ScopeVersion': '0.57b0', 'SpanAttributes': [('http.method', 'POST'), ('http.status_code', '200'), ('http.url', '[http://summarization-service:8000/summarize](http://summarization-service:8000/summarize)')], 'Duration': 1222437530, 'StatusCode': 'Unset', 'StatusMessage': '', 'Events.Timestamp': array([], dtype='datetime64[ns]'), 'Events.Name': array([], dtype=object), 'Events.Attributes': array([], dtype=object), 'Links.TraceId': array([], dtype=object), 'Links.SpanId': array([], dtype=object), 'Links.TraceState': array([], dtype=object), 'Links.Attributes': array([], dtype=object)}デフォルトだとotel_tracesというテーブル名でトレースデータが保管されており、スキーマはexporterのソースのこの辺りに定義されている。
例えばOpenAIのモデルにAPIコールした際のトレースの中身は以下のようにフィルタリングして取り出せる。
_df = df[df["SpanName"] == "ChatOpenAI"]
print(_df.iloc[0]["SpanAttributes"])# [('gen_ai.completion', '{"generations":[[{"text":"","generation_info":{"finish_reason":"tool_calls","logprobs":null},"type":"ChatGeneration","message":{"lc":1,"type":"constructor","id":["langchain","schema","messages","AIMessage"],"kwargs":{"content":"","additional_kwargs":{"tool_calls":[{"id":"call_BXPN83uBQbnjT3AkyBTlaZk1","function":{"arguments":"{\\"summarized_text\\":\\"OpenAI, Inc. is a San Francisco-based American AI organization.\\"}","name":"SummarizationResponse"},"type":"function"}],"refusal":null},"response_metadata":{"token_usage":{"completion_tokens":32,"prompt_tokens":75,"total_tokens":107,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0}},"model_name":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_8bda4d3a2c","id":"chatcmpl-CD2ftyGBAZkFjxzPodhaOUBTkt2p3","service_tier":"default","finish_reason":"tool_calls","logprobs":null},"type":"ai","id":"run--4ac3c9f5-d0f0-4a22-a0be-be3b0e71761d-0","tool_calls":[{"name":"SummarizationResponse","args":{"summarized_text":"OpenAI, Inc. is a San Francisco-based American AI organization."},"id":"call_BXPN83uBQbnjT3AkyBTlaZk1","type":"tool_call"}],"usage_metadata":{"input_tokens":75,"output_tokens":32,"total_tokens":107,"input_token_details":{"audio":0,"cache_read":0},"output_token_details":{"audio":0,"reasoning":0}},"invalid_tool_calls":[]}}}]],"llm_output":{"token_usage":{"completion_tokens":32,"prompt_tokens":75,"total_tokens":107,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0}},"model_name":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_8bda4d3a2c","id":"chatcmpl-CD2ftyGBAZkFjxzPodhaOUBTkt2p3","service_tier":"default"},"run":null,"type":"LLMResult"}'), ('gen_ai.operation.name', 'chat'), ('gen_ai.prompt', '{"messages":[[{"lc":1,"type":"constructor","id":["langchain","schema","messages","HumanMessage"],"kwargs":{"content":"Summarize the following text: OpenAI, Inc. is an American artificial intelligence (AI) organization headquartered in San Francisco, California.","type":"human"}}]]}'), ('gen_ai.system', 'langchain'), ('gen_ai.usage.input_tokens', '75'), ('gen_ai.usage.output_tokens', '32'), ('gen_ai.usage.total_tokens', '107'), ('langsmith.span.kind', 'llm'), ('langsmith.trace.name', 'ChatOpenAI'), ('langsmith.trace.session_name', 'default')]endpointのrequest/responseの中身は以下のような形でフィルタリング
_df = df[df["ScopeName"] == "opentelemetry.instrumentation.fastapi"]
print(_df.iloc[0].to_dict())# {'Timestamp': Timestamp('2025-09-07 05:56:20.719899359+0000', tz='Etc/UTC'), 'TraceId': '6cb804e12bd3e60847a5e2250ac707cf', 'SpanId': '81cd0b13e48b0b22', 'ParentSpanId': '', 'TraceState': '', 'SpanName': 'POST [/predict](https://vscode-remote+ssh-002dremote-002bbishop-002etailf7887c-002ets-002enet.vscode-resource.vscode-cdn.net/predict)', 'SpanKind': 'Server', 'ServiceName': 'coordination-service', 'ResourceAttributes': [('service.name', 'coordination-service'), ('service.version', '1.0.0'), ('telemetry.sdk.language', 'python'), ('telemetry.sdk.name', 'opentelemetry'), ('telemetry.sdk.version', '1.36.0')], 'ScopeName': 'opentelemetry.instrumentation.fastapi', 'ScopeVersion': '0.57b0', 'SpanAttributes': [('http.flavor', '1.1'), ('http.host', '172.19.0.3:8000'), ('http.method', 'POST'), ('http.request.body.content', '{"text":"OpenAI, Inc. is an American artificial intelligence (AI) organization headquartered in San Francisco, California."}'), ('http.response.body.content', '{"prediction":"OpenAI, Inc.はサンフランシスコに本拠を置くアメリカのAI組織です。"}'), ('http.route', '/predict'), ('http.scheme', 'http'), ('http.server_name', 'localhost:8000'), ('http.status_code', '200'), ('http.target', '/predict'), ('http.url', '[http://172.19.0.3:8000/predict](http://172.19.0.3:8000/predict)'), ('http.user_agent', 'curl/8.5.0'), ('net.host.port', '8000'), ('net.peer.ip', '172.19.0.1'), ('net.peer.port', '43244')], 'Duration': 2456230410, 'StatusCode': 'Unset', 'StatusMessage': '', 'Events.Timestamp': array([], dtype='datetime64[ns]'), 'Events.Name': array([], dtype=object), 'Events.Attributes': array([], dtype=object), 'Links.TraceId': array([], dtype=object), 'Links.SpanId': array([], dtype=object), 'Links.TraceState': array([], dtype=object), 'Links.Attributes': array([], dtype=object)}こんな感じでClickHouseに流して分析できる。
この方法であればローカル端末に閉じてトレースデータのパイプラインを構築できるメリットがあるが、閉じなくていい場合のAlternativeとしてはgooglecloudexporterを使ってCloud Traceに流してBigQueryにエクスポートするのも一案。