fix: emit cache token OTEL attributes as strings for Sentry#3501
Conversation
Sentry's OTLP ingestion drops unknown numeric span attributes but keeps string attributes (confirmed: awf.request_id string shows up, but numeric cache_read_tokens_count was dropped). Emit cache/reasoning values as strings under the awf.* namespace: - awf.cache_read_tokens (string) - awf.cache_write_tokens (string) - awf.reasoning_tokens (string) These will appear alongside awf.request_id in Sentry's span attributes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
There was a problem hiding this comment.
Pull request overview
This PR updates the api-proxy OpenTelemetry span attributes for cache/reasoning token counts so they appear in Sentry by emitting them as string attributes under the awf.* namespace (since Sentry drops unknown numeric span attributes).
Changes:
- Emit
awf.cache_read_tokens,awf.cache_write_tokens, andawf.reasoning_tokensas strings on the request span andgen_ai.usageevent. - Remove the previous
*_countnumeric attribute keys for these fields. - Update OTEL unit tests to assert the new
awf.cache_*attribute keys and string values.
Show a summary per file
| File | Description |
|---|---|
| containers/api-proxy/otel.js | Switches cache/reasoning token OTEL attributes to awf.* string attributes for Sentry visibility. |
| containers/api-proxy/otel.test.js | Updates expectations to match the new awf.cache_* string attributes. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 2
| // Standard GenAI semconv (Sentry recognizes these as numeric) | ||
| 'gen_ai.usage.input_tokens': normalizedUsage.input_tokens, | ||
| 'gen_ai.usage.output_tokens': normalizedUsage.output_tokens, | ||
| 'gen_ai.request.stream': streaming, | ||
| // Cache and reasoning — use Sentry measurement naming (no dots, _count suffix) | ||
| 'cache_read_tokens_count': normalizedUsage.cache_read_tokens, | ||
| 'cache_write_tokens_count': normalizedUsage.cache_write_tokens, | ||
| 'reasoning_tokens_count': normalizedUsage.reasoning_tokens || 0, | ||
| // Cache and reasoning as strings (Sentry drops unknown numeric attrs but keeps strings) |
| expect(s.attributes['gen_ai.response.model']).toBe('gpt-4o'); | ||
| expect(s.attributes['gen_ai.usage.input_tokens']).toBe(1000); | ||
| expect(s.attributes['gen_ai.usage.output_tokens']).toBe(500); | ||
| expect(s.attributes['cache_read_tokens_count']).toBe(200); | ||
| expect(s.attributes['cache_write_tokens_count']).toBe(50); | ||
| expect(s.attributes['awf.cache_read_tokens']).toBe('200'); | ||
| expect(s.attributes['awf.cache_write_tokens']).toBe('50'); | ||
| expect(s.attributes['gen_ai.request.stream']).toBe(false); | ||
|
|
||
| const usageEvent = s.events.find(e => e.name === 'gen_ai.usage'); | ||
| expect(usageEvent).toBeDefined(); | ||
| expect(usageEvent.attributes['gen_ai.usage.input_tokens']).toBe(1000); | ||
| expect(usageEvent.attributes['gen_ai.usage.output_tokens']).toBe(500); | ||
| expect(usageEvent.attributes['cache_read_tokens_count']).toBe(200); | ||
| expect(usageEvent.attributes['cache_write_tokens_count']).toBe(50); | ||
| expect(usageEvent.attributes['awf.cache_read_tokens']).toBe('200'); | ||
| expect(usageEvent.attributes['awf.cache_write_tokens']).toBe('50'); |
Smoke Test Results✅ GitHub API: 2 PR entries confirmed Result: PASS
|
🔥 Smoke Test: Copilot BYOK (Offline) Mode
Running in BYOK offline mode ( Overall: PASS (core BYOK path confirmed) — PR by @lpcox, reviewer
|
🤖 Copilot Smoke Test
Overall: FAIL — Pre-computed step outputs ( PR author: @lpcox
|
🔥 Smoke Test: API Proxy OTEL Tracing Results
All scenarios pass. OTEL tracing integration is fully functional.
|
Chroot Smoke Test Results
Result: ❌ Not all versions matched — Python and Node.js differ between host and chroot environments.
|
|
Smoke test results: Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "registry.npmjs.org"See Network Configuration for more information.
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS
|
|
Smoke test passed Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "localhost"See Network Configuration for more information.
|
Smoke Test Results
Overall: FAIL —
|
Sentry drops unknown numeric span attributes but keeps strings (confirmed:
awf.request_idstring shows up, numericcache_read_tokens_countwas dropped).Emits cache/reasoning as strings under
awf.*namespace:awf.cache_read_tokens→ e.g. "13288"awf.cache_write_tokens→ e.g. "0"awf.reasoning_tokens→ e.g. "0"These will appear next to
awf.request_idin Sentry's span attributes.