tracing
Version, currently 0.5.04 versions
- 0.5.2latestAug 26, 2026
- 0.5.1May 30, 2026
- 0.5.0not indexedJul 28, 2026
- 0.4.0not indexedJul 28, 2026
github.com/dsisnero/tracing.cr
structured, event based diagnostics for Crystal programs
Nothing has been indexed for 0.5.0 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
tracing:
github: dsisnero/tracing.cr
version: ~> 0.5.0Then run:
shards installshard.yml
No shard.yml has been indexed for 0.5.0. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
tracing
A Crystal port of tokio-rs/tracing: structured, event-based diagnostics for Crystal programs.
- Current version:
0.5.2 - Upstream pin:
tracing-0.1.44(2d55f6f) - Current status: core facade, subscriber stack, appender, flame, log bridge,
mock, error/span trace, attributes, and concurrency helpers are shipped in
src/. The OpenTelemetry bridge now lives in the optional companion shardtracing-opentelemetry.
Documentation
| Document | Purpose |
|---|---|
| Architecture | Runtime structure, data flow, public subsystems |
| Development | Setup, source tree, local workflow |
| Coding Guidelines | Porting and Crystal style rules |
| Testing | Quality gates and test organization |
| PR Workflow | Review checklist and branch/commit conventions |
| Changelog | Release history |
| Parity Status | Shipped feature ledger vs upstream |
Installation
dependencies:
tracing:
github: dsisnero/tracing.cr
shards install
Optional OpenTelemetry support is provided by the sibling shard:
dependencies:
tracing:
github: dsisnero/tracing.cr
branch: codex-tracing-opentelemetry-split
tracing-opentelemetry:
github: dsisnero/tracing-opentelemetry
Quick Start
require "tracing"
Tracing.fmt
.compact
.with_target(true)
.with_max_level(Tracing::LevelFilter::INFO)
.init
span!(Tracing::Level::INFO, "request", method: "GET").in_scope do
info!("request.started", user_id: 42)
end
Public Surface
The current entrypoint is src/tracing.cr. It requires and re-exports:
src/tracing/core/for metadata, fields, spans, events, subscribers, and dispatchsrc/tracing/facade files forTracing.span,Tracing.event,span!,info!, andTracing::Spansrc/tracing/subscriber/for registry, layers, filters, formatting, reload, appenders, flame, log bridge, mock subscriber, andSpanTracesrc/tracing/concurrency*for fiber and channel helpers
Core Concepts
Spans
Spans represent work with duration and context.
span = span!(Tracing::Level::INFO, "request", method: "GET")
span.in_scope do
span.record(path: "/users")
info!("request.authenticated", user: "alice")
end
Events
Events are point-in-time records, optionally attached to the current span.
info!("boot", port: 8080)
span!(Tracing::Level::DEBUG, "db_query").in_scope do
debug!("query.executed", rows: 100, duration_ms: 12)
end
Subscribers and Layers
Tracing::Registry stores span state. Tracing::Layer implementations observe
that state and render or export it.
registry = Tracing::Registry.default
.with(Tracing::FmtLayer.new(STDOUT).compact)
.with(Tracing::EnvFilter.new("info,my_app=debug"))
registry.init
Formatting
The formatting layer lives at src/tracing/subscriber/fmt.cr. There are two common entrypoints:
Tracing.fmtfor the builder APITracing::FmtLayer.new(...)for direct layer composition
Builder API
Tracing.fmt
.pretty
.with_target(true)
.with_thread_ids(true)
.with_max_level(Tracing::LevelFilter::DEBUG)
.init
JSON Output
Tracing.fmt
.json
.flatten_event(true)
.with_current_span(true)
.with_span_list(true)
.init
Current JSON controls:
flatten_event(true)moves event fields to the root objectwith_current_span(false)omits the current span namewith_span_list(false)omits the root-to-leaf span list
Filtering
The current filter surface lives under src/tracing/subscriber/.
# Level threshold
fmt = Tracing::FmtLayer.new(STDOUT).with_filter(Tracing::LevelFilter::INFO)
# Environment grammar
env = Tracing::EnvFilter.new("info,my_app=debug,my_app[db]=trace")
# Field-value matching (target[span{field=val}]=level)
field_filter = Tracing::EnvFilter.new("my_app[db{query=auth}]=debug")
# Closure-based
warn_only = Tracing::FilterFn.new { |meta| meta.level <= Tracing::Level::WARN }
# Programmatic targets
targets = Tracing::Targets.new
.with_target("my_app", Tracing::Level::DEBUG)
.with_default(Tracing::Level::INFO)
Filter combinators are also shipped:
combined = targets.and(env.not)
Runtime Reloading
The reload layer is implemented in src/tracing/subscriber/reload.cr.
builder = Tracing.fmt
.with_max_level(Tracing::LevelFilter::INFO)
.with_filter_reloading
builder.init
handle = builder.reload_handle
handle.reload(Tracing::LevelFilterLayer.new(Tracing::LevelFilter::DEBUG))
Crystal Log Bridge
Forward Crystal Log entries into tracing with
src/tracing/subscriber/log_tracer.cr.
Tracing.fmt.compact.init
Log.setup(:trace, Tracing::LogTracer.new)
Log.info { "routed to tracing" }
Level conversion between tracing and ::Log is available in
src/tracing/log.cr:
# Level → ::Log::Severity
Tracing::Log.level_as_log(Tracing::Level::WARN) # => ::Log::Severity::Warn
# LevelFilter → ::Log::Severity
Tracing::Log.level_filter_as_log(Tracing::LevelFilter.off) # => ::Log::Severity::None
# ::Log::Severity → Level
Tracing::Log.severity_as_trace(::Log::Severity::Notice) # => Tracing::Level::INFO
Non-Blocking Output and Rotation
Appender support lives in src/tracing/subscriber/appender.cr.
appender = Tracing::RollingFileAppender
.builder
.rotation(Tracing::Rotation::DAILY)
.filename_prefix("app")
.filename_suffix("log")
.build("logs")
non_blocking, guard = Tracing::NonBlocking.new(appender)
Tracing::Registry.default
.with(Tracing::FmtLayer.make_writer { non_blocking.make_writer }.compact)
.init
Keep guard alive until shutdown so the worker can flush buffered writes.
Current appender features:
NonBlockingworker fiber +WorkerGuardRollingFileAppender- builder support for
rotation,filename_prefix,filename_suffix,max_log_files
Flame Output
Tracing::FlameLayer is ported in src/tracing/subscriber/flame.cr.
It writes folded stack output for external tools such as inferno-flamegraph.
flame, guard = Tracing::FlameLayer.with_file("trace.folded")
Tracing::Registry.default.with(flame).init
# ... run app ...
# cat trace.folded | inferno-flamegraph > flame.svg
This is flamegraph/flamechart data, not Chrome trace format.
Keep guard alive until shutdown so remaining span samples are flushed.
OpenTelemetry
The OpenTelemetry bridge moved into the optional companion shard
tracing-opentelemetry. Install it alongside tracing, then require it
explicitly:
require "tracing"
require "tracing-opentelemetry"
exporter = OpenTelemetry::Exporter.new(:io, io: STDOUT)
provider = OpenTelemetry::TraceProvider.new(
service_name: "my_app",
exporter: exporter
)
Tracing::Registry.default
.with(
Tracing::OpenTelemetryLayer.new(provider)
.with_level(Tracing::Level::INFO)
.with_context_activation(true)
.with_target(true)
)
.init
span!(Tracing::Level::INFO, "request").in_scope do
info!("request.started", user: "alice")
end
Current OTel behavior:
- root and child spans export on span close
- contextual events become OTel span events
otel.name,otel.kind,otel.status_code, andotel.status_descriptionare mapped from tracing fields- error events can update span status and emit exception-style attributes
- context activation tracks the current trace/span on the active fiber
Note: those OTel override fields are read as dotted keys such as otel.kind.
The current Crystal facade is ergonomic for identifier-style named fields; if you
need dotted override keys today, inject them through lower-level field/value
construction rather than plain named args.
Concurrency Helpers
Concurrency helpers are split across src/tracing/concurrency*.
require "tracing/concurrency"
require "tracing/concurrency/channel_ext"
done = Tracing::Concurrency.spawn(name: "worker", job: "reindex") do
info!("worker.started")
42
end
result = done.receive
channel = Channel(String).new
traced = channel.traced("jobs")
Shipped helpers:
Tracing::Concurrency.spawnTracing::Concurrency.spawn_with_spanTracing::Concurrency.with_subscriberFiber.spawn_tracedChannel#tracedTracing::Concurrency::TracedChannel
Instrumentation Helpers
@[Tracing::Instrument]
def process(id : Int32)
Tracing.instrument("process", id: id) do
info!("process.started")
end
end
Development
shards install
crystal tool format --check src spec
ameba src spec
crystal spec
The current suite contains 314 examples in spec/tracing_spec.cr.
License
MIT — see LICENSE
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.5.0- Tagged
- Jul 28, 2026
- Commit
61e348abc884- Indexed
- not yet
Dependents
Repository
github.com/dsisnero/tracing.cr
Metadata
- Created
- Aug 18, 2026
- Updated
- Sep 21, 2026
- Synced
- Sep 21, 2026
- Versions
- 4