commits
otel-zig's BatchSpanProcessor held the mutex during HTTP export,
blocking all span.end() callers for seconds. Updated to version
that releases the lock before exporting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
original repo (ibd1279/otel-zig) has single maintainer and 3.5 months
of inactivity. forking to zzstoatzz/otel-zig ensures we can apply
0.16 updates when needed.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
the logfire-legacy module (direct OTLP/JSON implementation) was never
used by any downstream projects - both prefect-server and leaflet-search
use the otel-backed logfire module.
deleted:
- src/exporter.zig, root.zig, attribute.zig, span.zig, log.zig, metrics.zig
- examples/basic.zig
- docs/otel-adoption-plan.md
~75KB of dead code removed.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BasicSpanProcessor exports synchronously on every span.end(), causing
major latency overhead (~100-200ms per span for OTLP HTTP requests).
BatchSpanProcessor queues spans and exports them in batches every 1s
via a background thread, eliminating export latency from the request path.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
framework integrations for common span patterns:
- httpSpan(method, path) -> "HTTP GET /api/..." with standard attrs
- sqlSpan(sql, db_system) -> truncated SQL with db.system attr
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
enables HTTP and DB instrumentation with proper error tracking:
- StatusCode enum (unset, ok, error) per otel semantic conventions
- setStatus(code, description) for explicit status setting
- recordError(e) - zig-idiomatic naming (not recordException)
zig has errors, not exceptions; maps to otel's recordException internally
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- clarify we now use otel-zig with OTLP/protobuf (not JSON)
- update status: metrics are stubs, logging is console-only
- add example showing nested span parent-child linking
- note cross-platform support (macOS + Linux)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
adds thread-local tracking of current span context so nested spans
properly share trace_id and have parent_span_id set correctly.
- adds threadlocal tl_current_span_context for tracking active span
- createSpan now passes parent context to otel-zig's startSpan
- Span.end restores parent context for proper nesting
- also fixes setAttribute to check for null from toOtelValue
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
setGlobalTracerProvider expects the interface type, not the concrete
provider pointer.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TracerProvider doesn't have addProcessor method - need to use
pipelineBuilder().with(link).done() pattern instead.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
otel-zig's setupGlobalProvider calls detectResource which only supports
macOS (uses _NSGetExecutablePath). bypass this by:
- manually creating TracerProvider instead of setupGlobalProvider
- implementing createMinimalResource with platform-specific detection:
- linux: /proc/self/exe symlink
- macos: _NSGetExecutablePath
- others: skip executable info (pid still works)
tested in docker (debian bookworm + zig 0.15.2) - passes.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- toOtelValue now returns ?AttributeValue
- attrsToOtel skips null values from optionals
- enables leaflet-search patterns like .{ .uri = optional_uri }
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- `logfire` module now points to otel_wrapper.zig (recommended)
- `logfire-legacy` module available for transition period
- example renamed to use main module
- both modules tested in `zig build test`
leaflet-search can now adopt without import changes.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- global instance pattern (like Python's DEFAULT_LOGFIRE_INSTANCE)
- convenience functions: configure(), span(), info/warn/err/debug/trace()
- const span support via @constCast for `const span = ...` pattern
- metrics stubs: counter(), gaugeInt(), gaugeDouble()
- fixed Zig 0.15 stderr API (use std.debug.print)
- fixed comptime/runtime attrs via static storage per type
API now matches leaflet-search usage patterns.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
adds otel-zig dependency and creates wrapper module that:
- configures OTLP exporter with Logfire endpoint/auth
- provides OtelLogfire struct with span() API
- handles token-based region detection (us/eu)
new files:
- src/otel_wrapper.zig: logfire-otel module wrapping otel-zig
- examples/otel_basic.zig: demonstrates new otel-backed API
- tests/validate_otel_export.zig: validates OTLP export to Logfire
- docs/otel-adoption-plan.md: migration plan and findings
build targets:
- zig build otel-example: run otel-backed example
- zig build validate-otel: test OTLP export (requires LOGFIRE_TOKEN)
legacy API unchanged - both APIs available during transition.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- fix CLAUDE.md: zig build example (not run-example)
- update README status: mark batched async export and parent-child spans as done
- clarify remaining work: histograms need convenience API, protobuf not started
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
zig's struct copy semantics caused dangling pointers when Span.Data
was copied (in span.end()). the Value.string slice pointed to the
original struct's _string_storage, not the copy's.
fix: don't store a slice in Value.string at all. store string data in
_string_storage and reconstruct the slice via getString() on access.
this ensures the slice always points to the current struct's storage.
- Value.string is now just a tag (no payload)
- added getString() method to reconstruct slice from internal storage
- copyString() no longer returns a slice
- all consumers now use getString() instead of .string
- for loops capture |*attr| to get pointers
added test verifying copy safety: the getString() pointer comes from
the copy's _string_storage, not the original's.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- escape bytes 0x80-0xFF as \u00XX to ensure valid JSON
- escape DEL (0x7f) with other control characters
- add tests for string attributes with special chars and raw bytes
- update example to demonstrate string attribute handling
fixes "invalid unicode code point" errors when attribute values
contain invalid UTF-8 or non-ASCII bytes from URL-decoded data.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
jw.write() wasn't reliably serializing strings from internal storage.
bypass it with manual JSON string writing using beginWriteRaw/endWriteRaw
to maintain Stringify state while writing escaped strings directly.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
slices pointing to internal _string_storage need explicit cast to
[]const u8 for std.json.Stringify to serialize them as strings
rather than byte arrays.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
attributes now copy string data into internal storage instead of
storing pointers. this fixes segfaults when arena-allocated strings
are passed as span attributes - the data outlives the arena.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
track current span ID on thread so child spans can reference their
parent. when creating a span while another is active:
- capture current span as parent_span_id
- update tl_current_span_id to new span
- on span end, restore parent as current
adds parentSpanId to OTLP JSON output when present.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
concurrent requests on different thread pool workers were sharing
the same trace_id because current_trace_id was process-global.
now each thread has its own trace context via threadlocal:
- tl_trace_id: current trace ID for this thread
- tl_active_span_count: span nesting depth for this thread
this ensures each HTTP request gets its own trace_id even when
handled by different workers concurrently.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
the OTLP JSON format expects asInt/asDouble and histogram counts
to be numbers, not strings. only timestamps are strings.
compared against official examples in opentelemetry-proto repo.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. trace ID: generate new ID for each root span (when no active spans)
- add active_span_count tracking
- add newTrace() for explicit trace boundaries
2. metrics: change from cumulative to delta temporality
- each counter() call is an increment, not aggregated total
3. memory: track allocated NumberDataPoints and free on flush
- allocated_data_points list tracks allocations
- freed in flush() and deinit()
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
flushes pending data every batch_timeout_ms (default 500ms).
stops cleanly on shutdown().
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- metrics: Counter, Gauge, UpDownCounter, Histogram, ExponentialHistogram
- attributes: typed key-value pairs for spans/logs/metrics
- exporter: rewrite with std.json.Stringify (no manual string building)
- root: add counter(), gaugeInt(), gaugeDouble() convenience functions
- CLAUDE.md: project patterns and zig 0.15 notes
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
working:
- config with env var resolution (LOGFIRE_WRITE_TOKEN, LOGFIRE_TOKEN)
- OTLP HTTP/JSON export to /v1/traces and /v1/logs
- span tracking with timing
- structured logging with severity levels
- console fallback when no token
pending:
- attribute storage for spans/logs
- metrics (counter, gauge, histogram)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
the logfire-legacy module (direct OTLP/JSON implementation) was never
used by any downstream projects - both prefect-server and leaflet-search
use the otel-backed logfire module.
deleted:
- src/exporter.zig, root.zig, attribute.zig, span.zig, log.zig, metrics.zig
- examples/basic.zig
- docs/otel-adoption-plan.md
~75KB of dead code removed.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BasicSpanProcessor exports synchronously on every span.end(), causing
major latency overhead (~100-200ms per span for OTLP HTTP requests).
BatchSpanProcessor queues spans and exports them in batches every 1s
via a background thread, eliminating export latency from the request path.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
enables HTTP and DB instrumentation with proper error tracking:
- StatusCode enum (unset, ok, error) per otel semantic conventions
- setStatus(code, description) for explicit status setting
- recordError(e) - zig-idiomatic naming (not recordException)
zig has errors, not exceptions; maps to otel's recordException internally
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- clarify we now use otel-zig with OTLP/protobuf (not JSON)
- update status: metrics are stubs, logging is console-only
- add example showing nested span parent-child linking
- note cross-platform support (macOS + Linux)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
adds thread-local tracking of current span context so nested spans
properly share trace_id and have parent_span_id set correctly.
- adds threadlocal tl_current_span_context for tracking active span
- createSpan now passes parent context to otel-zig's startSpan
- Span.end restores parent context for proper nesting
- also fixes setAttribute to check for null from toOtelValue
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
otel-zig's setupGlobalProvider calls detectResource which only supports
macOS (uses _NSGetExecutablePath). bypass this by:
- manually creating TracerProvider instead of setupGlobalProvider
- implementing createMinimalResource with platform-specific detection:
- linux: /proc/self/exe symlink
- macos: _NSGetExecutablePath
- others: skip executable info (pid still works)
tested in docker (debian bookworm + zig 0.15.2) - passes.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- `logfire` module now points to otel_wrapper.zig (recommended)
- `logfire-legacy` module available for transition period
- example renamed to use main module
- both modules tested in `zig build test`
leaflet-search can now adopt without import changes.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- global instance pattern (like Python's DEFAULT_LOGFIRE_INSTANCE)
- convenience functions: configure(), span(), info/warn/err/debug/trace()
- const span support via @constCast for `const span = ...` pattern
- metrics stubs: counter(), gaugeInt(), gaugeDouble()
- fixed Zig 0.15 stderr API (use std.debug.print)
- fixed comptime/runtime attrs via static storage per type
API now matches leaflet-search usage patterns.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
adds otel-zig dependency and creates wrapper module that:
- configures OTLP exporter with Logfire endpoint/auth
- provides OtelLogfire struct with span() API
- handles token-based region detection (us/eu)
new files:
- src/otel_wrapper.zig: logfire-otel module wrapping otel-zig
- examples/otel_basic.zig: demonstrates new otel-backed API
- tests/validate_otel_export.zig: validates OTLP export to Logfire
- docs/otel-adoption-plan.md: migration plan and findings
build targets:
- zig build otel-example: run otel-backed example
- zig build validate-otel: test OTLP export (requires LOGFIRE_TOKEN)
legacy API unchanged - both APIs available during transition.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- fix CLAUDE.md: zig build example (not run-example)
- update README status: mark batched async export and parent-child spans as done
- clarify remaining work: histograms need convenience API, protobuf not started
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
zig's struct copy semantics caused dangling pointers when Span.Data
was copied (in span.end()). the Value.string slice pointed to the
original struct's _string_storage, not the copy's.
fix: don't store a slice in Value.string at all. store string data in
_string_storage and reconstruct the slice via getString() on access.
this ensures the slice always points to the current struct's storage.
- Value.string is now just a tag (no payload)
- added getString() method to reconstruct slice from internal storage
- copyString() no longer returns a slice
- all consumers now use getString() instead of .string
- for loops capture |*attr| to get pointers
added test verifying copy safety: the getString() pointer comes from
the copy's _string_storage, not the original's.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- escape bytes 0x80-0xFF as \u00XX to ensure valid JSON
- escape DEL (0x7f) with other control characters
- add tests for string attributes with special chars and raw bytes
- update example to demonstrate string attribute handling
fixes "invalid unicode code point" errors when attribute values
contain invalid UTF-8 or non-ASCII bytes from URL-decoded data.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
jw.write() wasn't reliably serializing strings from internal storage.
bypass it with manual JSON string writing using beginWriteRaw/endWriteRaw
to maintain Stringify state while writing escaped strings directly.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
attributes now copy string data into internal storage instead of
storing pointers. this fixes segfaults when arena-allocated strings
are passed as span attributes - the data outlives the arena.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
track current span ID on thread so child spans can reference their
parent. when creating a span while another is active:
- capture current span as parent_span_id
- update tl_current_span_id to new span
- on span end, restore parent as current
adds parentSpanId to OTLP JSON output when present.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
concurrent requests on different thread pool workers were sharing
the same trace_id because current_trace_id was process-global.
now each thread has its own trace context via threadlocal:
- tl_trace_id: current trace ID for this thread
- tl_active_span_count: span nesting depth for this thread
this ensures each HTTP request gets its own trace_id even when
handled by different workers concurrently.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
the OTLP JSON format expects asInt/asDouble and histogram counts
to be numbers, not strings. only timestamps are strings.
compared against official examples in opentelemetry-proto repo.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. trace ID: generate new ID for each root span (when no active spans)
- add active_span_count tracking
- add newTrace() for explicit trace boundaries
2. metrics: change from cumulative to delta temporality
- each counter() call is an increment, not aggregated total
3. memory: track allocated NumberDataPoints and free on flush
- allocated_data_points list tracks allocations
- freed in flush() and deinit()
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- metrics: Counter, Gauge, UpDownCounter, Histogram, ExponentialHistogram
- attributes: typed key-value pairs for spans/logs/metrics
- exporter: rewrite with std.json.Stringify (no manual string building)
- root: add counter(), gaugeInt(), gaugeDouble() convenience functions
- CLAUDE.md: project patterns and zig 0.15 notes
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
working:
- config with env var resolution (LOGFIRE_WRITE_TOKEN, LOGFIRE_TOKEN)
- OTLP HTTP/JSON export to /v1/traces and /v1/logs
- span tracking with timing
- structured logging with severity levels
- console fallback when no token
pending:
- attribute storage for spans/logs
- metrics (counter, gauge, histogram)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>