pax_global_header00006660000000000000000000000064143330443750014520gustar00rootroot0000000000000052 comment=db8d5e25dabec83426d2a28f11ac9156c757f8d9 ruby-traces-0.8.0/000077500000000000000000000000001433304437500137655ustar00rootroot00000000000000ruby-traces-0.8.0/.editorconfig000066400000000000000000000000641433304437500164420ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-traces-0.8.0/.github/000077500000000000000000000000001433304437500153255ustar00rootroot00000000000000ruby-traces-0.8.0/.github/workflows/000077500000000000000000000000001433304437500173625ustar00rootroot00000000000000ruby-traces-0.8.0/.github/workflows/coverage.yaml000066400000000000000000000023441433304437500220440ustar00rootroot00000000000000name: Coverage on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm COVERAGE: PartialSummary jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest env: TRACES_BACKEND: ${{matrix.backend}} strategy: matrix: os: - ubuntu - macos ruby: - "3.1" backend: - 'traces/backend/test' - 'traces/backend/console' steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: bundle exec bake test - uses: actions/upload-artifact@v2 with: name: coverage-${{matrix.os}}-${{matrix.ruby}} path: .covered.db validate: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: "3.1" bundler-cache: true - uses: actions/download-artifact@v3 - name: Validate coverage timeout-minutes: 5 run: bundle exec bake covered:validate --paths */.covered.db \; ruby-traces-0.8.0/.github/workflows/documentation.yaml000066400000000000000000000023171433304437500231220ustar00rootroot00000000000000name: Documentation on: push: branches: - main # Allows you to run this workflow manually from the Actions tab: workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages: permissions: contents: read pages: write id-token: write # Allow one concurrent deployment: concurrency: group: "pages" cancel-in-progress: true env: CONSOLE_OUTPUT: XTerm BUNDLE_WITH: maintenance jobs: generate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: "3.1" bundler-cache: true - name: Installing packages run: sudo apt-get install wget - name: Generate documentation timeout-minutes: 5 run: bundle exec bake utopia:project:static --force no - name: Upload documentation artifact uses: actions/upload-pages-artifact@v1 with: path: docs deploy: runs-on: ubuntu-latest environment: name: github-pages url: ${{steps.deployment.outputs.page_url}} needs: generate steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 ruby-traces-0.8.0/.github/workflows/test-external.yaml000066400000000000000000000011461433304437500230470ustar00rootroot00000000000000name: Test External on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest strategy: matrix: os: - ubuntu - macos ruby: - "2.7" - "3.0" - "3.1" steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 10 run: bundle exec bake test:external ruby-traces-0.8.0/.github/workflows/test.yaml000066400000000000000000000020461433304437500212270ustar00rootroot00000000000000name: Test on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} strategy: matrix: os: - ubuntu - macos ruby: - "2.7" - "3.0" - "3.1" experimental: [false] include: - os: ubuntu ruby: truffleruby experimental: true - os: ubuntu ruby: jruby experimental: true - os: ubuntu ruby: head experimental: true - os: ubuntu ruby: 3.1 env: TRACES_BACKEND: "traces/backend/test" steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 10 run: bundle exec bake test ruby-traces-0.8.0/.gitignore000066400000000000000000000000641433304437500157550ustar00rootroot00000000000000/.bundle/ /pkg/ /gems.locked /.covered.db /external ruby-traces-0.8.0/.rspec000066400000000000000000000000651433304437500151030ustar00rootroot00000000000000--format documentation --color --require spec_helper ruby-traces-0.8.0/benchmark/000077500000000000000000000000001433304437500157175ustar00rootroot00000000000000ruby-traces-0.8.0/benchmark/rebind_vs_prepend.rb000066400000000000000000000022431433304437500217350ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. require 'benchmark/ips' class MyClass1 def my_method true end end module Tracer1 def my_method(...) super end end MyClass1.prepend(Tracer1) class MyClass2 def my_method true end end module Tracer2 def trace(name) original_method = instance_method(name) trace_provider = @trace_provider remove_method(name) define_method(name) do |*arguments, &block| original_method.bind(self).call(*arguments, &block) end ruby2_keywords(name) end end MyClass2.extend(Tracer2) MyClass2.trace(:my_method) module Trace module Provider def trace_provider @trace_provider ||= Module.new end def trace(name) trace_provider.module_eval "def #{name}(...); super; end" end end def self.Provider(klass) klass.extend(Provider) klass.prepend(klass.trace_provider) end end class MyClass3 Trace::Provider(self) trace def my_method true end end Benchmark.ips do |x| x.report("MyClass1", "MyClass1.new.my_method") x.report("MyClass2", "MyClass2.new.my_method") x.report("MyClass3", "MyClass3.new.my_method") x.compare! end ruby-traces-0.8.0/config/000077500000000000000000000000001433304437500152325ustar00rootroot00000000000000ruby-traces-0.8.0/config/external.yaml000066400000000000000000000001551433304437500177410ustar00rootroot00000000000000traces-backend-datadog: url: https://github.com/socketry/traces-backend-datadog command: bundle exec sus ruby-traces-0.8.0/config/sus.rb000066400000000000000000000003101433304437500163630ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. ENV['TRACES_BACKEND'] ||= 'traces/backend/console' require 'covered/sus' include Covered::Sus ruby-traces-0.8.0/gems.rb000066400000000000000000000005331433304437500152460ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. # Copyright, 2022, by Felix Yan. source 'https://rubygems.org' gemspec group :maintenance, optional: true do gem "bake-modernize" gem "bake-gem" gem "bake-github-pages" gem "utopia-project" end group :test do gem "console" end ruby-traces-0.8.0/guides/000077500000000000000000000000001433304437500152455ustar00rootroot00000000000000ruby-traces-0.8.0/guides/getting-started/000077500000000000000000000000001433304437500203525ustar00rootroot00000000000000ruby-traces-0.8.0/guides/getting-started/README.md000066400000000000000000000037401433304437500216350ustar00rootroot00000000000000# 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' } 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. ### 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) ruby-traces-0.8.0/guides/links.yaml000066400000000000000000000000341433304437500172460ustar00rootroot00000000000000getting-started: order: 1 ruby-traces-0.8.0/lib/000077500000000000000000000000001433304437500145335ustar00rootroot00000000000000ruby-traces-0.8.0/lib/traces.rb000066400000000000000000000002631433304437500163420ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. require_relative 'traces/version' require_relative 'traces/provider' ruby-traces-0.8.0/lib/traces/000077500000000000000000000000001433304437500160145ustar00rootroot00000000000000ruby-traces-0.8.0/lib/traces/backend.rb000066400000000000000000000004461433304437500177340ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. module Traces # Require a specific trace backend. def self.require_backend(env = ENV) if backend = env['TRACES_BACKEND'] require(backend) end end end Traces.require_backend ruby-traces-0.8.0/lib/traces/backend/000077500000000000000000000000001433304437500174035ustar00rootroot00000000000000ruby-traces-0.8.0/lib/traces/backend/console.rb000066400000000000000000000033411433304437500213730ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. require_relative '../context' require 'console' require 'fiber' class Fiber attr_accessor :traces_backend_context end 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 def initialize(context, instance, name) @context = context @instance = instance @name = name end 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 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, resource: self, attributes: {}, &block) context = Context.nested(Fiber.current.traces_backend_context) Fiber.current.traces_backend_context = context ::Console.logger.info(resource, name, attributes) if block.arity.zero? yield else yield Span.new(context, self, 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 end end Interface = Console::Interface end end ruby-traces-0.8.0/lib/traces/backend/test.rb000066400000000000000000000044431433304437500207140ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. require_relative '../context' require 'fiber' class Fiber attr_accessor :traces_backend_context end module Traces module Backend # A backend which validates interface usage. module Test # A span which validates tag assignment. class Span def initialize(context) @context = context end 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 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 attributes [Hash] Metadata for the recorded span. def trace(name, resource: self.class.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 if resource # It should be convertable: resource = resource.to_s 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 end end Interface = Test::Interface end end ruby-traces-0.8.0/lib/traces/context.rb000066400000000000000000000060721433304437500200320ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, 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, **options) version, trace_id, parent_id, flags = parent.split('-') if version == '00' flags = Integer(flags, 16) if state.is_a?(String) state = state.split(',') end if state state = state.map{|item| item.split('=')}.to_h end self.new(trace_id, parent_id, flags, state, **options) end end # Create a local trace context which is likley 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 def initialize(trace_id, parent_id, flags, state = nil, remote: false) @trace_id = trace_id @parent_id = parent_id @flags = flags @state = state @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, 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 request 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 request). 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. Conveys information about the request’s position in multiple distributed tracing graphs. attr :state # 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 end end ruby-traces-0.8.0/lib/traces/provider.rb000066400000000000000000000016301433304437500201730ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. require_relative 'backend' module Traces # @returns [Boolean] Whether there is an active backend. def self.enabled? self.const_defined?(:Backend) end 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 provider.prepend(Backend::Interface) klass.prepend(provider) provider.module_exec(&block) if block_given? return provider end else def self.Provider(klass, &block) # Tracing disabled. end end end ruby-traces-0.8.0/lib/traces/version.rb000066400000000000000000000002231433304437500200230ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. module Traces VERSION = "0.8.0" end ruby-traces-0.8.0/license.md000066400000000000000000000021341433304437500157310ustar00rootroot00000000000000# MIT License Copyright, 2021-2022, by Samuel Williams. Copyright, 2022, by Felix Yan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-traces-0.8.0/readme.md000066400000000000000000000025741433304437500155540ustar00rootroot00000000000000# 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). ## 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 ## 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). - [metrics](https://github.com/socketry/metrics) — A metrics interface which follows a similar pattern. ruby-traces-0.8.0/release.cert000066400000000000000000000033141433304437500162650ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11 ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8 voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg= -----END CERTIFICATE----- ruby-traces-0.8.0/test/000077500000000000000000000000001433304437500147445ustar00rootroot00000000000000ruby-traces-0.8.0/test/traces.rb000066400000000000000000000064361433304437500165630ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. require 'traces' class MyClass def my_method(argument) argument end def my_method_with_result(result) result end def my_method_with_attributes(attributes) attributes end end class MySubClass < MyClass def my_method(argument) super * 2 end def my_other_method(argument) argument end end Traces::Provider(MyClass) do def my_method(argument) trace('my_method', attributes: {argument: argument}) {super} end def my_method_with_result(result) trace('my_method_with_result') do |span| super.tap do |result| span["result"] = result end end end def my_method_with_attributes(attributes) trace('my_method_with_attributes', attributes: attributes) {super} end end Traces::Provider(MySubClass) do def my_other_method(argument) trace('my_other_method', attributes: {argument: argument}) {super} end end describe Traces do it "has a version number" do expect(Traces::VERSION).to be =~ /\d+\.\d+\.\d+/ end describe MyClass do let(:instance) {MyClass.new} it "can invoke trace wrapper" do expect(instance).to receive(:trace) expect(instance.my_method(10)).to be == 10 end with 'result' do let(:result) {"result"} it "can invoke trace wrapper" do expect(instance).to receive(:trace) expect(instance.my_method_with_result(result)).to be == result end end with 'attributes' do let(:attributes) {{"name" => "value"}} it "can invoke trace wrapper" do expect(instance).to receive(:trace) expect(instance.my_method_with_attributes(attributes)).to be == attributes end end with 'parent trace context' do let(:context) {Traces::Context.local} it "can create child trace context" do instance.trace_context = context expect(instance.trace_context).to be == context end end end describe MySubClass do let(:instance) {MySubClass.new} it "can invoke trace wrapper" do expect(instance).to receive(:trace) expect(instance.my_method(10)).to be == 20 end it "does not affect the base class" do expect(MyClass.new).not.to respond_to(:my_other_method) end end end if defined?(Traces::Backend::Test) describe Traces do let(:instance) {MyClass.new} with 'invalid attribute key' do let(:attributes) {{Object.new => "value"}} it "fails with exception" do expect(instance).to receive(:trace) expect do instance.my_method_with_attributes(attributes) end.to raise_exception(ArgumentError) end end with 'invalid attribute value' do let(:value) do Object.new.tap do |object| object.singleton_class.undef_method :to_s end end let(:attributes) {{"key" => value}} it "fails with exception" do expect(instance).to receive(:trace) expect do instance.my_method_with_attributes(attributes) end.to raise_exception(ArgumentError) end end with 'missing block' do it "fails with exception" do expect do instance.trace('foo') end.to raise_exception(ArgumentError) end end with 'invalid name' do it "fails with exception" do expect do instance.trace(Object.new) {} end.to raise_exception(ArgumentError) end end end end ruby-traces-0.8.0/test/traces/000077500000000000000000000000001433304437500162255ustar00rootroot00000000000000ruby-traces-0.8.0/test/traces/context.rb000066400000000000000000000035341433304437500202430ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. require 'traces/context' describe Traces::Context do let(:trace_id) {"496e95c5964f7cb924fc820a469a9f74"} let(:parent_id) {"ae9b1d95d29fe974"} let(:trace_parent) {"00-496e95c5964f7cb924fc820a469a9f74-ae9b1d95d29fe974-0"} let(:flags) {0} with '.local' do it 'should create a trace context' do context = Traces::Context.local expect(context.trace_id).to be =~ /\h{32}/ expect(context.parent_id).to be =~ /\h{16}/ expect(context.flags).to be == 0 expect(context.state).to be == nil end end with '#nested' do it 'can nest contexts' do parent = Traces::Context.local child = parent.nested expect(parent.trace_id).to be == child.trace_id end end with '.nested' do with 'a local parent context' do it 'can nest contexts' do parent = Traces::Context.local child = Traces::Context.nested(parent) expect(parent.trace_id).to be == child.trace_id end end with 'no parent context' do it 'can nest contexts' do child = Traces::Context.nested(nil) expect(child).not.to be == nil end end end with '#to_s' do it 'can be converted to string' do context = Traces::Context.new(trace_id, parent_id, 0) expect(context.to_s).to be == trace_parent end end with '.parse' do let(:trace_state) {nil} let(:context) {Traces::Context.parse(trace_parent, trace_state)} it 'can extract trace context from string' do expect(context.trace_id).to be == trace_id expect(context.parent_id).to be == parent_id expect(context.flags).to be == flags expect(context.sampled?).to be == false end with 'trace state', trace_state: 'foo=bar' do it 'can extract trace context from string' do expect(context.state).to be == {'foo' => 'bar'} end end end end ruby-traces-0.8.0/test/traces/provider.rb000066400000000000000000000015021433304437500204020ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. unless ENV['TRACES_BACKEND'] abort "No backend specified, tests will fail!" end require 'traces/provider' describe Traces::Provider do let(:my_class) {Class.new} it "can yield span" do Traces::Provider(my_class) do def make_span trace('test.span') do |span| return span end end end span = my_class.new.make_span span["key"] = "value" end it "works without a block" do provider = Traces::Provider(my_class) expect(provider).to be_equal(my_class.traces_provider) end it "can get current trace context" do Traces::Provider(my_class) do def span trace('test.span') do |span| return trace_context end end end trace_context = my_class.new.span end end ruby-traces-0.8.0/traces.gemspec000066400000000000000000000013641433304437500166170ustar00rootroot00000000000000# frozen_string_literal: true require_relative "lib/traces/version" Gem::Specification.new do |spec| spec.name = "traces" spec.version = Traces::VERSION spec.summary = "Application instrumentation and tracing." spec.authors = ["Samuel Williams", "Felix Yan"] spec.license = "MIT" spec.cert_chain = ['release.cert'] spec.signing_key = File.expand_path('~/.gem/release.pem') spec.homepage = "https://github.com/socketry/traces" spec.files = Dir.glob(['{lib}/**/*', '*.md'], File::FNM_DOTMATCH, base: __dir__) spec.add_development_dependency "bake-test", "~> 0.2" spec.add_development_dependency "bake-test-external", "~> 0.2" spec.add_development_dependency "covered", "~> 0.16" spec.add_development_dependency "sus", "~> 0.13" end