traces 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/traces/backend/test.rb +1 -2
- data/lib/traces/backend.rb +81 -0
- data/lib/traces/config.rb +6 -6
- data/lib/traces/context.rb +65 -7
- data/lib/traces/version.rb +1 -1
- data/lib/traces.rb +1 -0
- data/readme.md +7 -0
- data/releases.md +17 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0ef616a2189f63b63abe00e1dc84c0d13b48dc0099c90b4c5aa5ea420ea92fa
|
4
|
+
data.tar.gz: 310a045915e9831698ca681c61d53a6175a00a9b34e44e4d9b92ff8b6917dc51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d5ab804b31b9440fd4cc36af5dfb9bd553d370681417f6441e86bcaeed6a41e7b02eff4f771144fac8f3fa528930bb457a7b60b902c2fe40b26e26ea901eb1e
|
7
|
+
data.tar.gz: 35918ff43dfac6158cb42dea57942a491b58ed906d67a5de03bd0bfffaa3cbda7e4ade3edd2e3c0ce1774a2c9e8caede58c8cb97b5bcf5c2cd5a1d921dc4d7d9
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/traces/backend/test.rb
CHANGED
data/lib/traces/backend.rb
CHANGED
@@ -4,23 +4,104 @@
|
|
4
4
|
# Copyright, 2021-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "config"
|
7
|
+
require_relative "context"
|
7
8
|
|
8
9
|
module Traces
|
9
10
|
# The backend implementation is responsible for recording and reporting traces.
|
10
11
|
module Backend
|
11
12
|
end
|
12
13
|
|
14
|
+
# Capture the current trace context for remote propagation.
|
15
|
+
#
|
13
16
|
# This is a default implementation, which can be replaced by the backend.
|
17
|
+
#
|
18
|
+
# You should prefer to use the new `Traces.current_context` family of methods.
|
19
|
+
#
|
14
20
|
# @returns [Object] The current trace context.
|
15
21
|
def self.trace_context
|
16
22
|
nil
|
17
23
|
end
|
18
24
|
|
25
|
+
# Whether there is an active trace context.
|
26
|
+
#
|
19
27
|
# This is a default implementation, which can be replaced by the backend.
|
28
|
+
#
|
20
29
|
# @returns [Boolean] Whether there is an active trace.
|
21
30
|
def self.active?
|
22
31
|
!!self.trace_context
|
23
32
|
end
|
24
33
|
|
34
|
+
# Capture the current trace context for local propagation between execution contexts.
|
35
|
+
#
|
36
|
+
# This method returns the current trace context that can be safely passed between threads, fibers, or other execution contexts within the same process.
|
37
|
+
#
|
38
|
+
# The returned object is opaque, in other words, you should not make assumptions about its structure.
|
39
|
+
#
|
40
|
+
# This is a default implementation, which can be replaced by the backend.
|
41
|
+
#
|
42
|
+
# @returns [Context | Nil] The current trace context, or nil if no active trace.
|
43
|
+
def self.current_context
|
44
|
+
trace_context
|
45
|
+
end
|
46
|
+
|
47
|
+
# Execute a block within a specific trace context for local execution.
|
48
|
+
#
|
49
|
+
# 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.
|
50
|
+
#
|
51
|
+
# When called without a block, permanently switches to the specified context. This enables manual context management for scenarios where automatic restoration isn't desired.
|
52
|
+
#
|
53
|
+
# This is a default implementation, which can be replaced by the backend.
|
54
|
+
#
|
55
|
+
# @parameter context [Context] A trace context obtained from `Traces.current_context`.
|
56
|
+
# @yields {...} If a block is given, the block is executed within the specified trace context.
|
57
|
+
def self.with_context(context)
|
58
|
+
if block_given?
|
59
|
+
# This implementation is not ideal but the best we can do with the current interface.
|
60
|
+
previous_context = self.trace_context
|
61
|
+
begin
|
62
|
+
self.trace_context = context
|
63
|
+
yield
|
64
|
+
ensure
|
65
|
+
self.trace_context = previous_context
|
66
|
+
end
|
67
|
+
else
|
68
|
+
self.trace_context = context
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Inject trace context into a headers hash for distributed propagation.
|
73
|
+
#
|
74
|
+
# 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.
|
75
|
+
#
|
76
|
+
# This is a default implementation, which can be replaced by the backend.
|
77
|
+
#
|
78
|
+
# @parameter headers [Hash] The headers object to mutate with trace context headers.
|
79
|
+
# @parameter context [Context] A trace context, or nil to use current context.
|
80
|
+
# @returns [Hash | Nil] The headers hash, or nil if no context is available.
|
81
|
+
def self.inject(headers = nil, context = nil)
|
82
|
+
context ||= self.trace_context
|
83
|
+
|
84
|
+
if context
|
85
|
+
headers ||= Hash.new
|
86
|
+
context.inject(headers)
|
87
|
+
else
|
88
|
+
headers = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
return headers
|
92
|
+
end
|
93
|
+
|
94
|
+
# Extract trace context from headers for distributed propagation.
|
95
|
+
#
|
96
|
+
# The returned object is opaque, in other words, you should not make assumptions about its structure.
|
97
|
+
#
|
98
|
+
# This is a default implementation, which can be replaced by the backend.
|
99
|
+
#
|
100
|
+
# @parameter headers [Hash] The headers object containing trace context.
|
101
|
+
# @returns [Context, nil] The extracted trace context, or nil if no valid context found.
|
102
|
+
def self.extract(headers)
|
103
|
+
Context.extract(headers)
|
104
|
+
end
|
105
|
+
|
25
106
|
Config::DEFAULT.require_backend
|
26
107
|
end
|
data/lib/traces/config.rb
CHANGED
@@ -35,12 +35,12 @@ module Traces
|
|
35
35
|
def require_backend(env = ENV)
|
36
36
|
if backend = env["TRACES_BACKEND"]
|
37
37
|
begin
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
require(backend)
|
39
|
+
|
40
|
+
# We ensure that the interface methods replace any existing methods by prepending the module:
|
41
|
+
Traces.singleton_class.prepend(Backend::Interface)
|
42
|
+
|
43
|
+
return true
|
44
44
|
rescue LoadError => error
|
45
45
|
warn "Unable to load traces backend: #{backend.inspect}!"
|
46
46
|
end
|
data/lib/traces/context.rb
CHANGED
@@ -11,10 +11,10 @@ module Traces
|
|
11
11
|
# Parse a string representation of a distributed trace.
|
12
12
|
# @parameter parent [String] The parent trace context.
|
13
13
|
# @parameter state [Array(String)] Any attached trace state.
|
14
|
-
def self.parse(parent, state = nil, **options)
|
14
|
+
def self.parse(parent, state = nil, baggage = nil, **options)
|
15
15
|
version, trace_id, parent_id, flags = parent.split("-")
|
16
16
|
|
17
|
-
if version == "00"
|
17
|
+
if version == "00" && trace_id && parent_id && flags
|
18
18
|
flags = Integer(flags, 16)
|
19
19
|
|
20
20
|
if state.is_a?(String)
|
@@ -25,11 +25,19 @@ module Traces
|
|
25
25
|
state = state.map{|item| item.split("=")}.to_h
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
if baggage.is_a?(String)
|
29
|
+
baggage = baggage.split(",")
|
30
|
+
end
|
31
|
+
|
32
|
+
if baggage
|
33
|
+
baggage = baggage.map{|item| item.split("=")}.to_h
|
34
|
+
end
|
35
|
+
|
36
|
+
self.new(trace_id, parent_id, flags, state, baggage, **options)
|
29
37
|
end
|
30
38
|
end
|
31
39
|
|
32
|
-
# Create a local trace context which is
|
40
|
+
# Create a local trace context which is likely to be globally unique.
|
33
41
|
# @parameter flags [Integer] Any trace context flags.
|
34
42
|
def self.local(flags = 0, **options)
|
35
43
|
self.new(SecureRandom.hex(16), SecureRandom.hex(8), flags, **options)
|
@@ -53,17 +61,18 @@ module Traces
|
|
53
61
|
# @parameter flags [Integer] An 8-bit field that controls tracing flags such as sampling, trace level, etc.
|
54
62
|
# @parameter state [Hash] Additional vendor-specific trace identification information.
|
55
63
|
# @parameter remote [Boolean] Whether this context was created from a distributed trace header.
|
56
|
-
def initialize(trace_id, parent_id, flags, state = nil, remote: false)
|
64
|
+
def initialize(trace_id, parent_id, flags, state = nil, baggage = nil, remote: false)
|
57
65
|
@trace_id = trace_id
|
58
66
|
@parent_id = parent_id
|
59
67
|
@flags = flags
|
60
68
|
@state = state
|
69
|
+
@baggage = baggage
|
61
70
|
@remote = remote
|
62
71
|
end
|
63
72
|
|
64
73
|
# Create a new nested trace context in which spans can be recorded.
|
65
74
|
def nested(flags = @flags)
|
66
|
-
Context.new(@trace_id, SecureRandom.hex(8), flags, @state, remote: @remote)
|
75
|
+
Context.new(@trace_id, SecureRandom.hex(8), flags, @state, @baggage, remote: @remote)
|
67
76
|
end
|
68
77
|
|
69
78
|
# 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.
|
@@ -75,9 +84,12 @@ module Traces
|
|
75
84
|
# 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.
|
76
85
|
attr :flags
|
77
86
|
|
78
|
-
# Provides additional vendor-specific trace identification information across different distributed tracing systems.
|
87
|
+
# Provides additional vendor-specific trace identification information across different distributed tracing systems.
|
79
88
|
attr :state
|
80
89
|
|
90
|
+
# Provides additional application-specific trace identification information across different distributed tracing systems.
|
91
|
+
attr :baggage
|
92
|
+
|
81
93
|
# Denotes that the caller may have recorded trace data. When unset, the caller did not record trace data out-of-band.
|
82
94
|
def sampled?
|
83
95
|
(@flags & SAMPLED) != 0
|
@@ -100,6 +112,7 @@ module Traces
|
|
100
112
|
parent_id: @parent_id,
|
101
113
|
flags: @flags,
|
102
114
|
state: @state,
|
115
|
+
baggage: @baggage,
|
103
116
|
remote: @remote
|
104
117
|
}
|
105
118
|
end
|
@@ -108,5 +121,50 @@ module Traces
|
|
108
121
|
def to_json(...)
|
109
122
|
as_json.to_json(...)
|
110
123
|
end
|
124
|
+
|
125
|
+
# Inject the trace context into the headers, including the `"traceparent"`, `"tracestate"`, and `"baggage"` headers.
|
126
|
+
#
|
127
|
+
# @parameter headers [Hash] The headers hash to inject the trace context into.
|
128
|
+
#
|
129
|
+
# @returns [Hash] The modified headers hash.
|
130
|
+
def inject(headers)
|
131
|
+
headers["traceparent"] = self.to_s
|
132
|
+
|
133
|
+
if @state and !@state.empty?
|
134
|
+
headers["tracestate"] = self.state.map{|key, value| "#{key}=#{value}"}.join(",")
|
135
|
+
end
|
136
|
+
|
137
|
+
if @baggage and !@baggage.empty?
|
138
|
+
headers["baggage"] = self.baggage.map{|key, value| "#{key}=#{value}"}.join(",")
|
139
|
+
end
|
140
|
+
|
141
|
+
return headers
|
142
|
+
end
|
143
|
+
|
144
|
+
# Extract the trace context from the headers.
|
145
|
+
#
|
146
|
+
# 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.
|
147
|
+
# The `"tracestate"` header is a string representation of the trace state. If it is a String, it is split on commas before being processed.
|
148
|
+
# The `"baggage"` header is a string representation of the baggage. If it is a String, it is split on commas before being processed.
|
149
|
+
#
|
150
|
+
# @parameter headers [Hash] The headers hash containing trace context.
|
151
|
+
# @returns [Context | Nil] The extracted trace context, or nil if no valid context found.
|
152
|
+
# @raises [ArgumentError] If headers is not a Hash or contains malformed trace data.
|
153
|
+
def self.extract(headers)
|
154
|
+
if traceparent = headers["traceparent"]
|
155
|
+
if traceparent.is_a?(Array)
|
156
|
+
traceparent = traceparent.first
|
157
|
+
end
|
158
|
+
|
159
|
+
if traceparent.empty?
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
|
163
|
+
tracestate = headers["tracestate"]
|
164
|
+
baggage = headers["baggage"]
|
165
|
+
|
166
|
+
return self.parse(traceparent, tracestate, baggage, remote: true)
|
167
|
+
end
|
168
|
+
end
|
111
169
|
end
|
112
170
|
end
|
data/lib/traces/version.rb
CHANGED
data/lib/traces.rb
CHANGED
data/readme.md
CHANGED
@@ -15,6 +15,8 @@ Please see the [project documentation](https://socketry.github.io/traces/) for m
|
|
15
15
|
|
16
16
|
- [Getting Started](https://socketry.github.io/traces/guides/getting-started/index) - This guide explains how to use `traces` for tracing code execution.
|
17
17
|
|
18
|
+
- [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`.
|
19
|
+
|
18
20
|
- [Testing](https://socketry.github.io/traces/guides/testing/index) - This guide explains how to test traces in your code.
|
19
21
|
|
20
22
|
- [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.
|
@@ -23,6 +25,11 @@ Please see the [project documentation](https://socketry.github.io/traces/) for m
|
|
23
25
|
|
24
26
|
Please see the [project releases](https://socketry.github.io/traces/releases/index) for all releases.
|
25
27
|
|
28
|
+
### v0.18.0
|
29
|
+
|
30
|
+
- **W3C Baggage Support** - Full support for W3C Baggage specification for application-specific context propagation.
|
31
|
+
- [New Context Propagation Interfaces](https://socketry.github.io/traces/releases/index#new-context-propagation-interfaces)
|
32
|
+
|
26
33
|
### v0.17.0
|
27
34
|
|
28
35
|
- Remove support for `resource:` keyword argument with no direct replacement – use an attribute instead.
|
data/releases.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v0.18.0
|
4
|
+
|
5
|
+
- **W3C Baggage Support** - Full support for W3C Baggage specification for application-specific context propagation.
|
6
|
+
|
7
|
+
### New Context Propagation Interfaces
|
8
|
+
|
9
|
+
`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.
|
10
|
+
|
11
|
+
Therefore, in order to propagate this context, we introduce 4 new methods:
|
12
|
+
|
13
|
+
- `Traces.current_context` - Capture the current trace context for local propagation between execution contexts (threads, fibers).
|
14
|
+
- `Traces.with_context(context)` - Execute code within a specific trace context, with automatic restoration when used with blocks.
|
15
|
+
- `Traces.inject(headers = nil, context = nil)` - Inject W3C Trace Context headers into a headers hash for distributed propagation.
|
16
|
+
- `Traces.extract(headers)` - Extract trace context from W3C Trace Context headers.
|
17
|
+
|
18
|
+
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`.
|
19
|
+
|
3
20
|
## v0.17.0
|
4
21
|
|
5
22
|
- Remove support for `resource:` keyword argument with no direct replacement – use an attribute instead.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
Binary file
|