tracekit 0.2.2 → 0.2.4
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
- data/CHANGELOG.md +17 -0
- data/README.md +101 -1
- data/lib/tracekit/config.rb +6 -2
- data/lib/tracekit/evaluator.rb +604 -0
- data/lib/tracekit/llm/anthropic_instrumentation.rb +218 -0
- data/lib/tracekit/llm/common.rb +118 -0
- data/lib/tracekit/llm/openai_instrumentation.rb +201 -0
- data/lib/tracekit/sdk.rb +29 -0
- data/lib/tracekit/snapshots/client.rb +119 -46
- data/lib/tracekit/snapshots/models.rb +6 -0
- data/lib/tracekit/version.rb +1 -1
- data/lib/tracekit.rb +10 -0
- metadata +10 -6
|
@@ -91,9 +91,31 @@ module Tracekit
|
|
|
91
91
|
return if breakpoint.expire_at && Time.now > breakpoint.expire_at
|
|
92
92
|
return if breakpoint.max_captures > 0 && breakpoint.capture_count >= breakpoint.max_captures
|
|
93
93
|
|
|
94
|
-
#
|
|
95
|
-
if
|
|
96
|
-
|
|
94
|
+
# Evaluate breakpoint condition locally for sdk-evaluable expressions
|
|
95
|
+
if breakpoint.condition && !breakpoint.condition.empty? && breakpoint.condition_eval == "sdk-evaluable"
|
|
96
|
+
begin
|
|
97
|
+
result = Tracekit::Evaluator.evaluate_condition(breakpoint.condition, variables)
|
|
98
|
+
return unless result # Condition false, skip capture
|
|
99
|
+
rescue Tracekit::Evaluator::UnsupportedExpressionError
|
|
100
|
+
# Classified as sdk-evaluable but failed locally, fall through to server
|
|
101
|
+
warn "TraceKit: expression classified as sdk-evaluable but failed locally, falling back to server" if ENV["DEBUG"]
|
|
102
|
+
rescue => e
|
|
103
|
+
# Other evaluation error, log and fall through to server
|
|
104
|
+
warn "TraceKit: condition evaluation error, falling back to server: #{e.message}" if ENV["DEBUG"]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Logpoint mode: capture only expression results, skip locals/stack/request
|
|
109
|
+
if breakpoint.mode == "logpoint"
|
|
110
|
+
snapshot = build_logpoint_snapshot(breakpoint, file_path, line_number, function_name, label, variables)
|
|
111
|
+
Thread.new { submit_snapshot_with_payload_limit(snapshot, breakpoint.max_payload_bytes) }
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Apply per-breakpoint capture depth limit (with SDK-level fallback)
|
|
116
|
+
effective_depth = breakpoint.max_depth || @capture_depth
|
|
117
|
+
if effective_depth && effective_depth > 0
|
|
118
|
+
variables = limit_depth(variables, 0, effective_depth)
|
|
97
119
|
end
|
|
98
120
|
|
|
99
121
|
# Scan for security issues
|
|
@@ -104,14 +126,23 @@ module Tracekit
|
|
|
104
126
|
span_id = nil
|
|
105
127
|
if defined?(OpenTelemetry::Trace)
|
|
106
128
|
span = OpenTelemetry::Trace.current_span
|
|
107
|
-
if span && span.context.valid?
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
if span && span.context.valid?
|
|
130
|
+
sampled = begin
|
|
131
|
+
span.context.trace_flags.sampled?
|
|
132
|
+
rescue NoMethodError
|
|
133
|
+
# Fallback for older OTel versions
|
|
134
|
+
(span.context.trace_flags.to_i & 0x01) != 0 rescue false
|
|
135
|
+
end
|
|
136
|
+
if sampled
|
|
137
|
+
trace_id = span.context.hex_trace_id
|
|
138
|
+
span_id = span.context.hex_span_id
|
|
139
|
+
end
|
|
110
140
|
end
|
|
111
141
|
end
|
|
112
142
|
|
|
113
|
-
# Get stack trace
|
|
114
|
-
|
|
143
|
+
# Get stack trace with dynamic depth from per-breakpoint config
|
|
144
|
+
effective_stack_depth = breakpoint.stack_depth || 50
|
|
145
|
+
stack_trace = caller(1, effective_stack_depth).join("\n")
|
|
115
146
|
|
|
116
147
|
snapshot = Snapshot.new(
|
|
117
148
|
breakpoint_id: breakpoint.id,
|
|
@@ -128,24 +159,9 @@ module Tracekit
|
|
|
128
159
|
captured_at: Time.now.utc.iso8601
|
|
129
160
|
)
|
|
130
161
|
|
|
131
|
-
# Apply
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
snapshot = Snapshot.new(
|
|
135
|
-
breakpoint_id: breakpoint.id,
|
|
136
|
-
service_name: @service_name,
|
|
137
|
-
file_path: file_path,
|
|
138
|
-
function_name: function_name,
|
|
139
|
-
label: label,
|
|
140
|
-
line_number: line_number,
|
|
141
|
-
variables: { "_truncated" => true, "_payload_size" => serialized.bytesize, "_max_payload" => @max_payload },
|
|
142
|
-
security_flags: [],
|
|
143
|
-
stack_trace: stack_trace,
|
|
144
|
-
trace_id: trace_id,
|
|
145
|
-
span_id: span_id,
|
|
146
|
-
captured_at: Time.now.utc.iso8601
|
|
147
|
-
)
|
|
148
|
-
end
|
|
162
|
+
# Apply per-breakpoint max payload limit (with SDK-level fallback)
|
|
163
|
+
effective_max_payload = breakpoint.max_payload_bytes || @max_payload
|
|
164
|
+
submit_snapshot_with_payload_limit(snapshot, effective_max_payload)
|
|
149
165
|
|
|
150
166
|
# Submit asynchronously (with optional timeout)
|
|
151
167
|
if @capture_timeout && @capture_timeout > 0
|
|
@@ -169,24 +185,77 @@ module Tracekit
|
|
|
169
185
|
|
|
170
186
|
private
|
|
171
187
|
|
|
172
|
-
# Limit variable nesting depth (opt-in)
|
|
173
|
-
def limit_depth(data, current_depth)
|
|
174
|
-
|
|
188
|
+
# Limit variable nesting depth (opt-in, supports per-breakpoint override)
|
|
189
|
+
def limit_depth(data, current_depth, max_depth = nil)
|
|
190
|
+
effective_depth = max_depth || @capture_depth
|
|
191
|
+
return { "_truncated" => true, "_depth" => current_depth } if current_depth >= effective_depth
|
|
175
192
|
|
|
176
193
|
case data
|
|
177
194
|
when Hash
|
|
178
195
|
result = {}
|
|
179
196
|
data.each do |k, v|
|
|
180
|
-
result[k] = limit_depth(v, current_depth + 1)
|
|
197
|
+
result[k] = limit_depth(v, current_depth + 1, effective_depth)
|
|
181
198
|
end
|
|
182
199
|
result
|
|
183
200
|
when Array
|
|
184
|
-
data.map { |item| limit_depth(item, current_depth + 1) }
|
|
201
|
+
data.map { |item| limit_depth(item, current_depth + 1, effective_depth) }
|
|
185
202
|
else
|
|
186
203
|
data
|
|
187
204
|
end
|
|
188
205
|
end
|
|
189
206
|
|
|
207
|
+
# Build a logpoint snapshot: expression results only, no locals/stack
|
|
208
|
+
def build_logpoint_snapshot(breakpoint, file_path, line_number, function_name, label, variables)
|
|
209
|
+
expression_results = {}
|
|
210
|
+
if breakpoint.capture_expressions && !breakpoint.capture_expressions.empty?
|
|
211
|
+
expression_results = Tracekit::Evaluator.evaluate_expressions(
|
|
212
|
+
breakpoint.capture_expressions, variables
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
Snapshot.new(
|
|
217
|
+
breakpoint_id: breakpoint.id,
|
|
218
|
+
service_name: @service_name,
|
|
219
|
+
file_path: file_path,
|
|
220
|
+
function_name: function_name,
|
|
221
|
+
label: label,
|
|
222
|
+
line_number: line_number,
|
|
223
|
+
variables: {},
|
|
224
|
+
security_flags: [],
|
|
225
|
+
stack_trace: "",
|
|
226
|
+
trace_id: nil,
|
|
227
|
+
span_id: nil,
|
|
228
|
+
captured_at: Time.now.utc.iso8601,
|
|
229
|
+
expression_results: expression_results,
|
|
230
|
+
mode: "logpoint"
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Submit snapshot with payload limit check
|
|
235
|
+
def submit_snapshot_with_payload_limit(snapshot, max_payload_bytes)
|
|
236
|
+
effective_limit = max_payload_bytes || @max_payload
|
|
237
|
+
if effective_limit && effective_limit > 0
|
|
238
|
+
serialized = JSON.generate(snapshot.to_h)
|
|
239
|
+
if serialized.bytesize > effective_limit
|
|
240
|
+
snapshot = Snapshot.new(
|
|
241
|
+
breakpoint_id: snapshot.breakpoint_id,
|
|
242
|
+
service_name: snapshot.service_name,
|
|
243
|
+
file_path: snapshot.file_path,
|
|
244
|
+
function_name: snapshot.function_name,
|
|
245
|
+
label: snapshot.label,
|
|
246
|
+
line_number: snapshot.line_number,
|
|
247
|
+
variables: { "_truncated" => true, "_payload_size" => serialized.bytesize, "_max_payload" => effective_limit },
|
|
248
|
+
security_flags: [],
|
|
249
|
+
stack_trace: snapshot.stack_trace,
|
|
250
|
+
trace_id: snapshot.trace_id,
|
|
251
|
+
span_id: snapshot.span_id,
|
|
252
|
+
captured_at: snapshot.captured_at
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
Thread.new { submit_snapshot(snapshot) }
|
|
257
|
+
end
|
|
258
|
+
|
|
190
259
|
def fetch_active_breakpoints
|
|
191
260
|
url = "#{@base_url}/sdk/snapshots/active/#{@service_name}"
|
|
192
261
|
uri = URI(url)
|
|
@@ -237,17 +306,7 @@ module Tracekit
|
|
|
237
306
|
@breakpoints_cache.clear
|
|
238
307
|
|
|
239
308
|
breakpoints.each do |bp_data|
|
|
240
|
-
bp =
|
|
241
|
-
id: bp_data[:id],
|
|
242
|
-
file_path: bp_data[:file_path],
|
|
243
|
-
line_number: bp_data[:line_number],
|
|
244
|
-
function_name: bp_data[:function_name],
|
|
245
|
-
label: bp_data[:label],
|
|
246
|
-
enabled: bp_data[:enabled],
|
|
247
|
-
max_captures: bp_data[:max_captures] || 0,
|
|
248
|
-
capture_count: bp_data[:capture_count] || 0,
|
|
249
|
-
expire_at: bp_data[:expire_at] ? Time.parse(bp_data[:expire_at]) : nil
|
|
250
|
-
)
|
|
309
|
+
bp = build_breakpoint_config(bp_data)
|
|
251
310
|
|
|
252
311
|
# Key by function + label
|
|
253
312
|
if bp.label && bp.function_name
|
|
@@ -303,6 +362,7 @@ module Tracekit
|
|
|
303
362
|
end
|
|
304
363
|
|
|
305
364
|
def submit_snapshot(snapshot)
|
|
365
|
+
# Circuit breaker check
|
|
306
366
|
# Circuit breaker check
|
|
307
367
|
return unless circuit_breaker_should_allow?
|
|
308
368
|
|
|
@@ -455,9 +515,9 @@ module Tracekit
|
|
|
455
515
|
warn "TraceKit: SSE event handling error: #{e.message}" if ENV["DEBUG"]
|
|
456
516
|
end
|
|
457
517
|
|
|
458
|
-
#
|
|
459
|
-
def
|
|
460
|
-
|
|
518
|
+
# Build a BreakpointConfig from parsed payload data
|
|
519
|
+
def build_breakpoint_config(bp_data)
|
|
520
|
+
BreakpointConfig.new(
|
|
461
521
|
id: bp_data[:id],
|
|
462
522
|
file_path: bp_data[:file_path],
|
|
463
523
|
line_number: bp_data[:line_number],
|
|
@@ -466,8 +526,21 @@ module Tracekit
|
|
|
466
526
|
enabled: bp_data[:enabled],
|
|
467
527
|
max_captures: bp_data[:max_captures] || 0,
|
|
468
528
|
capture_count: bp_data[:capture_count] || 0,
|
|
469
|
-
expire_at: bp_data[:expire_at] ? Time.parse(bp_data[:expire_at]) : nil
|
|
529
|
+
expire_at: bp_data[:expire_at] ? Time.parse(bp_data[:expire_at]) : nil,
|
|
530
|
+
condition: bp_data[:condition],
|
|
531
|
+
condition_eval: bp_data[:condition_eval],
|
|
532
|
+
mode: bp_data[:mode],
|
|
533
|
+
stack_depth: bp_data[:stack_depth],
|
|
534
|
+
max_depth: bp_data[:max_depth],
|
|
535
|
+
max_payload_bytes: bp_data[:max_payload_bytes],
|
|
536
|
+
capture_expressions: bp_data[:capture_expressions],
|
|
537
|
+
idle_timeout_hours: bp_data[:idle_timeout_hours]
|
|
470
538
|
)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# Upsert a single breakpoint into the cache
|
|
542
|
+
def upsert_breakpoint(bp_data)
|
|
543
|
+
bp = build_breakpoint_config(bp_data)
|
|
471
544
|
|
|
472
545
|
# Key by function + label
|
|
473
546
|
if bp.label && bp.function_name
|
|
@@ -6,6 +6,10 @@ module Tracekit
|
|
|
6
6
|
BreakpointConfig = Struct.new(
|
|
7
7
|
:id, :file_path, :line_number, :function_name, :label,
|
|
8
8
|
:enabled, :max_captures, :capture_count, :expire_at,
|
|
9
|
+
# v25 capture features
|
|
10
|
+
:condition, :condition_eval, :mode, :stack_depth,
|
|
11
|
+
:max_depth, :max_payload_bytes, :capture_expressions,
|
|
12
|
+
:idle_timeout_hours,
|
|
9
13
|
keyword_init: true
|
|
10
14
|
)
|
|
11
15
|
|
|
@@ -14,6 +18,8 @@ module Tracekit
|
|
|
14
18
|
:breakpoint_id, :service_name, :file_path, :function_name, :label,
|
|
15
19
|
:line_number, :variables, :security_flags, :stack_trace,
|
|
16
20
|
:trace_id, :span_id, :captured_at,
|
|
21
|
+
# v25 capture features
|
|
22
|
+
:expression_results, :mode,
|
|
17
23
|
keyword_init: true
|
|
18
24
|
)
|
|
19
25
|
|
data/lib/tracekit/version.rb
CHANGED
data/lib/tracekit.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "tracekit/version"
|
|
4
4
|
require_relative "tracekit/config"
|
|
5
5
|
require_relative "tracekit/endpoint_resolver"
|
|
6
|
+
require_relative "tracekit/evaluator"
|
|
6
7
|
|
|
7
8
|
# Metrics
|
|
8
9
|
require_relative "tracekit/metrics/metric_data_point"
|
|
@@ -23,6 +24,15 @@ require_relative "tracekit/local_ui_detector"
|
|
|
23
24
|
require_relative "tracekit/snapshots/models"
|
|
24
25
|
require_relative "tracekit/snapshots/client"
|
|
25
26
|
|
|
27
|
+
# LLM instrumentation
|
|
28
|
+
begin
|
|
29
|
+
require_relative "tracekit/llm/common"
|
|
30
|
+
require_relative "tracekit/llm/openai_instrumentation"
|
|
31
|
+
require_relative "tracekit/llm/anthropic_instrumentation"
|
|
32
|
+
rescue LoadError
|
|
33
|
+
# LLM instrumentation not available
|
|
34
|
+
end
|
|
35
|
+
|
|
26
36
|
# Core SDK
|
|
27
37
|
require_relative "tracekit/sdk"
|
|
28
38
|
require_relative "tracekit/middleware"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tracekit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TraceKit
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: opentelemetry-sdk
|
|
@@ -150,6 +150,10 @@ files:
|
|
|
150
150
|
- lib/tracekit.rb
|
|
151
151
|
- lib/tracekit/config.rb
|
|
152
152
|
- lib/tracekit/endpoint_resolver.rb
|
|
153
|
+
- lib/tracekit/evaluator.rb
|
|
154
|
+
- lib/tracekit/llm/anthropic_instrumentation.rb
|
|
155
|
+
- lib/tracekit/llm/common.rb
|
|
156
|
+
- lib/tracekit/llm/openai_instrumentation.rb
|
|
153
157
|
- lib/tracekit/local_ui/detector.rb
|
|
154
158
|
- lib/tracekit/local_ui_detector.rb
|
|
155
159
|
- lib/tracekit/metrics/counter.rb
|
|
@@ -173,7 +177,7 @@ metadata:
|
|
|
173
177
|
homepage_uri: https://github.com/Tracekit-Dev/ruby-sdk
|
|
174
178
|
source_code_uri: https://github.com/Tracekit-Dev/ruby-sdk
|
|
175
179
|
changelog_uri: https://github.com/Tracekit-Dev/ruby-sdk/blob/main/CHANGELOG.md
|
|
176
|
-
post_install_message:
|
|
180
|
+
post_install_message:
|
|
177
181
|
rdoc_options: []
|
|
178
182
|
require_paths:
|
|
179
183
|
- lib
|
|
@@ -188,8 +192,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
188
192
|
- !ruby/object:Gem::Version
|
|
189
193
|
version: '0'
|
|
190
194
|
requirements: []
|
|
191
|
-
rubygems_version: 3.
|
|
192
|
-
signing_key:
|
|
195
|
+
rubygems_version: 3.0.3.1
|
|
196
|
+
signing_key:
|
|
193
197
|
specification_version: 4
|
|
194
198
|
summary: TraceKit Ruby SDK - OpenTelemetry-based APM for Ruby applications
|
|
195
199
|
test_files: []
|