Summary
To improve cross-integration correlation and take advantage of Elastic's ECS GenAI fieldset, we can map several of the claude_code integration's custom fields onto their ECS equivalents. The current pipeline is in good shape — it already sets event.kind, event.action, event.outcome, event.duration, the categorization set, and populates related.user. The items below are incremental refinements, mostly enabled by the recent addition of gen_ai.* fields to ECS.
Context: Claude Code exports telemetry over OTLP but uses its own attribute naming rather than the OTel GenAI semantic conventions on the metrics/logs this integration ingests, so the ECS alignment has to be done in the ingest pipeline.
1. Map custom fields to ECS gen_ai.* (not currently set)
| Currently mapped as |
Better ECS field |
Notes |
claude_code.events.model |
gen_ai.request.model (+ gen_ai.response.model) |
|
claude_code.events.input_tokens |
gen_ai.usage.input_tokens |
long → integer |
claude_code.events.output_tokens |
gen_ai.usage.output_tokens |
long → integer |
claude_code.events.cache_read_tokens |
gen_ai.usage.cache_read.input_tokens |
|
claude_code.events.cache_creation_tokens |
gen_ai.usage.cache_creation.input_tokens |
|
claude_code.events.request_id |
gen_ai.response.id |
|
claude_code.events.tool_name / mcp_tool_name |
gen_ai.tool.name |
|
claude_code.events.tool_use_id |
gen_ai.tool.call.id |
|
claude_code.events.tool_input_flattened / tool_parameters_flattened |
gen_ai.tool.call.arguments |
flattened → flattened |
claude_code.events.query_source |
gen_ai.operation.name |
loose mapping |
| (constant) |
gen_ai.provider.name = "anthropic" |
add as constant_keyword |
2. Map custom fields to ECS core (not currently set)
| Currently mapped as |
Better ECS field |
Notes |
claude_code.events.error |
error.message |
Currently copied to event.reason only; error.message is the canonical home. Keep event.reason if desired. |
claude_code.events.error_code |
error.code |
|
claude_code.events.error_type |
error.type |
|
claude_code.events.organization.id |
organization.id |
|
claude_code.events.user.email |
user.email |
Already appended to related.user; the core user.email field is not set. |
claude_code.events.user.id |
user.id |
Already appended to related.user; the core user.id field is not set. |
claude_code.events.user.account_id / account_uuid |
user.domain / user.id |
|
3. ECS categorization refinements
3a. Additional signals for the categorization set
event.action, event.outcome (from success), event.kind, and event.duration are already handled in the pipeline. Remaining opportunities:
| Signal |
ECS field |
Mapping |
Status |
source |
event.provider |
user, system |
not set |
decision / decision_type |
event.outcome |
accept→success, reject→failure |
⚠️ verify — outcome is currently driven off success, so tool_decision rejects may not register as failure |
3b. Fix event.category per event type
| Event |
Current |
Proposed |
Rationale |
api_request |
web / info |
api / info |
Outbound API call to Anthropic; api is the purpose-built ECS value. |
api_error |
web / error |
api / error |
Failed API call. |
api_retries_exhausted |
web / error |
api / error |
Failed API call. |
api_refusal |
web / denied |
api / denied |
Model refusal on an API response, not a web-server log. |
tool_decision |
process / info |
iam (or configuration) / allowed|denied |
Authorization/permission decision, not a process event. |
user_prompt |
process / start |
(leave category empty) |
No fitting closed-vocab value; rely on event.kind + event.action. |
skill_activated |
process / info |
(leave category empty) |
Same as user_prompt. |
hook_registered |
process / info |
configuration (marginal) |
Registration is a config action; execution is the process. |
hook_execution_start |
process / info |
process / start |
Refine type. |
hook_execution_complete |
process / info |
process / end |
Refine type. |
plugin_loaded |
package / info |
library (marginal) |
package implies install/remove; runtime load is closer to library. |
plugin_installed |
package / info |
✅ package — leave as-is |
Install genuinely fits package. |
tool_result |
process / info |
process default, ideally derived from tool_name |
Spans process (Bash), file (Read/Write), web (WebFetch). |
auth |
authentication / info |
✅ no change |
Correct. |
permission_mode_changed |
configuration / change |
✅ no change |
Correct. |
mcp_server_connection |
network / connection |
✅ no change |
Correct. |
Note: event.category is a closed vocabulary. Where nothing fits (e.g. user_prompt, skill_activated), leave it empty and rely on event.kind + event.action rather than the current process.
4. Explicitly kept custom (no ECS equivalent)
cost_usd, cost_usd_micros (ECS GenAI has no cost field), session.id, prompt.id (no gen_ai.conversation.id), effort, speed, prompt_length, prompt_text, and all feature-specific fields (hook_*, mcp_*/server_*, plugin.*, skill.*, marketplace.*, *_path_count, permission-mode fields, terminal.type, tool_*_size_bytes, raw tool_input/tool_parameters).
Acceptance criteria
References
Summary
To improve cross-integration correlation and take advantage of Elastic's ECS GenAI fieldset, we can map several of the
claude_codeintegration's custom fields onto their ECS equivalents. The current pipeline is in good shape — it already setsevent.kind,event.action,event.outcome,event.duration, the categorization set, and populatesrelated.user. The items below are incremental refinements, mostly enabled by the recent addition ofgen_ai.*fields to ECS.Context: Claude Code exports telemetry over OTLP but uses its own attribute naming rather than the OTel GenAI semantic conventions on the metrics/logs this integration ingests, so the ECS alignment has to be done in the ingest pipeline.
1. Map custom fields to ECS
gen_ai.*(not currently set)claude_code.events.modelgen_ai.request.model(+gen_ai.response.model)claude_code.events.input_tokensgen_ai.usage.input_tokensclaude_code.events.output_tokensgen_ai.usage.output_tokensclaude_code.events.cache_read_tokensgen_ai.usage.cache_read.input_tokensclaude_code.events.cache_creation_tokensgen_ai.usage.cache_creation.input_tokensclaude_code.events.request_idgen_ai.response.idclaude_code.events.tool_name/mcp_tool_namegen_ai.tool.nameclaude_code.events.tool_use_idgen_ai.tool.call.idclaude_code.events.tool_input_flattened/tool_parameters_flattenedgen_ai.tool.call.argumentsclaude_code.events.query_sourcegen_ai.operation.namegen_ai.provider.name="anthropic"constant_keyword2. Map custom fields to ECS core (not currently set)
claude_code.events.errorerror.messageevent.reasononly;error.messageis the canonical home. Keepevent.reasonif desired.claude_code.events.error_codeerror.codeclaude_code.events.error_typeerror.typeclaude_code.events.organization.idorganization.idclaude_code.events.user.emailuser.emailrelated.user; the coreuser.emailfield is not set.claude_code.events.user.iduser.idrelated.user; the coreuser.idfield is not set.claude_code.events.user.account_id/account_uuiduser.domain/user.id3. ECS categorization refinements
3a. Additional signals for the categorization set
event.action,event.outcome(fromsuccess),event.kind, andevent.durationare already handled in the pipeline. Remaining opportunities:sourceevent.provideruser,systemdecision/decision_typeevent.outcomeaccept→success,reject→failuresuccess, sotool_decisionrejects may not register asfailure3b. Fix
event.categoryper event typeapi_requestweb/infoapi/infoapiis the purpose-built ECS value.api_errorweb/errorapi/errorapi_retries_exhaustedweb/errorapi/errorapi_refusalweb/deniedapi/deniedtool_decisionprocess/infoiam(orconfiguration) /allowed|denieduser_promptprocess/startevent.kind+event.action.skill_activatedprocess/infouser_prompt.hook_registeredprocess/infoconfiguration(marginal)hook_execution_startprocess/infoprocess/starthook_execution_completeprocess/infoprocess/endplugin_loadedpackage/infolibrary(marginal)packageimplies install/remove; runtime load is closer tolibrary.plugin_installedpackage/infopackage— leave as-ispackage.tool_resultprocess/infoprocessdefault, ideally derived fromtool_nameprocess(Bash),file(Read/Write),web(WebFetch).authauthentication/infopermission_mode_changedconfiguration/changemcp_server_connectionnetwork/connection4. Explicitly kept custom (no ECS equivalent)
cost_usd,cost_usd_micros(ECS GenAI has no cost field),session.id,prompt.id(nogen_ai.conversation.id),effort,speed,prompt_length,prompt_text, and all feature-specific fields (hook_*,mcp_*/server_*,plugin.*,skill.*,marketplace.*,*_path_count, permission-mode fields,terminal.type,tool_*_size_bytes, rawtool_input/tool_parameters).Acceptance criteria
gen_ai.*(Section 1), originals retainederror.message/error.code/error.type,organization.id, and coreuser.email/user.idpopulated (Section 2)event.categorycorrected per event type (3b) —api_*→api,tool_decision→iam, empty where nothing fitsevent.providerpopulated fromsource; permission-decision outcome handling verified (3a)fields.ymland ingest pipeline updated; sample events re-testedReferences