traces-0.18.2/0000755000004100000410000000000015062203775013122 5ustar www-datawww-datatraces-0.18.2/lib/0000755000004100000410000000000015062203775013670 5ustar www-datawww-datatraces-0.18.2/lib/traces/0000755000004100000410000000000015062203775015151 5ustar www-datawww-datatraces-0.18.2/lib/traces/config.rb0000644000004100000410000000256315062203775016751 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2024-2025, by Samuel Williams. module Traces # Represents a configuration for the traces library. class Config DEFAULT_PATH = ENV.fetch("TRACES_CONFIG_DEFAULT_PATH", "config/traces.rb") # Load the configuration from the given path. # @parameter path [String] The path to the configuration file. # @returns [Config] The loaded configuration. def self.load(path) config = self.new if File.exist?(path) config.instance_eval(File.read(path), path) end return config end # Load the default configuration. # @returns [Config] The default configuration. def self.default @default ||= self.load(DEFAULT_PATH) end # Prepare the backend, e.g. by loading additional libraries or instrumentation. def prepare end # Require a specific traces backend implementation. def require_backend(env = ENV) if backend = env["TRACES_BACKEND"] begin require(backend) # We ensure that the interface methods replace any existing methods by prepending the module: Traces.singleton_class.prepend(Backend::Interface) return true rescue LoadError => error warn "Unable to load traces backend: #{backend.inspect}!" end end return false end # Load the default configuration. DEFAULT = self.default end end traces-0.18.2/lib/traces/backend.rb0000644000004100000410000000741215062203775017071 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require_relative "config" require_relative "context" module Traces # The backend implementation is responsible for recording and reporting traces. module Backend end # Capture the current trace context for remote propagation. # # This is a default implementation, which can be replaced by the backend. # # You should prefer to use the new `Traces.current_context` family of methods. # # @returns [Object] The current trace context. def self.trace_context nil end # Whether there is an active trace context. # # This is a default implementation, which can be replaced by the backend. # # @returns [Boolean] Whether there is an active trace. def self.active? !!self.trace_context end # Capture the current trace context for local propagation between execution contexts. # # This method returns the current trace context that can be safely passed between threads, fibers, or other execution contexts within the same process. # # The returned object is opaque, in other words, you should not make assumptions about its structure. # # This is a default implementation, which can be replaced by the backend. # # @returns [Context | Nil] The current trace context, or nil if no active trace. def self.current_context trace_context end # Execute a block within a specific trace context for local execution. # # This method is designed for propagating trace context between execution contexts within the same process (threads, fibers, etc.). It temporarily switches to the specified trace context for the duration of the block execution, then restores the previous context. # # When called without a block, permanently switches to the specified context. This enables manual context management for scenarios where automatic restoration isn't desired. # # This is a default implementation, which can be replaced by the backend. # # @parameter context [Context] A trace context obtained from `Traces.current_context`. # @yields {...} If a block is given, the block is executed within the specified trace context. def self.with_context(context) if block_given? # This implementation is not ideal but the best we can do with the current interface. previous_context = self.trace_context begin self.trace_context = context yield ensure self.trace_context = previous_context end else self.trace_context = context end end # Inject trace context into a headers hash for distributed propagation. # # This method adds W3C Trace Context headers (traceparent, tracestate) and W3C Baggage headers to the provided headers hash, enabling distributed tracing across service boundaries. The headers hash is mutated in place. # # This is a default implementation, which can be replaced by the backend. # # @parameter headers [Hash] The headers object to mutate with trace context headers. # @parameter context [Context] A trace context, or nil to use current context. # @returns [Hash | Nil] The headers hash, or nil if no context is available. def self.inject(headers = nil, context = nil) context ||= self.trace_context if context headers ||= Hash.new context.inject(headers) else headers = nil end return headers end # Extract trace context from headers for distributed propagation. # # The returned object is opaque, in other words, you should not make assumptions about its structure. # # This is a default implementation, which can be replaced by the backend. # # @parameter headers [Hash] The headers object containing trace context. # @returns [Context, nil] The extracted trace context, or nil if no valid context found. def self.extract(headers) Context.extract(headers) end Config::DEFAULT.require_backend end traces-0.18.2/lib/traces/context.rb0000644000004100000410000001361715062203775017172 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require "securerandom" module Traces # A generic representation of the current tracing context. class Context # Parse a string representation of a distributed trace. # @parameter parent [String] The parent trace context. # @parameter state [Array(String)] Any attached trace state. def self.parse(parent, state = nil, baggage = nil, **options) version, trace_id, parent_id, flags = parent.split("-") if version == "00" && trace_id && parent_id && flags flags = Integer(flags, 16) if state.is_a?(String) state = state.split(",") end if state state = state.map{|item| item.split("=")}.to_h end if baggage.is_a?(String) baggage = baggage.split(",") end if baggage baggage = baggage.map{|item| item.split("=")}.to_h end self.new(trace_id, parent_id, flags, state, baggage, **options) end end # Create a local trace context which is likely to be globally unique. # @parameter flags [Integer] Any trace context flags. def self.local(flags = 0, **options) self.new(SecureRandom.hex(16), SecureRandom.hex(8), flags, **options) end # Nest a local trace context in an optional parent context. # @parameter parent [Context] An optional parent context. def self.nested(parent, flags = 0) if parent parent.nested(flags) else self.local(flags) end end SAMPLED = 0x01 # Initialize the trace context. # @parameter trace_id [String] The ID of the whole trace forest. # @parameter parent_id [String] The ID of this operation as known by the caller (sometimes referred to as the span ID). # @parameter flags [Integer] An 8-bit field that controls tracing flags such as sampling, trace level, etc. # @parameter state [Hash] Additional vendor-specific trace identification information. # @parameter remote [Boolean] Whether this context was created from a distributed trace header. def initialize(trace_id, parent_id, flags, state = nil, baggage = nil, remote: false) @trace_id = trace_id @parent_id = parent_id @flags = flags @state = state @baggage = baggage @remote = remote end # Create a new nested trace context in which spans can be recorded. def nested(flags = @flags) Context.new(@trace_id, SecureRandom.hex(8), flags, @state, @baggage, remote: @remote) end # The ID of the whole trace forest and is used to uniquely identify a distributed trace through a system. It is represented as a 16-byte array, for example, 4bf92f3577b34da6a3ce929d0e0e4736. All bytes as zero (00000000000000000000000000000000) is considered an invalid value. attr :trace_id # The ID of this operation as known by the caller (in some tracing systems, this is known as the span-id, where a span is the execution of a client operation). It is represented as an 8-byte array, for example, 00f067aa0ba902b7. All bytes as zero (0000000000000000) is considered an invalid value. attr :parent_id # An 8-bit field that controls tracing flags such as sampling, trace level, etc. These flags are recommendations given by the caller rather than strict rules. attr :flags # Provides additional vendor-specific trace identification information across different distributed tracing systems. attr :state # Provides additional application-specific trace identification information across different distributed tracing systems. attr :baggage # Denotes that the caller may have recorded trace data. When unset, the caller did not record trace data out-of-band. def sampled? (@flags & SAMPLED) != 0 end # Whether this context was created from a distributed trace header. def remote? @remote end # A string representation of the trace context (excluding trace state). def to_s "00-#{@trace_id}-#{@parent_id}-#{@flags.to_s(16)}" end # Convert the trace context to a JSON representation, including trace state. def as_json { trace_id: @trace_id, parent_id: @parent_id, flags: @flags, state: @state, baggage: @baggage, remote: @remote } end # Convert the trace context to a JSON string. def to_json(...) as_json.to_json(...) end # Inject the trace context into the headers, including the `"traceparent"`, `"tracestate"`, and `"baggage"` headers. # # @parameter headers [Hash] The headers hash to inject the trace context into. # # @returns [Hash] The modified headers hash. def inject(headers) headers["traceparent"] = self.to_s if @state and !@state.empty? headers["tracestate"] = self.state.map{|key, value| "#{key}=#{value}"}.join(",") end if @baggage and !@baggage.empty? headers["baggage"] = self.baggage.map{|key, value| "#{key}=#{value}"}.join(",") end return headers end # Extract the trace context from the headers. # # The `"traceparent"` header is a string representation of the trace context. If it is an Array, the first element is used, otherwise it is used as is. # The `"tracestate"` header is a string representation of the trace state. If it is a String, it is split on commas before being processed. # The `"baggage"` header is a string representation of the baggage. If it is a String, it is split on commas before being processed. # # @parameter headers [Hash] The headers hash containing trace context. # @returns [Context | Nil] The extracted trace context, or nil if no valid context found. # @raises [ArgumentError] If headers is not a Hash or contains malformed trace data. def self.extract(headers) if traceparent = headers["traceparent"] if traceparent.is_a?(Array) traceparent = traceparent.first end if traceparent.empty? return nil end tracestate = headers["tracestate"] baggage = headers["baggage"] return self.parse(traceparent, tracestate, baggage, remote: true) end end end end traces-0.18.2/lib/traces/provider.rb0000644000004100000410000000157315062203775017336 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require_relative "backend" module Traces # @returns [Boolean] Whether there is an active backend. def self.enabled? Backend.const_defined?(:Interface) end # @namespace module Provider end module Singleton # A module which contains tracing specific wrappers. def traces_provider @traces_provider ||= Module.new end end private_constant :Singleton # Bail out if there is no backend configured. if self.enabled? # Extend the specified class in order to emit traces. def self.Provider(klass, &block) klass.extend(Singleton) provider = klass.traces_provider klass.prepend(provider) provider.module_exec(&block) if block_given? return provider end else def self.Provider(klass, &block) # Tracing disabled. end end end traces-0.18.2/lib/traces/version.rb0000644000004100000410000000022415062203775017161 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. module Traces VERSION = "0.18.2" end traces-0.18.2/lib/traces/backend/0000755000004100000410000000000015062203775016540 5ustar www-datawww-datatraces-0.18.2/lib/traces/backend/capture.rb0000644000004100000410000000472015062203775020533 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2023-2025, by Samuel Williams. require_relative "../context" require "fiber" Fiber.attr_accessor :traces_backend_context module Traces module Backend # A backend which logs all spans to the Capture logger output. module Capture # A span which validates tag assignment. class Span # Initialize a new span. # @parameter context [Context] The context in which the span is recorded. # @parameter name [String] A useful name/annotation for the recorded span. # @parameter resource [String] The "resource" that the span is associated with. # @parameter attributes [Hash] Metadata for the recorded span. def initialize(context, name, attributes) @context = context @name = name @attributes = attributes end attr :context attr :name attr :attributes # Assign some metadata to the span. # @parameter key [String] The metadata key. # @parameter value [Object] The metadata value. Should be coercable to a string. def []= key, value @attributes[key] = value end # Convert the span to a JSON representation. def as_json { name: @name, attributes: @attributes, context: @context.as_json } end # Convert the span to a JSON string. def to_json(...) as_json.to_json(...) end end # All captured spans. def self.spans @spans ||= [] end # The capture backend interface. module Interface # Trace the given block of code and log the execution. # @parameter name [String] A useful name/annotation for the recorded span. # @parameter attributes [Hash] Metadata for the recorded span. def trace(name, attributes: {}, &block) context = Context.nested(Fiber.current.traces_backend_context) Fiber.current.traces_backend_context = context span = Span.new(context, name, attributes) Capture.spans << span yield span end # Assign a trace context to the current execution scope. def trace_context= context Fiber.current.traces_backend_context = context end # Get a trace context from the current execution scope. def trace_context Fiber.current.traces_backend_context end # @returns [Boolean] Whether there is an active trace. def active? !!Fiber.current.traces_backend_context end end end Interface = Capture::Interface end end traces-0.18.2/lib/traces/backend/test.rb0000644000004100000410000000511415062203775020045 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require_relative "../context" require "fiber" Fiber.attr_accessor :traces_backend_context module Traces module Backend # A backend which validates interface usage. module Test # A span which validates tag assignment. class Span # Initialize a new span. # @parameter context [Context] The context in which the span is recorded. def initialize(context) @context = context end # @attribute [Context] The context in which the span is recorded. attr :context # Assign some metadata to the span. # @parameter key [String] The metadata key. # @parameter value [Object] The metadata value. Should be coercable to a string. def []= key, value unless key.is_a?(String) || key.is_a?(Symbol) raise ArgumentError, "Invalid attribute key (must be String or Symbol): #{key.inspect}!" end begin String(value) rescue raise ArgumentError, "Invalid attribute value (must be convertible to String): #{value.inspect}!" end end end # The test backend interface. module Interface # Trace the given block of code and validate the interface usage. # @parameter name [String] A useful name/annotation for the recorded span. # @parameter resource [String] The context in which the trace operation is occuring. # @parameter attributes [Hash] Metadata for the recorded span. def trace(name, attributes: nil, &block) unless block_given? raise ArgumentError, "No block given!" end unless name.is_a?(String) raise ArgumentError, "Invalid name (must be String): #{name.inspect}!" end context = Context.nested(Fiber.current.traces_backend_context) span = Span.new(context) # Ensure the attributes are valid and follow the requirements: attributes&.each do |key, value| span[key] = value end Fiber.current.traces_backend_context = context if block.arity.zero? yield else yield span end end # Assign a trace context to the current execution scope. def trace_context= context Fiber.current.traces_backend_context = context end # Get a trace context from the current execution scope. def trace_context Fiber.current.traces_backend_context end # @returns [Boolean] Whether there is an active trace. def active? !!Fiber.current.traces_backend_context end end end Interface = Test::Interface end end traces-0.18.2/lib/traces/backend/console.rb0000644000004100000410000000410015062203775020522 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require_relative "../context" require "console" require "fiber" Fiber.attr_accessor :traces_backend_context module Traces module Backend # A backend which logs all spans to the console logger output. module Console # A span which validates tag assignment. class Span # Initialize a new span. # @parameter context [Context] The context in which the span is recorded. # @parameter name [String] A useful name/annotation for the recorded span. def initialize(context, name) @context = context @name = name end # @attribute [Context] The context in which the span is recorded. attr :context # Assign some metadata to the span. # @parameter key [String] The metadata key. # @parameter value [Object] The metadata value. Should be coercable to a string. def []= key, value ::Console.logger.info(@context, @name, "#{key} = #{value}") end end # The console backend interface. module Interface # Trace the given block of code and log the execution. # @parameter name [String] A useful name/annotation for the recorded span. # @parameter attributes [Hash] Metadata for the recorded span. def trace(name, attributes: {}, &block) context = Context.nested(Fiber.current.traces_backend_context) Fiber.current.traces_backend_context = context ::Console.logger.info(self, name, attributes) if block.arity.zero? yield else yield Span.new(context, name) end end # Assign a trace context to the current execution scope. def trace_context= context Fiber.current.traces_backend_context = context end # Get a trace context from the current execution scope. def trace_context Fiber.current.traces_backend_context end # @returns [Boolean] Whether there is an active trace. def active? !!Fiber.current.traces_backend_context end end end Interface = Console::Interface end end traces-0.18.2/lib/traces.rb0000644000004100000410000000040415062203775015474 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2025, by Samuel Williams. require_relative "traces/version" require_relative "traces/provider" # @namespace module Traces if self.enabled? Config::DEFAULT.prepare end end traces-0.18.2/checksums.yaml.gz.sig0000444000004100000410000000060015062203775017165 0ustar www-datawww-dataBK mAᨿ$Χxf/vls,NPe!^EY5|ީȀԮ,%?.TuKɥsj Ĕ 2Mg΅ܖ=K3dMYl7q'~채ϴu޾Is献H`sDexCRKX6AsBH%3=[SJnLyHzH#s7(:eBsfCwN9wg`%oy2c5Iq1% ogqtupL)$x OT@|$$bSO F|KQ0G !aƆT ݭ\#Rnqz|ɳ6=枽A!3ő%Ӎ<" ʷS traces-0.18.2/readme.md0000644000004100000410000000746115062203775014711 0ustar www-datawww-data# Traces Capture nested traces during code execution in a vendor agnostic way. [![Development Status](https://github.com/socketry/traces/workflows/Test/badge.svg)](https://github.com/socketry/traces/actions?workflow=Test) ## Features - Zero-overhead if tracing is disabled and minimal overhead if enabled. - Small opinionated interface with standardised semantics, consistent with the [W3C Trace Context Specification](https://github.com/w3c/trace-context). ## Usage Please see the [project documentation](https://socketry.github.io/traces/) for more details. - [Getting Started](https://socketry.github.io/traces/guides/getting-started/index) - This guide explains how to use `traces` for tracing code execution. - [Context Propagation](https://socketry.github.io/traces/guides/context-propagation/index) - This guide explains how to propagate trace context between different execution contexts within your application using `Traces.current_context` and `Traces.with_context`. - [Testing](https://socketry.github.io/traces/guides/testing/index) - This guide explains how to test traces in your code. - [Capture](https://socketry.github.io/traces/guides/capture/index) - This guide explains how to use `traces` for exporting traces from your application. This can be used to document all possible traces. ## Releases Please see the [project releases](https://socketry.github.io/traces/releases/index) for all releases. ### v0.18.1 - Don't call `prepare` in `traces/provider.rb`. It can cause circular loading warnings. ### v0.18.0 - **W3C Baggage Support** - Full support for W3C Baggage specification for application-specific context propagation. - [New Context Propagation Interfaces](https://socketry.github.io/traces/releases/index#new-context-propagation-interfaces) ### v0.17.0 - Remove support for `resource:` keyword argument with no direct replacement – use an attribute instead. ### v0.16.0 - Introduce `traces:provider:list` command to list all available trace providers. ### v0.14.0 - [Introduce `Traces::Config` to Expose `prepare` Hook](https://socketry.github.io/traces/releases/index#introduce-traces::config-to-expose-prepare-hook) ## Contributing We welcome contributions to this project. 1. Fork it. 2. Create your feature branch (`git checkout -b my-new-feature`). 3. Commit your changes (`git commit -am 'Add some feature'`). 4. Push to the branch (`git push origin my-new-feature`). 5. Create new Pull Request. ### Developer Certificate of Origin In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed. ### Community Guidelines This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers. ## See Also - [traces-backend-open\_telemetry](https://github.com/socketry/traces-backend-open_telemetry) — A backend for submitting traces to [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-ruby), including [ScoutAPM](https://github.com/scoutapp/scout_apm_ruby). - [traces-backend-datadog](https://github.com/socketry/traces-backend-datadog) — A backend for submitting traces to [Datadog](https://github.com/DataDog/dd-trace-rb). - [traces-backend-newrelic](https://github.com/newrelic/traces-backend-newrelic) - A backend for submitting traces to [New Relic](https://github.com/newrelic/newrelic-ruby-agent). - [metrics](https://github.com/socketry/metrics) — A metrics interface which follows a similar pattern. traces-0.18.2/data.tar.gz.sig0000444000004100000410000000060015062203775015735 0ustar www-datawww-dataBfKvFbV|meb L :X<<ODE(O:%\ V[˚<|}JMI>ADf6u.@jQ|%f hfIWf m.]%mƙպ "7$ |0޴?Ƹ7'q#2 +Fݎ|9Fgqp&m\yNNoPEN/I*盃|)LٴM@0ԁےbhIBDZӓ] `XیX#`udY5=ly2 pO^&0%g9!KkB{-">aAžP dG5% S 9P??\zL >'= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "documentation_uri" => "https://socketry.github.io/traces/", "source_code_uri" => "https://github.com/socketry/traces.git" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Samuel Williams".freeze, "Felix Yan".freeze, "Zach Taylor".freeze] s.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11\nZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK\nCZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz\nMjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd\nMBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj\nbzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB\nigKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2\n9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW\nsGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE\ne5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN\nXibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss\nRZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn\ntUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM\nzp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW\nxm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O\nBBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE\ncBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl\nxCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/\nc1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp\n8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws\nJkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP\neX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt\nQ2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8\nvoD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=\n-----END CERTIFICATE-----\n".freeze] s.date = "1980-01-02" s.files = ["bake/traces/capture.rb".freeze, "bake/traces/provider.rb".freeze, "context/capture.md".freeze, "context/context-propagation.md".freeze, "context/getting-started.md".freeze, "context/index.yaml".freeze, "context/testing.md".freeze, "lib/traces.rb".freeze, "lib/traces/backend.rb".freeze, "lib/traces/backend/capture.rb".freeze, "lib/traces/backend/console.rb".freeze, "lib/traces/backend/test.rb".freeze, "lib/traces/config.rb".freeze, "lib/traces/context.rb".freeze, "lib/traces/provider.rb".freeze, "lib/traces/version.rb".freeze, "license.md".freeze, "readme.md".freeze, "releases.md".freeze] s.homepage = "https://github.com/socketry/traces".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze) s.rubygems_version = "3.6.9".freeze s.summary = "Application instrumentation and tracing.".freeze end traces-0.18.2/bake/0000755000004100000410000000000015062203775014024 5ustar www-datawww-datatraces-0.18.2/bake/traces/0000755000004100000410000000000015062203775015305 5ustar www-datawww-datatraces-0.18.2/bake/traces/capture.rb0000644000004100000410000000053315062203775017276 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2023-2025, by Samuel Williams. # Enable capturing traces. def capture ENV["TRACES_BACKEND"] = "traces/backend/capture" require "traces" end # Generate a list of traces that have been captured. def list Traces::Backend::Capture.spans.sort_by!{|span| span.name} end traces-0.18.2/bake/traces/provider.rb0000644000004100000410000000074115062203775017466 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2025, by Samuel Williams. # List available providers for traces from all loaded gems. def list available = {} Gem.loaded_specs.each do |name, spec| spec.require_paths.each do |require_path| root = File.expand_path(require_path, spec.full_gem_path) Dir.glob("traces/provider/**/*.rb", base: root).each do |path| (available[name] ||= []) << path end end end return available end traces-0.18.2/metadata.gz.sig0000444000004100000410000000060015062203775016017 0ustar www-datawww-datam|/˾F$yW1$qG-* %3\NJrf9+4JqR;&~# 񟌈h϶J.wIIvG{J djqm8ˏ(~A87EQfEG01:MyZ4-+H 0R~^rivYlnNK (D܈ & ü놛3 6< N8MQKKv`pd0,8Rm#2 z*bɵ7bÙ<ȹ/ 6~=L7h2غy[isG5JکkA?ɕvB}^e7(9Tk}V:v'1b"/ltraces-0.18.2/releases.md0000644000004100000410000000467715062203775015265 0ustar www-datawww-data# Releases ## v0.18.1 - Don't call `prepare` in `traces/provider.rb`. It can cause circular loading warnings. ## v0.18.0 - **W3C Baggage Support** - Full support for W3C Baggage specification for application-specific context propagation. ### New Context Propagation Interfaces `Traces#trace_context` and `Traces.trace_context` are insufficient for efficient inter-process tracing when using OpenTelemetry. That is because OpenTelemetry has it's own "Context" concept with arbitrary key-value storage (of which the current span is one such key/value pair). Unfortunately, OpenTelemetry requires those values to be propagated "inter-process" while ignores them for "intra-process" tracing. Therefore, in order to propagate this context, we introduce 4 new methods: - `Traces.current_context` - Capture the current trace context for local propagation between execution contexts (threads, fibers). - `Traces.with_context(context)` - Execute code within a specific trace context, with automatic restoration when used with blocks. - `Traces.inject(headers = nil, context = nil)` - Inject W3C Trace Context headers into a headers hash for distributed propagation. - `Traces.extract(headers)` - Extract trace context from W3C Trace Context headers. The default implementation is built on top of `Traces.trace_context`, however these methods can be replaced by the backend. In that case, the `context` object is opaque, in other words it is library-specific, and you should not assume it is an instance of `Traces::Context`. ## v0.17.0 - Remove support for `resource:` keyword argument with no direct replacement – use an attribute instead. ## v0.16.0 - Introduce `traces:provider:list` command to list all available trace providers. ## v0.14.0 ### Introduce `Traces::Config` to Expose `prepare` Hook The `traces` gem uses aspect-oriented programming to wrap existing methods to emit traces. However, while there are some reasonable defaults for emitting traces, it can be useful to customize the behavior and level of detail. To that end, the `traces` gem now optionally loads a `config/traces.rb` which includes a `prepare` hook that can be used to load additional providers. ``` ruby # config/traces.rb def prepare require 'traces/provider/async' require 'traces/provider/async/http' end ``` The `prepare` method is called immediately after the traces backend is loaded. You can require any provider you want in this file, or even add your own custom providers. traces-0.18.2/context/0000755000004100000410000000000015062203775014606 5ustar www-datawww-datatraces-0.18.2/context/index.yaml0000644000004100000410000000206015062203775016577 0ustar www-datawww-data# Automatically generated context index for Utopia::Project guides. # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`. --- description: Application instrumentation and tracing. metadata: documentation_uri: https://socketry.github.io/traces/ source_code_uri: https://github.com/socketry/traces.git files: - path: getting-started.md title: Getting Started description: This guide explains how to use `traces` for tracing code execution. - path: context-propagation.md title: Context Propagation description: This guide explains how to propagate trace context between different execution contexts within your application using `Traces.current_context` and `Traces.with_context`. - path: testing.md title: Testing description: This guide explains how to test traces in your code. - path: capture.md title: Capture description: This guide explains how to use `traces` for exporting traces from your application. This can be used to document all possible traces. traces-0.18.2/context/testing.md0000644000004100000410000000156115062203775016610 0ustar www-datawww-data# Testing This guide explains how to test traces in your code. ## Expectations One approach to testing traces are emitted, is by using mocks to verify that methods are called with the expected arguments. ```ruby it "should trace the operation" do expect(Traces).to receive(:trace).with("my_controller.do_something") my_controller.do_something end ``` This is generally a good appoach for testing that specific traces are emitted. ## Validation The traces gem supports a variety of backends, and each backend may have different requirements for the data that is submitted. The test backend is designed to be used for testing that the data submitted is valid. ```ruby ENV['TRACES_BACKEND'] = 'traces/backend/test' require 'traces' Traces.trace(5) do puts "Hello" end # => lib/traces/backend/test.rb:52:in `trace': Invalid name (must be String): 5! (ArgumentError) ``` traces-0.18.2/context/context-propagation.md0000644000004100000410000001317115062203775021140 0ustar www-datawww-data# Context Propagation This guide explains how to propagate trace context between different execution contexts within your application using `Traces.current_context` and `Traces.with_context`. ## Overview The `traces` library provides two complementary approaches for managing trace context: - **Local context propagation** (`Traces.current_context` / `Traces.with_context`): For passing context between execution contexts within the same process (threads, fibers, async tasks). - **Distributed context propagation** (`Traces.inject` / `Traces.extract`): For transmitting context across process and service boundaries via serialization (HTTP headers, message metadata, etc.). There is a legacy interface `Traces.trace_context` and `Traces.trace_context=` but you should prefer to use the new methods outlined above. ## Local Context Propagation Local context propagation involves passing trace context between different execution contexts within the same process. This is essential for maintaining trace continuity when code execution moves between threads, fibers, async tasks, or other concurrent execution contexts. Unlike distributed propagation which requires serialization over network boundaries, local propagation uses Context objects directly. ### Capturing the Current Context Use `Traces.current_context` to capture the current trace context as a Context object: ~~~ ruby current_context = Traces.current_context # Returns a Traces::Context object or nil if no active trace ~~~ ### Using the Context Use `Traces.with_context(context)` to execute code within a specific trace context: ~~~ ruby # With block (automatic restoration): Traces.with_context(context) do # Code runs with the specified context. end # Without block (permanent switch): Traces.with_context(context) # Context remains active. ~~~ ### Use Cases #### Thread-Safe Context Propagation When spawning background threads, you often want them to inherit the current trace context: ~~~ ruby require 'traces' # Main thread has active tracing Traces.trace("main_operation") do # Capture current context before spawning thread: current_context = Traces.current_context # Spawn background thread: Thread.new do # Restore context in the new thread: Traces.with_context(current_context) do # This thread now has the same trace context as main thread: Traces.trace("background_work") do perform_heavy_computation end end end.join end ~~~ #### Fiber-Based Async Operations For fiber-based concurrency (like in async frameworks), context propagation ensures trace continuity: ~~~ ruby require 'traces' Traces.trace("main_operation") do current_context = Traces.current_context # Create fiber for async work: fiber = Fiber.new do Traces.with_context(current_context) do # Fiber inherits the trace context: Traces.trace("fiber_work") do perform_async_operation end end end fiber.resume end ~~~ ### Context Propagation vs. New Spans Remember that context propagation maintains the same trace, while `trace()` creates new spans: ~~~ ruby Traces.trace("parent") do context = Traces.current_context Thread.new do # This maintains the same trace context: Traces.with_context(context) do # This creates a NEW span within the same trace: Traces.trace("child") do # Child span, same trace as parent end end end end ~~~ ## Distributed Context Propagation Distributed context propagation involves transmitting trace context across process and service boundaries. Unlike local propagation which works within a single process, distributed propagation requires serializing context data and transmitting it over network protocols. ### Injecting Context into Headers Use `Traces.inject(headers, context = nil)` to add W3C Trace Context headers to a headers hash for transmission over network boundaries: ~~~ ruby require 'traces' # Capture current context: context = Traces.current_context headers = {'Content-Type' => 'application/json'} # Inject trace headers: Traces.inject(headers, context) # headers now contains: {'Content-Type' => '...', 'traceparent' => '00-...'} # Or use current context by default: Traces.inject(headers) # Uses current trace context ~~~ ### Extracting Context from Headers Use `Traces.extract(headers)` to extract trace context from W3C headers received over the network: ~~~ ruby # Receive headers from incoming request: incoming_headers = request.headers # Extract context: context = Traces.extract(incoming_headers) # Returns a Traces::Context object or nil if no valid context # Use the extracted context: if context Traces.with_context(context) do # Process request with distributed trace context end end ~~~ ### Use Cases #### Outgoing HTTP Requests ~~~ ruby require 'traces' class ApiClient def make_request(endpoint, data) Traces.trace("api_request", attributes: {endpoint: endpoint}) do headers = { 'content-type' => 'application/json' } # Add trace context to outgoing request: Traces.inject(headers) http_client.post(endpoint, body: data.to_json, headers: headers ) end end end ~~~ #### Incoming HTTP Requests ~~~ ruby require 'traces' class WebController def handle_request(request) # Extract trace context from incoming headers: context = Traces.extract(request.headers) # Process request with inherited context: if context Traces.with_context(context) do Traces.trace("web_request", attributes: { path: request.path, method: request.method }) do process_business_logic end end else Traces.trace("web_request", attributes: { path: request.path, method: request.method }) do process_business_logic end end end end ~~~ traces-0.18.2/context/capture.md0000644000004100000410000000163315062203775016576 0ustar www-datawww-data# Capture This guide explains how to use `traces` for exporting traces from your application. This can be used to document all possible traces. ## With Test Suite If your application defines one or more traces and emits them as part of a test suite, you can export them using the `bake traces:capture` command. ```bash $ cd test/traces/backend/.capture/ $ bake traces:capture run traces:capture:list output --format json [ { "name": "my_trace", "attributes": { "foo": "baz" }, "context": { "trace_id": "038d110379a499a8ebcfb2b77cd69e1a", "parent_id": "bf134b25de4f4a82", "flags": 0, "state": null, "remote": false } }, { "name": "nested", "attributes": { }, "context": { "trace_id": "038d110379a499a8ebcfb2b77cd69e1a", "parent_id": "2dd5510eb8fffc5f", "flags": 0, "state": null, "remote": false } } ] ``` traces-0.18.2/context/getting-started.md0000644000004100000410000000634215062203775020242 0ustar www-datawww-data# Getting Started This guide explains how to use `traces` for tracing code execution. ## Installation Add the gem to your project: ~~~ bash $ bundle add traces ~~~ ## Core Concepts `traces` has several core concepts: - A {ruby Traces::Provider} which implements custom logic for wrapping existing code in traces. - A {ruby Traces::Context} which represents the current tracing environment which can include distributed tracing. - A {ruby Traces::Backend} which connects traces to a specific backend system for processing. ## Usage There are two main aspects to integrating within this gem. 1. Libraries and applications must provide traces. 2. Those traces must be consumed or emitted somewhere. ### Providing Traces Adding tracing to libraries requires the use of {ruby Traces::Provider}: ~~~ ruby require 'traces' class MyClass def my_method puts "Hello World" end end # If tracing is disabled, this is a no-op. Traces::Provider(MyClass) do def my_method attributes = { 'foo' => 'bar' } Traces.trace('my_method', attributes: attributes) do super end end end MyClass.new.my_method ~~~ This code by itself will not create any traces. In order to execute it and output traces, you must set up a backend to consume them. In addition, to trace class methods: ~~~ ruby require 'traces' class MyClass def self.my_method puts "Hello World" end end # If tracing is disabled, this is a no-op. Traces::Provider(MyClass.singleton_class) do def my_method attributes = { 'foo' => 'bar' } Traces.trace('my_method', attributes: attributes) do super end end end MyClass.my_method ~~~ ### Consuming Traces Consuming traces means proving a backend implementation which can emit those traces to some log or service. There are several options, but two backends are included by default: - `traces/backend/test` does not emit any traces, but validates the usage of the tracing interface. - `traces/backend/console` emits traces using the [`console`](https://github.com/socketry/console) gem. In order to use a specific backend, set the `TRACES_BACKEND` environment variable, e.g. ~~~ shell $ TRACES_BACKEND=traces/backend/console ./my_script.rb ~~~ Separate implementations are provided for specific APMs: - [OpenTelemetry](https://github.com/socketry/traces-backend-open_telemetry) - [Datadog](https://github.com/socketry/traces-backend-datadog) - [New Relic](https://github.com/newrelic/traces-backend-newrelic) ### Configuration By default, you may not have many traces available, as they are typically opt-in. To enable more traces, create a `config/traces.rb` file in your project root and require the providers you want to use: ```ruby # config/traces.rb def prepare require "traces/provider/async" require "traces/provider/async/pool" end ``` To get a list of all available providers, you can use the `bake` command: ~~~ shell $ bundle exec bake traces:provider:list {"async" => ["traces/provider/async/barrier.rb", "traces/provider/async/task.rb", "traces/provider/async.rb"], "async-pool" => ["traces/provider/async/pool/controller.rb"], "protocol-http2" => ["traces/provider/protocol/http2/framer.rb", "traces/provider/protocol/http2.rb"]} ~~~ You can then add the providers you want to use to your `config/traces.rb` file.