0.0
Low commit activity in last 3 years
No release in over a year
Trifle::Traces is a way too simple timeline tracer that helps you track custom outputs.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 2.1
>= 0
~> 13.0
~> 3.2
= 1.0.0
 Project Readme

Trifle::Traces

Gem Version Ruby

Structured execution tracing for Ruby. Track timestamped outputs from background jobs, API integrations, and anything else that runs in a black box. Know exactly what happened, when, and in what order.

Part of the Trifle ecosystem.

Also available as Trifle.Traces for Elixir, with the same 2.0 persistence format.

Quick Start

gem 'trifle-traces'
Trifle::Traces.tracer = Trifle::Traces::Tracer::Hash.new(key: 'jobs/sync')

Trifle::Traces.trace('Starting sync')
result = Trifle::Traces.trace('Fetching records from API') { api.fetch_all }
Trifle::Traces.trace('Sync complete')

Trifle::Traces.tracer.wrapup

Every message becomes a structured timeline entry on the tracer:

Trifle::Traces.tracer.data
#=> [
#     {at: 1739700000, message: 'Starting sync', state: :success, type: :text, level: 0},
#     {at: 1739700001, message: 'Fetching records from API', state: :success, type: :text, level: 0},
#     {at: 1739700003, message: 'Sync complete', state: :success, type: :text, level: 0}
#   ]

Row at timestamps are Unix seconds with a fractional part, preserving subsecond precision. Existing integer-second payloads remain readable.

Ideal for debugging those background-job-that-talks-to-API-and-works-every-time-when-you-run-it-manually-but-never-in-production type of jobs.

Persistence (v2.0)

Traces persist through two pluggable drivers: an index driver (searchable trace metadata) and a data driver (the payload). Configure both and the gem handles the whole lifecycle — no hand-written persistence callbacks.

Trifle::Traces.configure do |config|
  config.index_driver = Trifle::Traces::Driver::Index::Mongo.new(
    mongo_client, collection_name: 'trifle_traces'
  )
  config.data_driver = Trifle::Traces::Driver::Data::S3.new(
    client: s3_client, buckets: %w[traces-a traces-b], gzip: true
  )

  config.context   = ->(tracer) { { tenant_id: tracer.meta&.first } } # extra index fields
  config.retention = ->(tracer) { 3 }                                 # days, value or callable
end
  • Index drivers: Postgres (shared schema with the Elixir implementation), Mongo (production-proven at 100M+ traces/day), Memory, Null. ClickHouse/OpenSearch are planned — see lib/trifle/traces/driver/README.md to write your own.
  • Data drivers: S3 (any S3-compatible storage, multi-bucket sharding, gzip), File, Memory, Null.
  • Retention: carried on every record (retention days + expires_at). Mongo expires metadata via TTL index; Postgres provides cleanup! for a scheduled job; S3 expires payloads via one lifecycle rule per retention class (Driver::Data::S3.setup! creates them).
  • Without configured drivers nothing persists — data stays on the tracer for your callbacks, as in 1.x.

S3 selects a bucket once per trace and persists its name as bucket_name in the index. Reads, writes, and deletes use that name directly, so changing the bucket list does not redirect existing traces. File, Memory, and Null store nil.

Automated persistence removes uploaded artifact source files after final payload and index writes succeed at wrapup, before wrapup callbacks. Live uploads and failed wrapups retain files until then. Pass cleanup: false to Trifle::Traces.artifact(name, path, cleanup: false) to keep a reusable source. Callback-only setups, Null data drivers, and direct driver writes retain sources. Cleanup errors warn; termination before wrapup can leave files behind.

Postgres keeps the client injectable, so the gem still has no runtime dependencies. Add pg to the host application, provision the table once, and schedule cleanup for expired metadata:

require 'pg'

client = PG.connect(ENV.fetch('DATABASE_URL'))
Trifle::Traces::Driver::Index::Postgres.setup!(client)

Trifle::Traces.configure do |config|
  config.index_driver = Trifle::Traces::Driver::Index::Postgres.new(client)
end

# Run periodically from your scheduler:
Trifle::Traces.default.index_driver.cleanup!

Write modes

Every liftoff/bump/wrapup writes to the index in :live mode (default), giving you live progress. For fast, line-heavy, high-volume jobs use :deferred — zero I/O until wrapup, then exactly one index write and one payload part per trace:

class Commodity::PollJob
  include Sidekiq::Job
  sidekiq_options tracer_key: 'commodity/poll', tracer_mode: :deferred
end

Deferred mode skips liftoff and bump callbacks as well as persistence. Only wrapup callbacks run. When a queued Sidekiq payload has no tracer_mode, the middleware uses the worker class's current option, then config.default_mode. An explicit mode in the payload takes priority.

Reading traces back

record = Trifle::Traces.find(reference) #=> TraceRecord with duration and counters
result = Trifle::Traces.search(
  segment: 'jobs/sync',
  tags: { any: ['tenant:42', 'tenant:43'], all: ['failed'] },
  state: :error,
  from: Time.utc(2026, 8, 30),
  to: Time.utc(2026, 8, 31),
  duration_min: 5_000,
  limit: 50,
  cursor: nil
)
entries = Trifle::Traces.payload(record) # all parts, in order

duration is elapsed monotonic time in integer milliseconds. counters contains totals by entry state and type plus max_level. Search selects traces that started in the half-open interval first_at >= from and first_at < to; duration_min is inclusive. Tag groups mean (any[0] OR any[1] ...) AND all[0] AND all[1] ...; empty groups are ignored.

Search is intentionally narrow — materialized key-path segment, explicit tag groups, exact state, start-time range and minimum duration, newest-first with opaque cursor pagination. That is what stays fast at hundreds of millions of traces.

Activity Stats

Install and require trifle-stats, configure a Stats instance, and pass it to Traces to enable native activity tracking:

require 'trifle/stats'

stats_config = Trifle::Stats::Configuration.new
stats_config.driver = Trifle::Stats::Driver::Mongo.new(
  mongo_client, collection_name: 'trifle_traces_stats', system_tracking: true
)
stats_config.granularities = %w[10m 1h 1d]
stats_config.buffer_enabled = false

Trifle::Traces.configure do |config|
  config.stats_config = stats_config
end

stats_config defaults to nil; global Stats is not used implicitly. The supplied configuration owns storage, granularities, timezone and buffering. Provision the Stats driver's indexes once before starting workers.

Traces tracks the full trace key once after successful wrapup, before user callbacks, for live and deferred traces. Values are count, states.<state>, entries.count, and duration count/sum/square samples in milliseconds, including per-state samples. This is the same activity format used by Elixir and Trifle App. Final record metadata needs no index query. Ignored traces and failed final writes emit no metrics; Stats errors warn without failing the trace. User callbacks remain independent. Tracking also works without persistence, preserving callback data. See the configuration docs.

Callbacks

Live traces fire :liftoff, :bump and :wrapup callbacks. Deferred traces only fire :wrapup. Use callbacks for side effects like emitting metrics (persistence no longer belongs here):

Trifle::Traces.configure do |config|
  config.on(:wrapup) do |tracer|
    tracer.keys.each do |key|
      Trifle::Stats.track(key: "traces::#{key}", at: Time.now, values: { count: 1 })
    end
  end
end

With persistence configured, tracer.trace_record exposes the dispatcher's in-memory record. After final writes, wrapup callbacks can read its duration (milliseconds), length, counters, tags, and bucket name without another index query. Treat this record as read-only; payload entries still come from Trifle::Traces.payload(record).

Upgrading from 1.x

  • The return value of :liftoff callbacks no longer becomes tracer.reference — the index driver generates references. Move persistence out of callbacks into drivers (or implement a custom driver; see lib/trifle/traces/driver/README.md).
  • Persistence errors are no longer silent: liftoff/wrapup failures raise, bump failures re-queue the data and retry on the next flush. Override config.error_handler to customize.

Features

  • Simple tracing. Collect messages and return values from code execution.
  • Driver-based persistence. Postgres or Mongo metadata + S3/File payloads out of the box; contracts for custom backends.
  • Deferred mode. One write per trace for high-volume jobs (100M/day proven).
  • State management. Automatic success/error state tracking.
  • Callbacks. Hook into trace events for custom processing.
  • Middleware integration. Built-in support for Rack, Rails, and Sidekiq.
  • Thread-safe. Safe for concurrent execution.
  • Zero runtime dependencies. Database/storage clients are injected.

Middleware

Trifle::Traces provides middleware for popular frameworks:

  • Rack. HTTP request tracing.
  • Rails. Controller and view tracing.
  • Sidekiq. Background job tracing (sidekiq_options tracer_key:, tracer_mode:).

Documentation

Full guides and API reference at docs.trifle.io/trifle-traces

Trifle Ecosystem

Component What it does
Trifle App Dashboards, alerts, scheduled reports, AI-powered chat.
Trifle::Stats Time-series metrics for Ruby (Postgres, Redis, MongoDB, MySQL, SQLite).
Trifle.Traces (Elixir) Elixir implementation with compatible Postgres, Mongo, and File/S3 persistence.
Trifle CLI Terminal access to metrics. MCP server mode for AI agents.
Trifle::Logs File-based log storage with ripgrep-powered search.
Trifle::Docs Map a folder of Markdown files to documentation URLs.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/trifle-io/trifle-traces.

License

The gem is available as open source under the terms of the MIT License.