logbrew-sdk 0.1.6 → 0.1.7
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 +10 -4
- data/lib/logbrew/operation_tracing.rb +26 -61
- data/lib/logbrew/rails.rb +3 -0
- data/lib/logbrew/rails_integration.rb +101 -60
- data/lib/logbrew/version.rb +1 -1
- data/lib/logbrew.rb +31 -73
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f086729efb0a0a98821ef3bce8d96b71066b202beff5656917d958e4ca119479
|
|
4
|
+
data.tar.gz: 7c5eafb18cda7d001dc8c72c9646852e857a46bd4e06508abeda5a23365489a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 583a7cdc7ccc4ea1435a31454c0d90cea390a7b9f37e1322c3f39bc84bf0b2c731aaa08abff5384675871f62082fd8fc98c5c2c7284ad89a1fb6c9b1221982e0
|
|
7
|
+
data.tar.gz: 5093fb1d5ea5540302c6a99c8d6fa7ce1b66142a55010c7d867e1f0539d1fe57525270486c59e58acd687a68bb51e44aaa62f480a3c119681648d9a876d70fbb
|
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
|
|
7
|
+
Public Ruby SDK for building, validating, previewing, and flushing LogBrew event batches, with automatic Rails request, database, cache, view, and error 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.7"
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
```bash
|
|
@@ -40,8 +40,14 @@ disabled and the application behaves normally. Set `LOGBREW_ENABLED=false` to
|
|
|
40
40
|
disable it explicitly. If the Gemfile uses `require: false`, load
|
|
41
41
|
`logbrew/rails` yourself after Rails.
|
|
42
42
|
|
|
43
|
-
The automatic integration records route-template request spans and
|
|
44
|
-
|
|
43
|
+
The automatic integration records route-template request spans and up to eight
|
|
44
|
+
slowest request-local Active Record, Rails cache, and Action View child spans.
|
|
45
|
+
The request span reports exact observed and captured operation counts plus
|
|
46
|
+
truncation. Operation evidence contains only the operation type, duration,
|
|
47
|
+
cache-hit state, relative `app/views` template path, and exception type when
|
|
48
|
+
present. It never contains SQL, cache keys or values, or absolute paths.
|
|
49
|
+
|
|
50
|
+
Escaped request failures include exception identity,
|
|
45
51
|
`rails.middleware` with `handled: false`, and up to 32 newest-first sanitized
|
|
46
52
|
frames. Handled Rails reports use `rails.error_reporter` with `handled: true`.
|
|
47
53
|
It does not record concrete request paths, query strings, request or response
|
|
@@ -3,34 +3,10 @@
|
|
|
3
3
|
module LogBrew
|
|
4
4
|
# Explicit dependency spans for app-owned database, cache, and queue work.
|
|
5
5
|
module OperationTracing
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/
|
|
9
|
-
|
|
10
|
-
/cache.?key/i,
|
|
11
|
-
/command/i,
|
|
12
|
-
/connection/i,
|
|
13
|
-
/cookie/i,
|
|
14
|
-
/dsn/i,
|
|
15
|
-
/header/i,
|
|
16
|
-
/host/i,
|
|
17
|
-
/\bjid\z/i,
|
|
18
|
-
/job.?id/i,
|
|
19
|
-
/key/i,
|
|
20
|
-
/message/i,
|
|
21
|
-
/param/i,
|
|
22
|
-
/pass#{'word'}/i,
|
|
23
|
-
/payload/i,
|
|
24
|
-
/query/i,
|
|
25
|
-
/sec#{'ret'}/i,
|
|
26
|
-
/sql/i,
|
|
27
|
-
/statement/i,
|
|
28
|
-
/to#{'ken'}/i,
|
|
29
|
-
/url/i,
|
|
30
|
-
/username/i,
|
|
31
|
-
/value/i
|
|
32
|
-
].freeze
|
|
33
|
-
private_constant :UNSAFE_METADATA_PATTERNS
|
|
6
|
+
UNSAFE_METADATA = /authorization|body|broker|cache.?key|command|connection|cookie|dsn|header|host|
|
|
7
|
+
\bjid\z|job.?id|key|message|param|pass#{'word'}|payload|query|sec#{'ret'}|sql|statement|
|
|
8
|
+
to#{'ken'}|url|username|value/ix
|
|
9
|
+
private_constant :UNSAFE_METADATA
|
|
34
10
|
|
|
35
11
|
module_function
|
|
36
12
|
|
|
@@ -70,20 +46,22 @@ module LogBrew
|
|
|
70
46
|
end
|
|
71
47
|
|
|
72
48
|
def capture_span(client, kind, name, context, started_at, options, error)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
49
|
+
Trace.with_context(context) do
|
|
50
|
+
client.span(
|
|
51
|
+
event_id(kind, context, options),
|
|
52
|
+
timestamp(options),
|
|
53
|
+
{
|
|
54
|
+
name: "#{kind}.operation:#{name}",
|
|
55
|
+
traceId: context.trace_id,
|
|
56
|
+
spanId: context.span_id,
|
|
57
|
+
parentSpanId: context.parent_span_id,
|
|
58
|
+
status: error ? "error" : "ok",
|
|
59
|
+
durationMs: duration_ms(started_at, options),
|
|
60
|
+
metadata: span_metadata(kind, options, error),
|
|
61
|
+
events: span_events(error)
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
end
|
|
87
65
|
rescue StandardError => capture_error
|
|
88
66
|
on_error = read_option(options, :on_error)
|
|
89
67
|
begin
|
|
@@ -99,7 +77,7 @@ module LogBrew
|
|
|
99
77
|
|
|
100
78
|
Trace.create(
|
|
101
79
|
trace_id: parent.trace_id,
|
|
102
|
-
span_id: generate_span_id,
|
|
80
|
+
span_id: Trace.generate_span_id,
|
|
103
81
|
parent_span_id: parent.span_id,
|
|
104
82
|
trace_flags: parent.trace_flags
|
|
105
83
|
)
|
|
@@ -107,7 +85,7 @@ module LogBrew
|
|
|
107
85
|
|
|
108
86
|
def span_metadata(kind, options, error)
|
|
109
87
|
sanitized_metadata(read_option(options, :metadata)).tap do |metadata|
|
|
110
|
-
metadata["source"] = "#{kind}.operation"
|
|
88
|
+
metadata["source"] = read_option(options, :source) || "#{kind}.operation"
|
|
111
89
|
add_option(metadata, "#{kind}.system", read_option(options, :system))
|
|
112
90
|
add_option(metadata, "#{kind}.operation", read_option(options, :operation))
|
|
113
91
|
add_option(metadata, "#{kind}.target", read_option(options, :target))
|
|
@@ -137,29 +115,22 @@ module LogBrew
|
|
|
137
115
|
normalized_key = key.to_s
|
|
138
116
|
next if normalized_key.strip.empty?
|
|
139
117
|
next if unsafe_metadata_key?(normalized_key)
|
|
140
|
-
next unless primitive_metadata_value?(value)
|
|
118
|
+
next unless CaptureHelpers.primitive_metadata_value?(value)
|
|
141
119
|
|
|
142
120
|
copied[normalized_key] = value
|
|
143
121
|
end
|
|
144
122
|
end
|
|
145
123
|
|
|
146
124
|
def unsafe_metadata_key?(key)
|
|
147
|
-
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def primitive_metadata_value?(value)
|
|
151
|
-
return true if value.nil? || value == true || value == false
|
|
152
|
-
return true if value.is_a?(String) || value.is_a?(Integer)
|
|
153
|
-
|
|
154
|
-
value.is_a?(Float) && value.finite?
|
|
125
|
+
key.match?(UNSAFE_METADATA)
|
|
155
126
|
end
|
|
156
127
|
|
|
157
128
|
def add_option(metadata, key, value)
|
|
158
|
-
metadata[key] = value if primitive_metadata_value?(value) && !(value.is_a?(String) && value.strip.empty?)
|
|
129
|
+
metadata[key] = value if CaptureHelpers.primitive_metadata_value?(value) && !(value.is_a?(String) && value.strip.empty?)
|
|
159
130
|
end
|
|
160
131
|
|
|
161
132
|
def read_option(options, key)
|
|
162
|
-
options[key]
|
|
133
|
+
options.key?(key) ? options[key] : options[key.to_s]
|
|
163
134
|
end
|
|
164
135
|
|
|
165
136
|
def event_id(kind, context, options)
|
|
@@ -184,11 +155,5 @@ module LogBrew
|
|
|
184
155
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
185
156
|
end
|
|
186
157
|
|
|
187
|
-
def generate_span_id
|
|
188
|
-
loop do
|
|
189
|
-
value = SecureRandom.hex(8)
|
|
190
|
-
return value unless value.delete("0").empty?
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
158
|
end
|
|
194
159
|
end
|
data/lib/logbrew/rails.rb
CHANGED
|
@@ -77,6 +77,9 @@ module LogBrew
|
|
|
77
77
|
class RailsRailtie < ::Rails::Railtie
|
|
78
78
|
initializer "logbrew.install", after: :load_config_initializers do |application|
|
|
79
79
|
runtime = LogBrew::Rails.install(application: application)
|
|
80
|
+
if runtime.configuration.enabled?
|
|
81
|
+
LogBrew::Rails.const_get(:RequestOperations).install(::ActiveSupport::Notifications)
|
|
82
|
+
end
|
|
80
83
|
middleware = application.config.middleware
|
|
81
84
|
if defined?(::ActionDispatch::ShowExceptions)
|
|
82
85
|
middleware.insert_after(
|
|
@@ -120,7 +120,6 @@ module LogBrew
|
|
|
120
120
|
text = value.to_s.strip
|
|
121
121
|
text.empty? ? nil : text
|
|
122
122
|
end
|
|
123
|
-
private_class_method :optional_text
|
|
124
123
|
|
|
125
124
|
def self.server_key_value(value)
|
|
126
125
|
text = optional_text(value)
|
|
@@ -131,7 +130,6 @@ module LogBrew
|
|
|
131
130
|
|
|
132
131
|
text
|
|
133
132
|
end
|
|
134
|
-
private_class_method :server_key_value
|
|
135
133
|
|
|
136
134
|
def self.bounded_label(value, label)
|
|
137
135
|
text = optional_text(value)
|
|
@@ -142,7 +140,6 @@ module LogBrew
|
|
|
142
140
|
|
|
143
141
|
text.freeze
|
|
144
142
|
end
|
|
145
|
-
private_class_method :bounded_label
|
|
146
143
|
|
|
147
144
|
def self.optional_bounded_label(value, label)
|
|
148
145
|
text = optional_text(value)
|
|
@@ -150,7 +147,6 @@ module LogBrew
|
|
|
150
147
|
|
|
151
148
|
bounded_label(text, label)
|
|
152
149
|
end
|
|
153
|
-
private_class_method :optional_bounded_label
|
|
154
150
|
|
|
155
151
|
def self.boolean_value(environment, name, default)
|
|
156
152
|
value = optional_text(environment[name])
|
|
@@ -163,7 +159,6 @@ module LogBrew
|
|
|
163
159
|
raise SdkError.new("configuration_error", "#{name} must be true or false")
|
|
164
160
|
end
|
|
165
161
|
end
|
|
166
|
-
private_class_method :boolean_value
|
|
167
162
|
|
|
168
163
|
def self.integer_value(environment, name, default, minimum, maximum)
|
|
169
164
|
text = optional_text(environment[name])
|
|
@@ -179,7 +174,6 @@ module LogBrew
|
|
|
179
174
|
"#{name} must be an integer between #{minimum} and #{maximum}"
|
|
180
175
|
)
|
|
181
176
|
end
|
|
182
|
-
private_class_method :integer_value
|
|
183
177
|
|
|
184
178
|
def self.endpoint_value(value)
|
|
185
179
|
text = optional_text(value) || DEFAULT_ENDPOINT
|
|
@@ -203,7 +197,8 @@ module LogBrew
|
|
|
203
197
|
rescue URI::InvalidURIError
|
|
204
198
|
raise SdkError.new("configuration_error", "LOGBREW_ENDPOINT must be a valid HTTP URL")
|
|
205
199
|
end
|
|
206
|
-
private_class_method :
|
|
200
|
+
private_class_method :optional_text, :server_key_value, :bounded_label,
|
|
201
|
+
:optional_bounded_label, :boolean_value, :integer_value, :endpoint_value
|
|
207
202
|
|
|
208
203
|
def initialize(
|
|
209
204
|
enabled:,
|
|
@@ -249,6 +244,7 @@ module LogBrew
|
|
|
249
244
|
|
|
250
245
|
# Owns one lazy automatic-delivery client per operating-system process.
|
|
251
246
|
class Runtime
|
|
247
|
+
include CaptureHelpers
|
|
252
248
|
attr_reader :configuration
|
|
253
249
|
|
|
254
250
|
def initialize(
|
|
@@ -320,6 +316,16 @@ module LogBrew
|
|
|
320
316
|
nil
|
|
321
317
|
end
|
|
322
318
|
|
|
319
|
+
def metadata
|
|
320
|
+
@metadata ||= {
|
|
321
|
+
"service" => @configuration.service_name,
|
|
322
|
+
"environment" => @configuration.app_environment,
|
|
323
|
+
"framework" => "rails",
|
|
324
|
+
"framework.version" => @configuration.rails_version,
|
|
325
|
+
"release" => @configuration.release
|
|
326
|
+
}.compact.freeze
|
|
327
|
+
end
|
|
328
|
+
|
|
323
329
|
private
|
|
324
330
|
|
|
325
331
|
def prepare_process_state
|
|
@@ -372,7 +378,6 @@ module LogBrew
|
|
|
372
378
|
|
|
373
379
|
def record_process_context(created)
|
|
374
380
|
timestamp = logbrew_timestamp
|
|
375
|
-
metadata = base_metadata
|
|
376
381
|
created.environment(
|
|
377
382
|
"ruby_rails_environment_#{SecureRandom.hex(8)}",
|
|
378
383
|
timestamp,
|
|
@@ -388,29 +393,84 @@ module LogBrew
|
|
|
388
393
|
metadata: metadata
|
|
389
394
|
)
|
|
390
395
|
end
|
|
396
|
+
end
|
|
391
397
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
398
|
+
# Buffers only the slowest request-local framework operations without raw
|
|
399
|
+
# SQL, cache keys, absolute paths, or exception messages.
|
|
400
|
+
module RequestOperations
|
|
401
|
+
LIMIT = 8
|
|
402
|
+
STATE_KEY = :logbrew_rails_request_operations
|
|
403
|
+
EVENTS = /\A(?:(sql)\.active_record|(cache)_(read|write|delete|exist\?|fetch_hit|generate)\.active_support|(render)_(template|partial|collection)\.action_view)\z/
|
|
404
|
+
private_constant :LIMIT, :STATE_KEY, :EVENTS
|
|
405
|
+
|
|
406
|
+
module_function
|
|
407
|
+
|
|
408
|
+
def install(notifications)
|
|
409
|
+
@mutex ||= Mutex.new
|
|
410
|
+
@mutex.synchronize { @subscription ||= notifications.subscribe(EVENTS) { |*event| record(*event) } }
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def within
|
|
414
|
+
previous = Thread.current[STATE_KEY]
|
|
415
|
+
Thread.current[STATE_KEY] = [0, []]
|
|
416
|
+
yield
|
|
417
|
+
ensure
|
|
418
|
+
Thread.current[STATE_KEY] = previous
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def record(name, started_at, finished_at, _id = nil, payload = {})
|
|
422
|
+
state = Thread.current[STATE_KEY]
|
|
423
|
+
match = EVENTS.match(name.to_s)
|
|
424
|
+
return if state.nil? || match.nil? || !payload.is_a?(Hash)
|
|
425
|
+
duration_ms = ((finished_at - started_at) * 1_000).round(3)
|
|
426
|
+
return unless duration_ms.finite? && !duration_ms.negative?
|
|
427
|
+
kind, operation = match[1] ? %w[database query] : [match[2] ? "cache" : "view", match[3] || match[5]]
|
|
428
|
+
metadata = { "rails.notification" => name.to_s }
|
|
429
|
+
metadata["cache.hit"] = payload[:hit] if kind == "cache" && [true, false].include?(payload[:hit])
|
|
430
|
+
template = template_path(payload[:identifier]) if kind == "view"
|
|
431
|
+
metadata["view.template"] = template unless template.nil?
|
|
432
|
+
error = payload[:exception_object]
|
|
433
|
+
state[0] += 1
|
|
434
|
+
timestamp = started_at.iso8601(6) if started_at.respond_to?(:iso8601)
|
|
435
|
+
timestamp ||= Time.now.utc.iso8601(6)
|
|
436
|
+
state[1] << [duration_ms, kind, operation, metadata, error.is_a?(Exception) ? error : nil, timestamp, started_at]
|
|
437
|
+
state[1].sort_by! { |item| [item[0], item[1], item[2]] }
|
|
438
|
+
state[1].shift if state[1].length > LIMIT
|
|
439
|
+
rescue StandardError
|
|
440
|
+
nil
|
|
399
441
|
end
|
|
400
442
|
|
|
401
|
-
def
|
|
402
|
-
|
|
403
|
-
|
|
443
|
+
def snapshot
|
|
444
|
+
Thread.current[STATE_KEY] || [0, []]
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def template_path(identifier)
|
|
448
|
+
text = identifier.to_s.tr("\\", "/")
|
|
449
|
+
relative = text.split("/app/views/", 2)[1]
|
|
450
|
+
return if relative.nil? || relative.empty? || relative.bytesize > 255 || relative.match?(/[[:cntrl:]]/) || relative.split("/").include?("..")
|
|
404
451
|
|
|
405
|
-
|
|
452
|
+
relative
|
|
406
453
|
end
|
|
454
|
+
private_class_method :template_path
|
|
407
455
|
end
|
|
456
|
+
private_constant :RequestOperations
|
|
408
457
|
|
|
409
458
|
# Internal Rack adapter that replaces concrete request paths with Rails
|
|
410
459
|
# route templates while reusing the core request/error lifecycle.
|
|
411
460
|
class RailsRackMiddleware < LogBrew::RackMiddleware
|
|
412
461
|
private
|
|
413
462
|
|
|
463
|
+
def capture_request_span(*arguments)
|
|
464
|
+
super.tap do
|
|
465
|
+
RequestOperations.snapshot[1].sort_by(&:last).each do |duration_ms, kind, name, metadata, error, timestamp|
|
|
466
|
+
LogBrew::OperationTracing.capture_span(
|
|
467
|
+
@client, kind, name, LogBrew::OperationTracing.child_context, nil,
|
|
468
|
+
{ duration_ms: duration_ms, timestamp: timestamp, source: "rails.active_support", system: kind == "database" ? "active_record" : "rails", operation: name, metadata: metadata, on_error: @on_error }, error
|
|
469
|
+
)
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
414
474
|
def exception_mechanism_type
|
|
415
475
|
"rails.middleware"
|
|
416
476
|
end
|
|
@@ -442,17 +502,21 @@ module LogBrew
|
|
|
442
502
|
end
|
|
443
503
|
|
|
444
504
|
def request_metadata(env, status_code)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
505
|
+
super.tap do |metadata|
|
|
506
|
+
metadata.delete("http.path")
|
|
507
|
+
metadata.delete("action_dispatch.request_id")
|
|
508
|
+
metadata.delete("HTTP_X_REQUEST_ID")
|
|
509
|
+
metadata["source"] = "rails"
|
|
510
|
+
metadata["http.route"] = route_template(env)
|
|
511
|
+
metadata["http.status_class"] = "#{status_code.to_i / 100}xx"
|
|
512
|
+
controller, action = controller_and_action(env)
|
|
513
|
+
metadata["rails.controller"] = controller unless controller.nil?
|
|
514
|
+
metadata["rails.action"] = action unless action.nil?
|
|
515
|
+
observed, operations = RequestOperations.snapshot
|
|
516
|
+
metadata["rails.operations.observed"] = observed
|
|
517
|
+
metadata["rails.operations.captured"] = operations.length
|
|
518
|
+
metadata["rails.operations.truncated"] = observed > operations.length
|
|
519
|
+
end
|
|
456
520
|
end
|
|
457
521
|
|
|
458
522
|
def route_template(env)
|
|
@@ -486,8 +550,7 @@ module LogBrew
|
|
|
486
550
|
parameters = env["action_dispatch.request.path_parameters"]
|
|
487
551
|
return [nil, nil] unless parameters.is_a?(Hash)
|
|
488
552
|
|
|
489
|
-
[bounded_identifier(parameters[
|
|
490
|
-
bounded_identifier(parameters[:action] || parameters["action"])]
|
|
553
|
+
%i[controller action].map { |key| bounded_identifier(parameters[key] || parameters[key.to_s]) }
|
|
491
554
|
end
|
|
492
555
|
|
|
493
556
|
def bounded_route(value)
|
|
@@ -500,12 +563,7 @@ module LogBrew
|
|
|
500
563
|
end
|
|
501
564
|
|
|
502
565
|
def bounded_identifier(value)
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
identifier = value.to_s
|
|
506
|
-
return nil unless identifier.match?(/\A[a-zA-Z0-9_\/.-]{1,128}\z/)
|
|
507
|
-
|
|
508
|
-
identifier
|
|
566
|
+
value.to_s[/\A[a-zA-Z0-9_\/.-]{1,128}\z/]
|
|
509
567
|
end
|
|
510
568
|
end
|
|
511
569
|
private_constant :RailsRackMiddleware
|
|
@@ -530,7 +588,7 @@ module LogBrew
|
|
|
530
588
|
adapter = adapter_for(active_client)
|
|
531
589
|
return @app.call(environment) if adapter.nil?
|
|
532
590
|
|
|
533
|
-
adapter.call(environment)
|
|
591
|
+
RequestOperations.within { adapter.call(environment) }
|
|
534
592
|
end
|
|
535
593
|
|
|
536
594
|
private
|
|
@@ -543,7 +601,7 @@ module LogBrew
|
|
|
543
601
|
@app,
|
|
544
602
|
client: active_client,
|
|
545
603
|
flush_on_response: false,
|
|
546
|
-
metadata:
|
|
604
|
+
metadata: @runtime.metadata,
|
|
547
605
|
include_exception_message: @runtime.configuration.capture_exception_messages?,
|
|
548
606
|
include_exception_backtrace: @runtime.configuration.include_exception_backtrace?,
|
|
549
607
|
on_error: ->(error) { @runtime.report_error("request_capture", error) }
|
|
@@ -556,17 +614,6 @@ module LogBrew
|
|
|
556
614
|
nil
|
|
557
615
|
end
|
|
558
616
|
|
|
559
|
-
def base_metadata
|
|
560
|
-
configuration = @runtime.configuration
|
|
561
|
-
{
|
|
562
|
-
"service" => configuration.service_name,
|
|
563
|
-
"environment" => configuration.app_environment,
|
|
564
|
-
"framework" => "rails",
|
|
565
|
-
"framework.version" => configuration.rails_version
|
|
566
|
-
}.tap do |metadata|
|
|
567
|
-
metadata["release"] = configuration.release unless configuration.release.nil?
|
|
568
|
-
end
|
|
569
|
-
end
|
|
570
617
|
end
|
|
571
618
|
|
|
572
619
|
# Rails.error subscriber for handled reports. Unhandled errors stay owned
|
|
@@ -606,18 +653,12 @@ module LogBrew
|
|
|
606
653
|
@mutex.synchronize do
|
|
607
654
|
return @subscriber if @subscriber_client.equal?(active_client) && !@subscriber.nil?
|
|
608
655
|
|
|
609
|
-
configuration = @runtime.configuration
|
|
610
656
|
@subscriber = LogBrew::RailsErrorSubscriber.new(
|
|
611
657
|
client: active_client,
|
|
612
658
|
flush_on_report: false,
|
|
613
|
-
metadata:
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
"framework" => "rails",
|
|
617
|
-
"framework.version" => configuration.rails_version
|
|
618
|
-
},
|
|
619
|
-
include_exception_message: configuration.capture_exception_messages?,
|
|
620
|
-
include_exception_backtrace: configuration.include_exception_backtrace?,
|
|
659
|
+
metadata: @runtime.metadata,
|
|
660
|
+
include_exception_message: @runtime.configuration.capture_exception_messages?,
|
|
661
|
+
include_exception_backtrace: @runtime.configuration.include_exception_backtrace?,
|
|
621
662
|
on_error: ->(error) { @runtime.report_error("handled_error_capture", error) }
|
|
622
663
|
)
|
|
623
664
|
@subscriber_client = active_client
|
data/lib/logbrew/version.rb
CHANGED
data/lib/logbrew.rb
CHANGED
|
@@ -33,6 +33,32 @@ module LogBrew
|
|
|
33
33
|
METRIC_KINDS = METRIC_TEMPORALITIES_BY_KIND.keys.freeze
|
|
34
34
|
NON_NEGATIVE_METRIC_KINDS = %w[counter histogram].freeze
|
|
35
35
|
|
|
36
|
+
module CaptureHelpers
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
def primitive_metadata_value?(value)
|
|
40
|
+
value.nil? || value == true || value == false || value.is_a?(String) || value.is_a?(Integer) ||
|
|
41
|
+
(value.is_a?(Float) && value.finite?)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def copy_metadata(metadata)
|
|
45
|
+
metadata.each_with_object({}) { |(key, value), copied| copied[key.to_s] = value if primitive_metadata_value?(value) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def logbrew_timestamp
|
|
49
|
+
timestamp = @timestamp_provider.respond_to?(:call) ? @timestamp_provider.call : Time.now
|
|
50
|
+
timestamp.respond_to?(:iso8601) ? timestamp.iso8601 : timestamp.to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def capture_safely
|
|
54
|
+
yield
|
|
55
|
+
rescue StandardError => error
|
|
56
|
+
@on_error.call(error) if @on_error.respond_to?(:call)
|
|
57
|
+
raise error if @raise_errors
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
private_constant :CaptureHelpers
|
|
61
|
+
|
|
36
62
|
class SdkError < StandardError
|
|
37
63
|
attr_reader :code
|
|
38
64
|
|
|
@@ -202,6 +228,7 @@ module LogBrew
|
|
|
202
228
|
end
|
|
203
229
|
|
|
204
230
|
class Logger < ::Logger
|
|
231
|
+
include CaptureHelpers
|
|
205
232
|
DEFAULT_LOGGER_NAME = "ruby-logger"
|
|
206
233
|
SEVERITY_TO_LOGBREW_LEVEL = {
|
|
207
234
|
::Logger::DEBUG => "info",
|
|
@@ -306,13 +333,6 @@ module LogBrew
|
|
|
306
333
|
raise error if @raise_errors
|
|
307
334
|
end
|
|
308
335
|
|
|
309
|
-
def logbrew_timestamp
|
|
310
|
-
timestamp = @timestamp_provider.respond_to?(:call) ? @timestamp_provider.call : Time.now
|
|
311
|
-
return timestamp.iso8601 if timestamp.respond_to?(:iso8601)
|
|
312
|
-
|
|
313
|
-
timestamp.to_s
|
|
314
|
-
end
|
|
315
|
-
|
|
316
336
|
def logbrew_message(message)
|
|
317
337
|
return message.message if message.is_a?(Exception)
|
|
318
338
|
return message if message.is_a?(String)
|
|
@@ -347,18 +367,6 @@ module LogBrew
|
|
|
347
367
|
metadata["exceptionBacktrace"] = exception.backtrace.join("\n") if @include_exception_backtrace && exception.backtrace
|
|
348
368
|
end
|
|
349
369
|
|
|
350
|
-
def copy_metadata(metadata)
|
|
351
|
-
metadata.each_with_object({}) do |(key, value), copied|
|
|
352
|
-
copied[key.to_s] = value if primitive_metadata_value?(value)
|
|
353
|
-
end
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
def primitive_metadata_value?(value)
|
|
357
|
-
return true if value.nil? || value == true || value == false
|
|
358
|
-
return true if value.is_a?(String) || value.is_a?(Integer)
|
|
359
|
-
|
|
360
|
-
value.is_a?(Float) && value.finite?
|
|
361
|
-
end
|
|
362
370
|
end
|
|
363
371
|
# Rack-compatible middleware for Rails, Sinatra, and other Rack-based Ruby apps.
|
|
364
372
|
#
|
|
@@ -366,6 +374,7 @@ module LogBrew
|
|
|
366
374
|
# exceptions as issue plus error-span events. It does not require the rack or
|
|
367
375
|
# rails gems at runtime; any app object that responds to `call(env)` is enough.
|
|
368
376
|
class RackMiddleware
|
|
377
|
+
include CaptureHelpers
|
|
369
378
|
DEFAULT_EVENT_ID_PREFIX = "ruby_rack"
|
|
370
379
|
DEFAULT_SPAN_LOGGER = "rack"
|
|
371
380
|
|
|
@@ -407,7 +416,7 @@ module LogBrew
|
|
|
407
416
|
response = @app.call(env)
|
|
408
417
|
rescue StandardError => error
|
|
409
418
|
status_code = exception_status_code(error)
|
|
410
|
-
|
|
419
|
+
capture_safely do
|
|
411
420
|
capture_exception_issue(env, error) if status_code >= 500
|
|
412
421
|
capture_request_span(env, status_code, duration_ms(started_at), status_code >= 500 ? "error" : "ok")
|
|
413
422
|
flush_if_configured
|
|
@@ -416,7 +425,7 @@ module LogBrew
|
|
|
416
425
|
end
|
|
417
426
|
|
|
418
427
|
status_code = rack_status(response)
|
|
419
|
-
|
|
428
|
+
capture_safely do
|
|
420
429
|
capture_request_span(env, status_code, duration_ms(started_at), status_code >= 500 ? "error" : "ok")
|
|
421
430
|
flush_if_configured
|
|
422
431
|
end
|
|
@@ -460,13 +469,6 @@ module LogBrew
|
|
|
460
469
|
end
|
|
461
470
|
end
|
|
462
471
|
|
|
463
|
-
def logbrew_timestamp
|
|
464
|
-
timestamp = @timestamp_provider.respond_to?(:call) ? @timestamp_provider.call : Time.now
|
|
465
|
-
return timestamp.iso8601 if timestamp.respond_to?(:iso8601)
|
|
466
|
-
|
|
467
|
-
timestamp.to_s
|
|
468
|
-
end
|
|
469
|
-
|
|
470
472
|
def request_name(env)
|
|
471
473
|
"#{request_method(env)} #{request_path(env)}"
|
|
472
474
|
end
|
|
@@ -562,19 +564,6 @@ module LogBrew
|
|
|
562
564
|
metadata[key] = value unless value.nil?
|
|
563
565
|
end
|
|
564
566
|
|
|
565
|
-
def copy_metadata(metadata)
|
|
566
|
-
metadata.each_with_object({}) do |(key, value), copied|
|
|
567
|
-
copied[key.to_s] = value if primitive_metadata_value?(value)
|
|
568
|
-
end
|
|
569
|
-
end
|
|
570
|
-
|
|
571
|
-
def primitive_metadata_value?(value)
|
|
572
|
-
return true if value.nil? || value == true || value == false
|
|
573
|
-
return true if value.is_a?(String) || value.is_a?(Integer)
|
|
574
|
-
|
|
575
|
-
value.is_a?(Float) && value.finite?
|
|
576
|
-
end
|
|
577
|
-
|
|
578
567
|
def monotonic_time
|
|
579
568
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
580
569
|
end
|
|
@@ -589,12 +578,6 @@ module LogBrew
|
|
|
589
578
|
@client.flush(@transport)
|
|
590
579
|
end
|
|
591
580
|
|
|
592
|
-
def safely_capture
|
|
593
|
-
yield
|
|
594
|
-
rescue StandardError => error
|
|
595
|
-
@on_error.call(error) if @on_error.respond_to?(:call)
|
|
596
|
-
raise error if @raise_errors
|
|
597
|
-
end
|
|
598
581
|
end
|
|
599
582
|
|
|
600
583
|
# Rails.error subscriber for handled and manually reported Rails exceptions.
|
|
@@ -603,6 +586,7 @@ module LogBrew
|
|
|
603
586
|
# initializer. This class avoids a hard Rails dependency so the core gem stays
|
|
604
587
|
# usable in plain Ruby and Rack apps.
|
|
605
588
|
class RailsErrorSubscriber
|
|
589
|
+
include CaptureHelpers
|
|
606
590
|
DEFAULT_EVENT_ID_PREFIX = "ruby_rails_error"
|
|
607
591
|
SEVERITY_TO_ISSUE_LEVEL = {
|
|
608
592
|
"info" => "info",
|
|
@@ -667,13 +651,6 @@ module LogBrew
|
|
|
667
651
|
|
|
668
652
|
private
|
|
669
653
|
|
|
670
|
-
def logbrew_timestamp
|
|
671
|
-
timestamp = @timestamp_provider.respond_to?(:call) ? @timestamp_provider.call : Time.now
|
|
672
|
-
return timestamp.iso8601 if timestamp.respond_to?(:iso8601)
|
|
673
|
-
|
|
674
|
-
timestamp.to_s
|
|
675
|
-
end
|
|
676
|
-
|
|
677
654
|
def error_title(error)
|
|
678
655
|
return error.class.name if error.is_a?(Exception)
|
|
679
656
|
|
|
@@ -743,31 +720,12 @@ module LogBrew
|
|
|
743
720
|
"rails-exception-#{Digest::SHA256.hexdigest(material)}"
|
|
744
721
|
end
|
|
745
722
|
|
|
746
|
-
def copy_metadata(metadata)
|
|
747
|
-
metadata.each_with_object({}) do |(key, value), copied|
|
|
748
|
-
copied[key.to_s] = value if primitive_metadata_value?(value)
|
|
749
|
-
end
|
|
750
|
-
end
|
|
751
|
-
|
|
752
|
-
def primitive_metadata_value?(value)
|
|
753
|
-
return true if value.nil? || value == true || value == false
|
|
754
|
-
return true if value.is_a?(String) || value.is_a?(Integer)
|
|
755
|
-
|
|
756
|
-
value.is_a?(Float) && value.finite?
|
|
757
|
-
end
|
|
758
|
-
|
|
759
723
|
def flush_if_configured
|
|
760
724
|
return unless @flush_on_report && !@transport.nil? && @client.pending_events.positive?
|
|
761
725
|
|
|
762
726
|
@client.flush(@transport)
|
|
763
727
|
end
|
|
764
728
|
|
|
765
|
-
def capture_safely
|
|
766
|
-
yield
|
|
767
|
-
rescue StandardError => error
|
|
768
|
-
@on_error.call(error) if @on_error.respond_to?(:call)
|
|
769
|
-
raise error if @raise_errors
|
|
770
|
-
end
|
|
771
729
|
end
|
|
772
730
|
|
|
773
731
|
module Validation
|
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.7
|
|
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-13 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.
|