logbrew-sdk 0.1.7 → 0.1.9
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/README.md +15 -3
- data/lib/logbrew/queue_carrier.rb +56 -0
- data/lib/logbrew/rails.rb +3 -2
- data/lib/logbrew/rails_integration.rb +170 -0
- data/lib/logbrew/sidekiq.rb +16 -44
- data/lib/logbrew/trace.rb +4 -14
- data/lib/logbrew/version.rb +1 -1
- data/lib/logbrew.rb +24 -18
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95bb6223a8ef074b3c8dd0973fe99e36734849c1b47a19f1c1ed88655888d71b
|
|
4
|
+
data.tar.gz: 4ac8224ffe4e2fae6f4262b80a3119f2574982a6c0f384c529c77c74163340fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f90b3564b39c4546b8a34b424b1f59e8ca5de6afaba35529e511a688712e770c92c04deb547fd7e370bfa1590688be7ce3b11ca5f04ccd7d11ecb022f37ff401
|
|
7
|
+
data.tar.gz: 3d47121b9f61ed19bbeda0e9389f3daa5137c34b88256ea9b5d3f7e47dd28b4d4c36fc7f94bf0c9e2172be0b65f72235e5e96e2923c113811e0b758158498f18
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<img src="https://raw.githubusercontent.com/LogBrewCo/sdk/main/assets/brand/logbrew-logo-transparent-512.png" alt="LogBrew logo" width="96" height="96">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
Public Ruby SDK for building, validating, previewing, and flushing LogBrew event batches, with automatic Rails request, database, cache, view, and
|
|
7
|
+
Public Ruby SDK for building, validating, previewing, and flushing LogBrew event batches, with automatic Rails request, database, cache, view, error, and ActiveJob capture; standard-library `Net::HTTP` delivery; opt-in standard-library `Logger` support; and manual Rack helpers.
|
|
8
8
|
|
|
9
9
|
The core package has no runtime gem dependencies. Its automatic integration
|
|
10
10
|
activates only inside an application that has already loaded Rails.
|
|
@@ -23,7 +23,7 @@ needed:
|
|
|
23
23
|
|
|
24
24
|
```ruby
|
|
25
25
|
# Gemfile
|
|
26
|
-
gem "logbrew-sdk", "~> 0.1.
|
|
26
|
+
gem "logbrew-sdk", "~> 0.1.9"
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
```bash
|
|
@@ -53,7 +53,19 @@ frames. Handled Rails reports use `rails.error_reporter` with `handled: true`.
|
|
|
53
53
|
It does not record concrete request paths, query strings, request or response
|
|
54
54
|
bodies, arbitrary headers, authorization values, cookies, user IDs, exception
|
|
55
55
|
messages, raw backtrace text, source snippets, locals, arguments, or absolute
|
|
56
|
-
paths. Exception messages and raw backtrace text are separate opt-ins
|
|
56
|
+
paths. Exception messages and raw backtrace text are separate opt-ins.
|
|
57
|
+
|
|
58
|
+
ActiveJob needs no extra initializer. Each enqueue and execution emits a
|
|
59
|
+
producer or worker span, including bounded adapter identity, retry count, and
|
|
60
|
+
queue wait. A versioned W3C carrier keeps retries and supported queue adapters
|
|
61
|
+
on one trace. A retryable failure remains span evidence; only an unexpected
|
|
62
|
+
terminal `StandardError` creates an unhandled `rails.active_job` issue with
|
|
63
|
+
sanitized structured frames. The adapter does not capture job IDs, queue names,
|
|
64
|
+
arguments, serialized payloads, exception messages, or raw backtraces by
|
|
65
|
+
default. When ActiveJob uses LogBrew's Sidekiq middleware, the inner carrier
|
|
66
|
+
suppresses duplicate Sidekiq spans and issues.
|
|
67
|
+
|
|
68
|
+
The explicit Rails capture settings are:
|
|
57
69
|
|
|
58
70
|
| Environment variable | Default | Purpose |
|
|
59
71
|
| --- | --- | --- |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LogBrew
|
|
4
|
+
# Bounded W3C carrier shared by explicit and framework-owned queue adapters.
|
|
5
|
+
module QueueCarrier
|
|
6
|
+
KEY = "logbrew".freeze
|
|
7
|
+
VERSION = 1
|
|
8
|
+
MAX_ENQUEUED_AT_MS = 9_007_199_254_740_991
|
|
9
|
+
MAX_QUEUE_WAIT_MS = 604_800_000
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def create(context, enqueued_at_ms: wall_time_ms)
|
|
14
|
+
{
|
|
15
|
+
"version" => VERSION,
|
|
16
|
+
"traceparent" => Trace.create_headers(context).fetch("traceparent"),
|
|
17
|
+
"enqueuedAtMs" => enqueued_at_ms
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def read(value)
|
|
22
|
+
keys = %w[enqueuedAtMs traceparent version]
|
|
23
|
+
return unless value.is_a?(Hash) && value.size == keys.length && keys.all? { |key| value.key?(key) }
|
|
24
|
+
return unless value["version"] == VERSION
|
|
25
|
+
return unless value["traceparent"].is_a?(String) && value["traceparent"].bytesize <= 55
|
|
26
|
+
return unless value["enqueuedAtMs"].is_a?(Integer) && value["enqueuedAtMs"].between?(0, MAX_ENQUEUED_AT_MS)
|
|
27
|
+
|
|
28
|
+
Traceparent.parse(value["traceparent"])
|
|
29
|
+
value
|
|
30
|
+
rescue SdkError
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def child_context(carrier)
|
|
35
|
+
parsed = carrier && Traceparent.parse(carrier.fetch("traceparent"))
|
|
36
|
+
return Trace.create_root if parsed.nil?
|
|
37
|
+
|
|
38
|
+
Trace.create(
|
|
39
|
+
trace_id: parsed.trace_id,
|
|
40
|
+
span_id: Trace.generate_span_id,
|
|
41
|
+
parent_span_id: parsed.parent_span_id,
|
|
42
|
+
trace_flags: parsed.trace_flags
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def queue_wait_ms(carrier)
|
|
47
|
+
return if carrier.nil?
|
|
48
|
+
|
|
49
|
+
[[wall_time_ms - carrier.fetch("enqueuedAtMs"), 0].max, MAX_QUEUE_WAIT_MS].min
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def wall_time_ms
|
|
53
|
+
(Time.now.to_f * 1000.0).floor
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/logbrew/rails.rb
CHANGED
|
@@ -8,8 +8,6 @@ module LogBrew
|
|
|
8
8
|
# Process-safe Rails integration installed by RailsRailtie.
|
|
9
9
|
module Rails
|
|
10
10
|
class << self
|
|
11
|
-
attr_reader :runtime
|
|
12
|
-
|
|
13
11
|
def install(application:, environment: ENV)
|
|
14
12
|
@installation_mutex ||= Mutex.new
|
|
15
13
|
@installation_mutex.synchronize do
|
|
@@ -80,6 +78,9 @@ module LogBrew
|
|
|
80
78
|
if runtime.configuration.enabled?
|
|
81
79
|
LogBrew::Rails.const_get(:RequestOperations).install(::ActiveSupport::Notifications)
|
|
82
80
|
end
|
|
81
|
+
::ActiveSupport.on_load(:active_job) do
|
|
82
|
+
prepend LogBrew::Rails::ActiveJobExtension unless ancestors.include?(LogBrew::Rails::ActiveJobExtension)
|
|
83
|
+
end
|
|
83
84
|
middleware = application.config.middleware
|
|
84
85
|
if defined?(::ActionDispatch::ShowExceptions)
|
|
85
86
|
middleware.insert_after(
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../logbrew" unless defined?(LogBrew::Client)
|
|
4
|
+
require_relative "queue_carrier"
|
|
4
5
|
require "uri"
|
|
5
6
|
|
|
6
7
|
module LogBrew
|
|
7
8
|
module Rails
|
|
9
|
+
singleton_class.attr_reader :runtime
|
|
10
|
+
|
|
8
11
|
# Immutable, environment-derived settings for the automatic Rails adapter.
|
|
9
12
|
class Configuration
|
|
10
13
|
DEFAULT_ENDPOINT = LogBrew::HttpTransport::DEFAULT_ENDPOINT
|
|
@@ -395,6 +398,173 @@ module LogBrew
|
|
|
395
398
|
end
|
|
396
399
|
end
|
|
397
400
|
|
|
401
|
+
# One privacy-bounded producer or worker operation for ActiveJob.
|
|
402
|
+
class ActiveJobOperation
|
|
403
|
+
attr_reader :context
|
|
404
|
+
|
|
405
|
+
def self.create(runtime, job, kind, carrier: nil)
|
|
406
|
+
client = runtime&.client
|
|
407
|
+
return if client.nil?
|
|
408
|
+
|
|
409
|
+
new(runtime, client, job, kind, carrier)
|
|
410
|
+
rescue StandardError => error
|
|
411
|
+
runtime&.report_error("active_job_capture", error)
|
|
412
|
+
nil
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def initialize(runtime, client, job, kind, carrier)
|
|
416
|
+
@runtime = runtime
|
|
417
|
+
@client = client
|
|
418
|
+
@kind = kind
|
|
419
|
+
@context = kind == :enqueue ? OperationTracing.child_context : QueueCarrier.child_context(QueueCarrier.read(carrier))
|
|
420
|
+
@job_class = bounded_identifier(job.class.name, "ActiveJob")
|
|
421
|
+
@adapter = bounded_identifier(job.class.respond_to?(:queue_adapter_name) ? job.class.queue_adapter_name : nil, "active_job")
|
|
422
|
+
@retry_count = normalized_retry_count(job.respond_to?(:executions) ? job.executions : nil)
|
|
423
|
+
@queue_wait_ms = kind == :perform ? QueueCarrier.queue_wait_ms(QueueCarrier.read(carrier)) : nil
|
|
424
|
+
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
425
|
+
@timestamp = Time.now.utc.iso8601(6)
|
|
426
|
+
@finished = false
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def around
|
|
430
|
+
result = Trace.with_context(@context) { yield }
|
|
431
|
+
finish
|
|
432
|
+
result
|
|
433
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
434
|
+
finish(error)
|
|
435
|
+
raise
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def finish(error = nil)
|
|
439
|
+
return if @finished
|
|
440
|
+
|
|
441
|
+
@finished = true
|
|
442
|
+
options = {
|
|
443
|
+
timestamp: @timestamp,
|
|
444
|
+
source: "rails.active_job",
|
|
445
|
+
system: @adapter,
|
|
446
|
+
operation: @kind.to_s,
|
|
447
|
+
metadata: operation_metadata
|
|
448
|
+
}
|
|
449
|
+
OperationTracing.capture_span(@client, "queue", "active_job.#{@kind}", @context, @started_at, options, error)
|
|
450
|
+
rescue StandardError => capture_error
|
|
451
|
+
@runtime.report_error("active_job_capture", capture_error)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def capture_terminal_issue(error)
|
|
455
|
+
return unless error.is_a?(StandardError)
|
|
456
|
+
|
|
457
|
+
metadata = operation_metadata.merge(
|
|
458
|
+
"source" => "rails.active_job",
|
|
459
|
+
"handled" => false,
|
|
460
|
+
"mechanism" => "rails.active_job",
|
|
461
|
+
"issueGroupingKey" => grouping_key(error),
|
|
462
|
+
"issueGroupingSource" => "exception_type_job_file"
|
|
463
|
+
)
|
|
464
|
+
attributes = IssueDiagnostics.from_exception(
|
|
465
|
+
error,
|
|
466
|
+
title: IssueDiagnostics.safe_exception_type(error),
|
|
467
|
+
message: @runtime.configuration.capture_exception_messages? ? error.message : nil,
|
|
468
|
+
mechanism_type: "rails.active_job",
|
|
469
|
+
handled: false,
|
|
470
|
+
metadata: metadata
|
|
471
|
+
)
|
|
472
|
+
Trace.with_context(@context) do
|
|
473
|
+
@client.issue("ruby_active_job_issue_#{@context.span_id}", Time.now.utc.iso8601, attributes)
|
|
474
|
+
end
|
|
475
|
+
rescue StandardError => capture_error
|
|
476
|
+
@runtime.report_error("active_job_capture", capture_error)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
private
|
|
480
|
+
|
|
481
|
+
def operation_metadata
|
|
482
|
+
@runtime.metadata.merge("activeJob.class" => @job_class).tap do |metadata|
|
|
483
|
+
metadata["retryCount"] = @retry_count unless @retry_count.nil?
|
|
484
|
+
metadata["queueWaitMs"] = @queue_wait_ms unless @queue_wait_ms.nil?
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def grouping_key(error)
|
|
489
|
+
frame = IssueDiagnostics.stack_frames_from_exception(error).first
|
|
490
|
+
file = frame.nil? ? "" : frame.fetch("filename")
|
|
491
|
+
values = [IssueDiagnostics.safe_exception_type(error), @job_class, file]
|
|
492
|
+
"rails-active-job-#{Digest::SHA256.hexdigest(values.join("\n"))}"
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def bounded_identifier(value, fallback)
|
|
496
|
+
text = value.to_s
|
|
497
|
+
text.match?(/\A[A-Za-z_][A-Za-z0-9_:.-]{0,254}\z/) ? text : fallback
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def normalized_retry_count(value)
|
|
501
|
+
[[value, 0].max, 1_000].min if value.is_a?(Integer)
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
private_constant :ActiveJobOperation
|
|
505
|
+
|
|
506
|
+
# Adapter-neutral ActiveJob tracing installed through the Rails lazy-load hook.
|
|
507
|
+
module ActiveJobExtension
|
|
508
|
+
def enqueue(options = {})
|
|
509
|
+
operation = ActiveJobOperation.create(LogBrew::Rails.runtime, self, :enqueue)
|
|
510
|
+
return super if operation.nil?
|
|
511
|
+
|
|
512
|
+
previous = @logbrew_enqueue_operation
|
|
513
|
+
@logbrew_enqueue_operation = operation
|
|
514
|
+
operation.around { super }
|
|
515
|
+
ensure
|
|
516
|
+
@logbrew_enqueue_operation = previous unless operation.nil?
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def serialize
|
|
520
|
+
payload = super
|
|
521
|
+
operation = @logbrew_enqueue_operation
|
|
522
|
+
return payload if operation.nil?
|
|
523
|
+
|
|
524
|
+
begin
|
|
525
|
+
payload[QueueCarrier::KEY] = QueueCarrier.create(operation.context)
|
|
526
|
+
rescue StandardError => error
|
|
527
|
+
LogBrew::Rails.runtime&.report_error("active_job_capture", error)
|
|
528
|
+
end
|
|
529
|
+
payload
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def deserialize(payload)
|
|
533
|
+
result = super
|
|
534
|
+
begin
|
|
535
|
+
@logbrew_queue_carrier = QueueCarrier.read(payload[QueueCarrier::KEY]) if payload.is_a?(Hash)
|
|
536
|
+
rescue StandardError => error
|
|
537
|
+
LogBrew::Rails.runtime&.report_error("active_job_capture", error)
|
|
538
|
+
end
|
|
539
|
+
result
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def perform_now
|
|
543
|
+
operation = ActiveJobOperation.create(LogBrew::Rails.runtime, self, :perform, carrier: @logbrew_queue_carrier)
|
|
544
|
+
return super if operation.nil?
|
|
545
|
+
|
|
546
|
+
previous = @logbrew_perform_operation
|
|
547
|
+
@logbrew_perform_operation = operation
|
|
548
|
+
begin
|
|
549
|
+
Trace.with_context(operation.context) { super }
|
|
550
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
551
|
+
operation.finish(error)
|
|
552
|
+
operation.capture_terminal_issue(error)
|
|
553
|
+
raise
|
|
554
|
+
ensure
|
|
555
|
+
unless operation.nil?
|
|
556
|
+
operation.finish
|
|
557
|
+
@logbrew_perform_operation = previous
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def _perform_job
|
|
563
|
+
operation = @logbrew_perform_operation
|
|
564
|
+
operation.nil? ? super : operation.around { super }
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
398
568
|
# Buffers only the slowest request-local framework operations without raw
|
|
399
569
|
# SQL, cache keys, absolute paths, or exception messages.
|
|
400
570
|
module RequestOperations
|
data/lib/logbrew/sidekiq.rb
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../logbrew"
|
|
4
|
+
require_relative "queue_carrier"
|
|
4
5
|
|
|
5
6
|
module LogBrew
|
|
6
7
|
# Explicit Sidekiq middleware for app-owned client and server chains.
|
|
7
8
|
module Sidekiq
|
|
8
|
-
CARRIER_KEY = "logbrew".freeze
|
|
9
|
-
CARRIER_VERSION = 1
|
|
10
|
-
MAX_QUEUE_WAIT_MS = 604_800_000
|
|
11
9
|
MAX_RETRY_COUNT = 1_000
|
|
12
10
|
MAX_REPORTED_FAILURES = 1_024
|
|
13
|
-
private_constant :
|
|
11
|
+
private_constant :MAX_RETRY_COUNT, :MAX_REPORTED_FAILURES
|
|
14
12
|
|
|
15
13
|
class TraceOperation
|
|
16
14
|
attr_reader :context, :traceparent
|
|
@@ -197,17 +195,13 @@ module LogBrew
|
|
|
197
195
|
def around_client(job)
|
|
198
196
|
return yield unless capture_enabled?
|
|
199
197
|
return yield unless job.is_a?(Hash)
|
|
200
|
-
return yield if job.key?(
|
|
198
|
+
return yield if active_job_wrapper?(job) || job.key?(QueueCarrier::KEY)
|
|
201
199
|
|
|
202
200
|
operation = prepare_operation(name: "sidekiq.enqueue", source: "sidekiq.client", continue_current: true)
|
|
203
201
|
return yield if operation.nil?
|
|
204
202
|
|
|
205
203
|
begin
|
|
206
|
-
job[
|
|
207
|
-
"version" => CARRIER_VERSION,
|
|
208
|
-
"traceparent" => operation.traceparent,
|
|
209
|
-
"enqueuedAtMs" => wall_time_ms
|
|
210
|
-
}
|
|
204
|
+
job[QueueCarrier::KEY] = QueueCarrier.create(operation.context)
|
|
211
205
|
rescue StandardError => error
|
|
212
206
|
report_capture_error(error)
|
|
213
207
|
return yield
|
|
@@ -218,16 +212,17 @@ module LogBrew
|
|
|
218
212
|
def around_server(job)
|
|
219
213
|
return yield unless capture_enabled?
|
|
220
214
|
return yield unless job.is_a?(Hash)
|
|
215
|
+
return yield if active_job_wrapper?(job)
|
|
221
216
|
|
|
222
217
|
begin
|
|
223
|
-
carrier =
|
|
218
|
+
carrier = QueueCarrier.read(job[QueueCarrier::KEY])
|
|
224
219
|
retry_count = normalized_retry_count(job["retry_count"], job.key?("retry_count"))
|
|
225
220
|
operation = prepare_operation(
|
|
226
221
|
name: "sidekiq.perform",
|
|
227
222
|
source: "sidekiq.server",
|
|
228
223
|
carrier: carrier,
|
|
229
224
|
retry_count: retry_count,
|
|
230
|
-
queue_wait_ms: queue_wait_ms(carrier)
|
|
225
|
+
queue_wait_ms: QueueCarrier.queue_wait_ms(carrier)
|
|
231
226
|
)
|
|
232
227
|
rescue StandardError => error
|
|
233
228
|
report_capture_error(error)
|
|
@@ -328,15 +323,9 @@ module LogBrew
|
|
|
328
323
|
end
|
|
329
324
|
|
|
330
325
|
def prepare_operation(name:, source:, carrier: nil, retry_count: nil, queue_wait_ms: nil, continue_current: false)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
Trace.create(
|
|
335
|
-
trace_id: parsed.trace_id,
|
|
336
|
-
span_id: Trace.generate_span_id,
|
|
337
|
-
parent_span_id: parsed.parent_span_id,
|
|
338
|
-
trace_flags: parsed.trace_flags
|
|
339
|
-
)
|
|
326
|
+
parent = continue_current ? Trace.current : nil
|
|
327
|
+
context = if carrier
|
|
328
|
+
QueueCarrier.child_context(carrier)
|
|
340
329
|
elsif parent
|
|
341
330
|
Trace.create(
|
|
342
331
|
trace_id: parent.trace_id,
|
|
@@ -360,18 +349,12 @@ module LogBrew
|
|
|
360
349
|
nil
|
|
361
350
|
end
|
|
362
351
|
|
|
363
|
-
def
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
return nil unless value["enqueuedAtMs"].is_a?(Integer) && value["enqueuedAtMs"].between?(0, 9_007_199_254_740_991)
|
|
370
|
-
|
|
371
|
-
Traceparent.parse(value["traceparent"])
|
|
372
|
-
value
|
|
373
|
-
rescue SdkError
|
|
374
|
-
nil
|
|
352
|
+
def active_job_wrapper?(job)
|
|
353
|
+
wrapped = job["wrapped"]
|
|
354
|
+
arguments = job["args"]
|
|
355
|
+
payload = arguments.first if arguments.is_a?(Array)
|
|
356
|
+
wrapped.is_a?(String) && payload.is_a?(Hash) && payload["job_class"] == wrapped &&
|
|
357
|
+
!QueueCarrier.read(payload[QueueCarrier::KEY]).nil?
|
|
375
358
|
end
|
|
376
359
|
|
|
377
360
|
def normalized_retry_count(value, present)
|
|
@@ -392,17 +375,6 @@ module LogBrew
|
|
|
392
375
|
retry_count >= [limit - 1, 0].max
|
|
393
376
|
end
|
|
394
377
|
|
|
395
|
-
def queue_wait_ms(carrier)
|
|
396
|
-
return nil if carrier.nil?
|
|
397
|
-
|
|
398
|
-
elapsed = wall_time_ms - carrier.fetch("enqueuedAtMs")
|
|
399
|
-
[[elapsed, 0].max, MAX_QUEUE_WAIT_MS].min
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
def wall_time_ms
|
|
403
|
-
(Time.now.to_f * 1000.0).floor
|
|
404
|
-
end
|
|
405
|
-
|
|
406
378
|
def capture_span(operation, error)
|
|
407
379
|
@client.span(
|
|
408
380
|
"ruby_sidekiq_span_#{operation.context.span_id}",
|
data/lib/logbrew/trace.rb
CHANGED
|
@@ -240,7 +240,7 @@ module LogBrew
|
|
|
240
240
|
|
|
241
241
|
private
|
|
242
242
|
|
|
243
|
-
def capture_request_span(env, status_code, elapsed_ms, status)
|
|
243
|
+
def capture_request_span(env, status_code, elapsed_ms, status, timestamp)
|
|
244
244
|
context = rack_trace_context(env)
|
|
245
245
|
attributes = {
|
|
246
246
|
name: request_name(env),
|
|
@@ -252,31 +252,21 @@ module LogBrew
|
|
|
252
252
|
}
|
|
253
253
|
attributes[:parentSpanId] = context.parent_span_id if context && context.parent_span_id
|
|
254
254
|
|
|
255
|
-
@client.span(next_event_id("span"),
|
|
255
|
+
@client.span(next_event_id("span"), timestamp, attributes)
|
|
256
256
|
end
|
|
257
257
|
|
|
258
258
|
def trace_id(env)
|
|
259
|
-
|
|
260
|
-
return context.trace_id if context
|
|
261
|
-
|
|
262
|
-
super
|
|
259
|
+
rack_trace_context(env)&.trace_id || super
|
|
263
260
|
end
|
|
264
261
|
|
|
265
262
|
def span_id(env)
|
|
266
|
-
|
|
267
|
-
return context.span_id if context
|
|
268
|
-
|
|
269
|
-
super
|
|
263
|
+
rack_trace_context(env)&.span_id || super
|
|
270
264
|
end
|
|
271
265
|
|
|
272
266
|
def request_metadata(env, status_code)
|
|
273
267
|
Trace.add_metadata(super, rack_trace_context(env))
|
|
274
268
|
end
|
|
275
269
|
|
|
276
|
-
def exception_metadata(env, error)
|
|
277
|
-
Trace.add_metadata(super, rack_trace_context(env))
|
|
278
|
-
end
|
|
279
|
-
|
|
280
270
|
def rack_trace_context(env)
|
|
281
271
|
trace = env["logbrew.trace"] if env.respond_to?(:[])
|
|
282
272
|
trace.is_a?(TraceContext) ? trace : Trace.current
|
data/lib/logbrew/version.rb
CHANGED
data/lib/logbrew.rb
CHANGED
|
@@ -45,9 +45,9 @@ module LogBrew
|
|
|
45
45
|
metadata.each_with_object({}) { |(key, value), copied| copied[key.to_s] = value if primitive_metadata_value?(value) }
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def logbrew_timestamp
|
|
48
|
+
def logbrew_timestamp(precision = nil)
|
|
49
49
|
timestamp = @timestamp_provider.respond_to?(:call) ? @timestamp_provider.call : Time.now
|
|
50
|
-
timestamp.respond_to?(:iso8601) ? timestamp.iso8601 : timestamp.to_s
|
|
50
|
+
timestamp.respond_to?(:iso8601) ? timestamp.iso8601(*[precision].compact) : timestamp.to_s
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def capture_safely
|
|
@@ -412,36 +412,42 @@ module LogBrew
|
|
|
412
412
|
|
|
413
413
|
def call(env)
|
|
414
414
|
started_at = monotonic_time
|
|
415
|
-
|
|
416
|
-
|
|
415
|
+
started_timestamp, timestamp_error = request_timestamp
|
|
416
|
+
response = begin
|
|
417
|
+
@app.call(env)
|
|
417
418
|
rescue StandardError => error
|
|
418
|
-
|
|
419
|
-
capture_safely do
|
|
420
|
-
capture_exception_issue(env, error) if status_code >= 500
|
|
421
|
-
capture_request_span(env, status_code, duration_ms(started_at), status_code >= 500 ? "error" : "ok")
|
|
422
|
-
flush_if_configured
|
|
423
|
-
end
|
|
419
|
+
capture_request(env, exception_status_code(error), started_at, started_timestamp, timestamp_error, error)
|
|
424
420
|
raise
|
|
425
421
|
end
|
|
422
|
+
capture_request(env, rack_status(response), started_at, started_timestamp, timestamp_error)
|
|
423
|
+
response
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
private
|
|
426
427
|
|
|
427
|
-
|
|
428
|
+
def capture_request(env, status_code, started_at, timestamp, timestamp_error, error = nil)
|
|
428
429
|
capture_safely do
|
|
429
|
-
|
|
430
|
+
raise timestamp_error unless timestamp_error.nil?
|
|
431
|
+
capture_exception_issue(env, error, logbrew_timestamp(6)) if error && status_code >= 500
|
|
432
|
+
capture_request_span(env, status_code, duration_ms(started_at), status_code >= 500 ? "error" : "ok", timestamp)
|
|
430
433
|
flush_if_configured
|
|
431
434
|
end
|
|
432
|
-
response
|
|
433
435
|
end
|
|
434
436
|
|
|
435
|
-
|
|
437
|
+
def request_timestamp
|
|
438
|
+
[logbrew_timestamp(6), nil]
|
|
439
|
+
rescue StandardError => error
|
|
440
|
+
[nil, error]
|
|
441
|
+
end
|
|
436
442
|
|
|
437
443
|
def exception_status_code(_error)
|
|
438
444
|
500
|
|
439
445
|
end
|
|
440
446
|
|
|
441
|
-
def capture_request_span(env, status_code, elapsed_ms, status)
|
|
447
|
+
def capture_request_span(env, status_code, elapsed_ms, status, timestamp)
|
|
442
448
|
@client.span(
|
|
443
449
|
next_event_id("span"),
|
|
444
|
-
|
|
450
|
+
timestamp,
|
|
445
451
|
name: request_name(env),
|
|
446
452
|
traceId: trace_id(env),
|
|
447
453
|
spanId: span_id(env),
|
|
@@ -451,7 +457,7 @@ module LogBrew
|
|
|
451
457
|
)
|
|
452
458
|
end
|
|
453
459
|
|
|
454
|
-
def capture_exception_issue(env, error)
|
|
460
|
+
def capture_exception_issue(env, error, timestamp)
|
|
455
461
|
attributes = IssueDiagnostics.from_exception(
|
|
456
462
|
error,
|
|
457
463
|
message: @include_exception_message ? error.message : nil,
|
|
@@ -459,7 +465,7 @@ module LogBrew
|
|
|
459
465
|
handled: false,
|
|
460
466
|
metadata: exception_metadata(env, error)
|
|
461
467
|
)
|
|
462
|
-
@client.issue(next_event_id("issue"),
|
|
468
|
+
@client.issue(next_event_id("issue"), timestamp, attributes)
|
|
463
469
|
end
|
|
464
470
|
|
|
465
471
|
def next_event_id(kind)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: logbrew-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LogBrew
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Public LogBrew Ruby SDK with typed issue diagnostics, automatic Rails
|
|
14
14
|
request/error capture, and standard-library delivery.
|
|
@@ -38,6 +38,7 @@ files:
|
|
|
38
38
|
- lib/logbrew/operation_tracing.rb
|
|
39
39
|
- lib/logbrew/persistent_event_store.rb
|
|
40
40
|
- lib/logbrew/product_timeline.rb
|
|
41
|
+
- lib/logbrew/queue_carrier.rb
|
|
41
42
|
- lib/logbrew/rails.rb
|
|
42
43
|
- lib/logbrew/rails_integration.rb
|
|
43
44
|
- lib/logbrew/sidekiq.rb
|