Lọc telemetry AI agent bằng OpenTelemetry Collector mà vẫn giữ tín hiệu điều tra
Một lần chạy Claude Code trong thử nghiệm tạo 181 bản ghi, 289 KB JSON khi bật mọi cờ nội dung. Bài viết CNCF hướng dẫn dùng OpenTelemetry Collector để giảm nội dung dư thừa và metric cardinality, đồng thời giữ tín hiệu về chi phí, lỗi và độ trễ.
Một AI coding agent có thể tạo telemetry chứa nhiều dữ liệu hơn đội vận hành cần để theo dõi chi phí, lỗi và hiệu năng. Trong thử nghiệm của tác giả bài viết CNCF, Claude Code thực hiện tác vụ sửa hai kiểm thử lỗi và thêm kiểm thử biên đã tạo 181 bản ghi, tương đương 289 KB JSON, khi mọi cờ ghi nội dung được bật.
Khối dữ liệu này có thể gồm prompt, nội dung tệp, diff, đối số tool và phản hồi tool. Hướng tiếp cận được đề xuất là dùng OpenTelemetry Collector để giữ các bộ đếm và thuộc tính ngắn trả lời câu hỏi vận hành, thay vì mặc định lưu toàn bộ nội dung agent đã xử lý.
> Với SOC và đội an ninh, điểm khởi đầu không phải là “xóa bao nhiêu”, mà là xác định trường tối thiểu cần giữ cho phát hiện và điều tra sự cố: lỗi API, lỗi tool, định danh phiên phù hợp, thời điểm, loại tác vụ và tín hiệu chi phí/độ trễ.
Telemetry AI agent phình ra ở đâu?
AI coding agent thường hoạt động theo vòng lặp: prompt, gọi mô hình, gọi tool, nhận kết quả, quyết định rồi lặp lại.
Theo bài viết, một tương tác có thể là span claudecode.interaction, có span con llmrequest cho từng lượt gọi mô hình và span tool cho mỗi lần gọi công cụ.
Khi bật OTELLOGTOOLCONTENT, mỗi span tool có thể có event tool.output chứa dữ liệu tool trả về. Với tool Read, dữ liệu đó có thể là cả nội dung tệp. Tác giả nêu ví dụ: một tệp 40 KB có thể trở thành một span event 40 KB.
Dữ liệu còn có thể bị lặp giữa traces và logs:
Event toolresult và tooldecision có thể mang toolparameters, tức đầy đủ đối số gửi tới tool.
Với thao tác ghi tệp, đối số này có thể chứa toàn bộ nội dung tệp.
Metrics mang các thuộc tính nhận diện như session.id, user.id, user.email và hai ID tài khoản.
Các thuộc tính theo phiên và người dùng làm tăng metric cardinality, vì mỗi phiên mới có thể tạo thêm time series riêng tồn tại lâu dài.
Giữ tín hiệu trả lời câu hỏi vận hành
Tác giả xác định sáu câu hỏi telemetry cần trả lời:
| Câu hỏi | Signal hoặc thuộc tính |
|---|---|
| Chi phí theo model là bao nhiêu? | claudecode.cost.usage |
| Token theo phiên và phần cache là bao nhiêu? | claudecode.token.usage theo loại |
| Tool nào lỗi và lỗi bao nhiêu lần? | Event toolresult và trạng thái thành công |
| Người dùng có từ chối thay đổi của agent không? | Event tooldecision |
| Độ trễ nằm ở đâu? | Span llmrequest và ttftms |
| API gặp lỗi gì? | Event apierror và errorclass |
Các câu hỏi này chủ yếu cần counter hoặc thuộc tính ngắn. Chúng không nhất thiết đòi hỏi lưu nguyên prompt, nội dung tệp hay diff.
Tuy nhiên, trước khi xóa prompt, response hoặc toolparameters, SOC cần đối chiếu với nhu cầu điều tra thực tế. Chẳng hạn, đội có thể cần giữ phân loại tool, trạng thái thành công, mã lỗi, thời gian và định danh phiên đã được xử lý để liên kết sự kiện — thay vì giữ nguyên nội dung nhạy cảm.
So sánh raw và optimized trên cùng input
Ví dụ của bài viết đưa một OTLP receiver vào hai nhánh Collector:
Raw pipeline: mang service.name và batch processor.
Optimized pipeline: bổ sung processor để lọc event, xóa trường, hash định danh và giảm cardinality.
Cả hai nhánh nhận cùng input, sau đó xuất sang Grafana để so sánh số bản ghi, byte mỗi phút và số time series. Theo tác giả, raw pipeline trong môi trường production có thể chuyển sang object storage hoặc được bỏ hẳn.
Cần lưu ý: raw pipeline có thể chứa prompt, mã nguồn, nội dung tệp, diff và dữ liệu nhạy cảm khác. Vì vậy, chỉ nên bật nhánh này có kiểm soát, giới hạn người được truy cập và đặt thời gian lưu phù hợp với chính sách bảo mật của tổ chức.
Đoạn Docker Compose dưới đây chỉ là minh họa từ bài viết, không phải tệp cấu hình Collector hoàn chỉnh có thể chạy nguyên trạng:
yaml
services:
lgtm:
image: grafana/otel-lgtm:0.33.1
ports:
"3000:3000"
collector:
image: otel/opentelemetry-collector-contrib:0.161.0
dependson: [lgtm]
volumes:
./config.yaml:/etc/otelcol-contrib/config.yaml:ro
ports:
"4317:4317"
"4318:4318"
Để tạo trạng thái thu thập đầy đủ nội dung trong ví dụ, tác giả bật telemetry và các exporter OTLP của Claude Code:
bash
export CLAUDECODEENABLETELEMETRY=1
export CLAUDECODEENHANCEDTELEMETRYBETA=1
export OTELMETRICSEXPORTER=otlp
export OTELLOGSEXPORTER=otlp
export OTELTRACESEXPORTER=otlp
export OTELEXPORTEROTLPPROTOCOL=grpc
export OTELEXPORTEROTLPENDPOINT=http://localhost:4317
export OTELLOGUSERPROMPTS=1
export OTELLOGASSISTANTRESPONSES=1
export OTELLOGTOOLDETAILS=1
export OTELLOGTOOLCONTENT=1
Logs: bỏ event, xóa nội dung, rút gọn định danh
Bài viết trước hết lọc event apirequest. Event này có model name và request ID, nhưng ví dụ vẫn loại bỏ toàn bộ event vì các câu hỏi vận hành mà tác giả nhắm tới đã được trả lời qua các signal khác.
yaml
processors:
filter/dropapirequest:
errormode: ignore
logs:
logrecord:
'attributes["event.name"] == "apirequest"'
Theo tác giả, bước này loại khoảng một phần tư log record.
Pipeline sau đó xóa sáu trường: prompt, response, đối số tool, đường dẫn workspace, email người dùng và loại terminal.
yaml
transform/logsdeletefields:
errormode: ignore
logstatements:
context: log
statements:
deletekey(log.attributes, "prompt")
deletekey(log.attributes, "response")
deletekey(log.attributes, "toolparameters")
deletekey(log.attributes, "workspace.hostpaths")
deletekey(log.attributes, "user.email")
deletekey(log.attributes, "terminal.type")
Trong khi đó, promptlength và responselength được giữ để theo dõi xu hướng kích thước mà không lưu văn bản gốc.
Tác giả cũng hash session.id và user.id, rút xuống 16 ký tự, đồng thời giới hạn trường error ở 256 ký tự:
yaml
transform/logshashandtrim:
errormode: ignore
logstatements:
context: log
statements:
set(log.attributes["session.id"], Substring(SHA256(log.attributes["session.id"]), 0, 16)) where log.attributes["session.id"] != nil
set(log.attributes["user.id"], Substring(SHA256(log.attributes["user.id"]), 0, 16)) where log.attributes["user.id"] != nil
set(log.attributes["error"], Substring(log.attributes["error"], 0, 256)) where log.attributes["error"] != nil and Len(log.attributes["error"]) > 256
Việc rút ngắn hash nhằm tránh trường hợp SHA-256 đầy đủ dài 64 ký tự lại lớn hơn UUID gốc dài 36 ký tự. Nhưng hash định danh không đồng nghĩa với ẩn danh: một giá trị hash ổn định vẫn có thể được liên kết giữa nhiều bản ghi.
Ngoài ra, 16 ký tự hex tương đương 64 bit. Đội vận hành cần đánh giá nguy cơ va chạm dựa trên quy mô dữ liệu, thời gian lưu và yêu cầu điều tra trước khi dùng giá trị rút gọn này làm khóa liên kết.
Theo tác giả, các bước xử lý log giúp dung lượng log giảm khoảng một nửa. Pipeline minh họa có dạng:
yaml
logs/optimized:
receivers: [otlp]
processors:
filter/dropapirequest
transform/logsdeletefields
transform/logshashandtrim
batch
exporters: [otlp/lgtmoptlogs]
Metrics: giảm cardinality bằng cách bỏ dimension theo phiên
Với metrics, vấn đề chính là cardinality. Các thuộc tính như session.id, user.id, email, ID tài khoản và loại terminal có thể tạo ra nhiều time series riêng.
Trong thử nghiệm của tác giả, 25 phiên tạo 300 series cho 8 metrics. Sau khi xóa sáu thuộc tính định danh khỏi data point, số series giảm còn 12.
yaml
transform/metricsdeletefields:
errormode: ignore
metricstatements:
context: datapoint
statements:
deletekey(datapoint.attributes, "session.id")
deletekey(datapoint.attributes, "user.accountuuid")
deletekey(datapoint.attributes, "user.accountid")
deletekey(datapoint.attributes, "user.id")
deletekey(datapoint.attributes, "user.email")
deletekey(datapoint.attributes, "terminal.type")
Claude Code xuất metric ở delta temporality, trong khi Prometheus cần cumulative temporality. Vì vậy, bài viết đề xuất phải gộp các phiên trước, rồi mới dùng deltatocumulative.
Thứ tự processor là điều kiện quan trọng:
Gom dữ liệu trong cửa sổ hai giây.
Đưa metric cùng tên về một resource.
Gán timestamp chung và cộng các data point có thuộc tính giống nhau.
Sau cùng mới chuyển từ delta sang cumulative.
Tác giả ghi nhận tổng metric của nhánh optimized khớp với nhánh raw trong sai lệch khoảng nửa phần trăm. Khi triển khai, đội vận hành nên kiểm thử tổng metric trước và sau tối ưu, thay vì chỉ nhìn mức giảm số series hoặc số byte.
Cách gộp này chỉ phù hợp khi một Collector gateway nhìn thấy mọi phiên. Nếu nhiều Collector nằm sau load balancer, việc cộng dữ liệu theo cửa sổ không còn bảo đảm đúng như thiết kế. Đây là ràng buộc kiến trúc cần được xác nhận trước khi đưa pipeline vào production.
Checklist cho SOC và đội platform
Xác định dashboard, alert và truy vấn điều tra nào thực sự cần từng trường telemetry.
Phân tách dữ liệu phục vụ điều tra khỏi prompt, mã nguồn, nội dung tệp, diff và đối số tool.
Theo dõi metric cardinality, đặc biệt với thuộc tính theo phiên hoặc người dùng.
So sánh raw và optimized pipeline trên cùng input trong giai đoạn kiểm thử.
Đối chiếu tổng metric trước và sau tối ưu để phát hiện sai lệch do phép gộp hoặc thứ tự processor.
Kiểm soát chặt quyền truy cập và thời gian lưu của raw telemetry chứa nội dung nhạy cảm.
Đánh giá việc hash và rút gọn định danh theo yêu cầu liên kết điều tra, nguy cơ va chạm và phạm vi lưu trữ.
Thông điệp chính của bài viết là: telemetry hữu ích không đồng nghĩa với việc phải lưu mọi nội dung agent đã xử lý. Với OpenTelemetry Collector, đội vận hành có thể giữ tín hiệu về chi phí, token, lỗi tool, độ trễ và lỗi API, đồng thời giảm dữ liệu dư thừa và rủi ro lộ nội dung trong pipeline quan sát.