brainzlab 0.1.0 → 0.1.2
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 +9 -0
- data/README.md +30 -0
- data/lib/brainzlab/beacon/client.rb +209 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +341 -3
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +141 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +227 -0
- data/lib/brainzlab/dendrite/client.rb +232 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
- data/lib/brainzlab/devtools/assets/devtools.js +322 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +180 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +376 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +155 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +94 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +70 -0
- data/lib/brainzlab/flux/provisioner.rb +57 -0
- data/lib/brainzlab/flux.rb +174 -0
- data/lib/brainzlab/instrumentation/active_record.rb +18 -1
- data/lib/brainzlab/instrumentation/aws.rb +179 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/resque.rb +115 -0
- data/lib/brainzlab/instrumentation/solid_queue.rb +198 -0
- data/lib/brainzlab/instrumentation/stripe.rb +164 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +104 -0
- data/lib/brainzlab/instrumentation.rb +72 -0
- data/lib/brainzlab/nerve/client.rb +217 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/instrumentation.rb +35 -2
- data/lib/brainzlab/pulse/propagation.rb +1 -1
- data/lib/brainzlab/pulse/tracer.rb +1 -1
- data/lib/brainzlab/pulse.rb +1 -1
- data/lib/brainzlab/rails/log_subscriber.rb +1 -2
- data/lib/brainzlab/rails/railtie.rb +36 -3
- data/lib/brainzlab/recall/provisioner.rb +17 -0
- data/lib/brainzlab/recall.rb +6 -1
- data/lib/brainzlab/reflex.rb +20 -5
- data/lib/brainzlab/sentinel/client.rb +218 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +62 -0
- data/lib/brainzlab/signal/provisioner.rb +55 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +290 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +265 -0
- data/lib/brainzlab/utilities/health_check.rb +296 -0
- data/lib/brainzlab/utilities/log_formatter.rb +256 -0
- data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
- data/lib/brainzlab/utilities.rb +17 -0
- data/lib/brainzlab/vault/cache.rb +80 -0
- data/lib/brainzlab/vault/client.rb +198 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +268 -0
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +128 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +157 -0
- data/lib/brainzlab.rb +101 -0
- metadata +62 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module DalliInstrumentation
|
|
6
|
+
TRACKED_COMMANDS = %w[get set add replace delete incr decr cas get_multi set_multi].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def install!
|
|
10
|
+
return unless defined?(::Dalli::Client)
|
|
11
|
+
|
|
12
|
+
install_client_instrumentation!
|
|
13
|
+
|
|
14
|
+
BrainzLab.debug_log("[Instrumentation] Dalli/Memcached instrumentation installed")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def install_client_instrumentation!
|
|
20
|
+
::Dalli::Client.class_eval do
|
|
21
|
+
TRACKED_COMMANDS.each do |cmd|
|
|
22
|
+
original_method = "original_#{cmd}"
|
|
23
|
+
next if method_defined?(original_method)
|
|
24
|
+
|
|
25
|
+
alias_method original_method, cmd
|
|
26
|
+
|
|
27
|
+
define_method(cmd) do |*args, &block|
|
|
28
|
+
BrainzLab::Instrumentation::DalliInstrumentation.track_command(cmd, args) do
|
|
29
|
+
send(original_method, *args, &block)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.track_command(command, args)
|
|
38
|
+
started_at = Time.now
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
result = yield
|
|
42
|
+
track_success(command, args, started_at, result)
|
|
43
|
+
result
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
track_error(command, args, started_at, e)
|
|
46
|
+
raise
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.track_success(command, args, started_at, result)
|
|
51
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
52
|
+
key = extract_key(args)
|
|
53
|
+
|
|
54
|
+
# Add breadcrumb
|
|
55
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
56
|
+
"Memcached #{command.upcase}",
|
|
57
|
+
category: "cache",
|
|
58
|
+
level: :info,
|
|
59
|
+
data: { command: command, key: key, duration_ms: duration_ms }
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Track with Flux
|
|
63
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
64
|
+
tags = { command: command }
|
|
65
|
+
BrainzLab::Flux.distribution("memcached.duration_ms", duration_ms, tags: tags)
|
|
66
|
+
BrainzLab::Flux.increment("memcached.commands", tags: tags)
|
|
67
|
+
|
|
68
|
+
# Track cache hits/misses for get commands
|
|
69
|
+
if command == "get"
|
|
70
|
+
if result.nil?
|
|
71
|
+
BrainzLab::Flux.increment("memcached.miss", tags: tags)
|
|
72
|
+
else
|
|
73
|
+
BrainzLab::Flux.increment("memcached.hit", tags: tags)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.track_error(command, args, started_at, error)
|
|
80
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
81
|
+
key = extract_key(args)
|
|
82
|
+
|
|
83
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
84
|
+
"Memcached #{command.upcase} failed: #{error.message}",
|
|
85
|
+
category: "cache",
|
|
86
|
+
level: :error,
|
|
87
|
+
data: { command: command, key: key, error: error.class.name }
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
91
|
+
BrainzLab::Flux.increment("memcached.errors", tags: { command: command })
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.extract_key(args)
|
|
96
|
+
key = args.first
|
|
97
|
+
case key
|
|
98
|
+
when String
|
|
99
|
+
key.length > 50 ? "#{key[0..47]}..." : key
|
|
100
|
+
when Array
|
|
101
|
+
"[#{key.size} keys]"
|
|
102
|
+
else
|
|
103
|
+
key.to_s[0..50]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module ExconInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::Excon)
|
|
9
|
+
|
|
10
|
+
install_middleware!
|
|
11
|
+
|
|
12
|
+
BrainzLab.debug_log("[Instrumentation] Excon instrumentation installed")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def install_middleware!
|
|
18
|
+
# Add our instrumentor to Excon defaults
|
|
19
|
+
::Excon.defaults[:instrumentor] = BrainzLabInstrumentor
|
|
20
|
+
|
|
21
|
+
# Also set up middleware
|
|
22
|
+
if ::Excon.defaults[:middlewares]
|
|
23
|
+
::Excon.defaults[:middlewares] = [Middleware] + ::Excon.defaults[:middlewares]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Excon Instrumentor for ActiveSupport-style notifications
|
|
29
|
+
module BrainzLabInstrumentor
|
|
30
|
+
def self.instrument(name, params = {}, &block)
|
|
31
|
+
started_at = Time.now
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
result = yield if block_given?
|
|
35
|
+
track_request(name, params, started_at, nil)
|
|
36
|
+
result
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
track_request(name, params, started_at, e)
|
|
39
|
+
raise
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.track_request(name, params, started_at, error)
|
|
44
|
+
return if skip_tracking?(params)
|
|
45
|
+
|
|
46
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
47
|
+
host = params[:host] || "unknown"
|
|
48
|
+
method = (params[:method] || "GET").to_s.upcase
|
|
49
|
+
path = params[:path] || "/"
|
|
50
|
+
status = params[:status]
|
|
51
|
+
|
|
52
|
+
# Add breadcrumb
|
|
53
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
54
|
+
"HTTP #{method} #{host}#{path}",
|
|
55
|
+
category: "http",
|
|
56
|
+
level: error ? :error : :info,
|
|
57
|
+
data: {
|
|
58
|
+
method: method,
|
|
59
|
+
host: host,
|
|
60
|
+
path: path,
|
|
61
|
+
status: status,
|
|
62
|
+
duration_ms: duration_ms
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Track with Pulse
|
|
67
|
+
if BrainzLab.configuration.pulse_effectively_enabled?
|
|
68
|
+
BrainzLab::Pulse.span("http.excon", kind: "http") do
|
|
69
|
+
# Already completed, just recording
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Track with Flux
|
|
74
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
75
|
+
tags = { host: host, method: method, status: status.to_s }
|
|
76
|
+
BrainzLab::Flux.distribution("http.excon.duration_ms", duration_ms, tags: tags)
|
|
77
|
+
BrainzLab::Flux.increment("http.excon.requests", tags: tags)
|
|
78
|
+
|
|
79
|
+
if error || (status && status >= 400)
|
|
80
|
+
BrainzLab::Flux.increment("http.excon.errors", tags: tags)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.skip_tracking?(params)
|
|
86
|
+
host = params[:host]
|
|
87
|
+
return true unless host
|
|
88
|
+
|
|
89
|
+
ignore_hosts = BrainzLab.configuration.http_ignore_hosts || []
|
|
90
|
+
ignore_hosts.any? { |h| host.include?(h) }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Excon Middleware
|
|
95
|
+
class Middleware
|
|
96
|
+
def initialize(stack)
|
|
97
|
+
@stack = stack
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def request_call(datum)
|
|
101
|
+
datum[:brainzlab_started_at] = Time.now
|
|
102
|
+
@stack.request_call(datum)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def response_call(datum)
|
|
106
|
+
track_response(datum)
|
|
107
|
+
@stack.response_call(datum)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def error_call(datum)
|
|
111
|
+
track_response(datum, error: true)
|
|
112
|
+
@stack.error_call(datum)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def track_response(datum, error: false)
|
|
118
|
+
started_at = datum[:brainzlab_started_at]
|
|
119
|
+
return unless started_at
|
|
120
|
+
|
|
121
|
+
host = datum[:host]
|
|
122
|
+
return if skip_host?(host)
|
|
123
|
+
|
|
124
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
125
|
+
method = (datum[:method] || "GET").to_s.upcase
|
|
126
|
+
path = datum[:path] || "/"
|
|
127
|
+
status = datum[:response]&.dig(:status)
|
|
128
|
+
|
|
129
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
130
|
+
"HTTP #{method} #{host}#{path} -> #{status || 'error'}",
|
|
131
|
+
category: "http",
|
|
132
|
+
level: error ? :error : :info,
|
|
133
|
+
data: { method: method, host: host, status: status, duration_ms: duration_ms }
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
137
|
+
tags = { host: host, method: method }
|
|
138
|
+
tags[:status] = status.to_s if status
|
|
139
|
+
BrainzLab::Flux.distribution("http.excon.duration_ms", duration_ms, tags: tags)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def skip_host?(host)
|
|
144
|
+
return true unless host
|
|
145
|
+
|
|
146
|
+
ignore_hosts = BrainzLab.configuration.http_ignore_hosts || []
|
|
147
|
+
ignore_hosts.any? { |h| host.include?(h) }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module GoodJobInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::GoodJob)
|
|
9
|
+
|
|
10
|
+
install_notifier!
|
|
11
|
+
install_middleware!
|
|
12
|
+
|
|
13
|
+
BrainzLab.debug_log("[Instrumentation] GoodJob instrumentation installed")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def install_notifier!
|
|
19
|
+
return unless defined?(::ActiveSupport::Notifications)
|
|
20
|
+
|
|
21
|
+
# GoodJob emits ActiveSupport notifications
|
|
22
|
+
::ActiveSupport::Notifications.subscribe("perform_job.good_job") do |*args|
|
|
23
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
|
24
|
+
handle_perform(event)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
::ActiveSupport::Notifications.subscribe("finished_job_task.good_job") do |*args|
|
|
28
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
|
29
|
+
handle_finished(event)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def install_middleware!
|
|
34
|
+
return unless defined?(::GoodJob::Adapter)
|
|
35
|
+
|
|
36
|
+
# Add our callback to GoodJob
|
|
37
|
+
if ::GoodJob.respond_to?(:on_thread_error)
|
|
38
|
+
::GoodJob.on_thread_error = ->(error) do
|
|
39
|
+
BrainzLab::Reflex.capture(error, tags: { source: "good_job" }) if BrainzLab.configuration.reflex_effectively_enabled?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def handle_perform(event)
|
|
45
|
+
payload = event.payload
|
|
46
|
+
job = payload[:job]
|
|
47
|
+
job_class = job&.class&.name || payload[:job_class] || "Unknown"
|
|
48
|
+
queue = job&.queue_name || payload[:queue_name] || "default"
|
|
49
|
+
duration_ms = event.duration.round(2)
|
|
50
|
+
|
|
51
|
+
# Track with Pulse
|
|
52
|
+
if BrainzLab.configuration.pulse_effectively_enabled?
|
|
53
|
+
BrainzLab::Pulse.record_trace(
|
|
54
|
+
"job.#{job_class}",
|
|
55
|
+
kind: "job",
|
|
56
|
+
started_at: event.time,
|
|
57
|
+
ended_at: event.end,
|
|
58
|
+
job_class: job_class,
|
|
59
|
+
job_id: job&.job_id || payload[:job_id],
|
|
60
|
+
queue: queue,
|
|
61
|
+
error: payload[:error].present?,
|
|
62
|
+
error_class: payload[:error]&.class&.name,
|
|
63
|
+
error_message: payload[:error]&.message
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Track with Flux
|
|
68
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
69
|
+
tags = { job_class: job_class, queue: queue }
|
|
70
|
+
BrainzLab::Flux.distribution("good_job.job.duration_ms", duration_ms, tags: tags)
|
|
71
|
+
BrainzLab::Flux.increment("good_job.job.processed", tags: tags)
|
|
72
|
+
|
|
73
|
+
if payload[:error]
|
|
74
|
+
BrainzLab::Flux.increment("good_job.job.failed", tags: tags)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Capture error with Reflex
|
|
79
|
+
if payload[:error] && BrainzLab.configuration.reflex_effectively_enabled?
|
|
80
|
+
BrainzLab::Reflex.capture(payload[:error],
|
|
81
|
+
tags: { job_class: job_class, queue: queue, source: "good_job" },
|
|
82
|
+
extra: { job_id: job&.job_id, duration_ms: duration_ms }
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def handle_finished(event)
|
|
88
|
+
payload = event.payload
|
|
89
|
+
|
|
90
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
91
|
+
result = payload[:result]
|
|
92
|
+
if result == :discarded
|
|
93
|
+
BrainzLab::Flux.increment("good_job.job.discarded")
|
|
94
|
+
elsif result == :retried
|
|
95
|
+
BrainzLab::Flux.increment("good_job.job.retried")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module ResqueInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::Resque)
|
|
9
|
+
|
|
10
|
+
install_hooks!
|
|
11
|
+
install_failure_backend!
|
|
12
|
+
|
|
13
|
+
BrainzLab.debug_log("[Instrumentation] Resque instrumentation installed")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def install_hooks!
|
|
19
|
+
::Resque.before_fork do |job|
|
|
20
|
+
# Clear any stale connections before forking
|
|
21
|
+
BrainzLab::Recall.reset! if defined?(BrainzLab::Recall)
|
|
22
|
+
BrainzLab::Pulse.reset! if defined?(BrainzLab::Pulse)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
::Resque.after_fork do |job|
|
|
26
|
+
# Re-establish connections after forking
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def install_failure_backend!
|
|
31
|
+
# Create a custom failure backend
|
|
32
|
+
failure_backend = Class.new do
|
|
33
|
+
def initialize(exception, worker, queue, payload)
|
|
34
|
+
@exception = exception
|
|
35
|
+
@worker = worker
|
|
36
|
+
@queue = queue
|
|
37
|
+
@payload = payload
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def save
|
|
41
|
+
job_class = @payload["class"] || "Unknown"
|
|
42
|
+
|
|
43
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
44
|
+
BrainzLab::Reflex.capture(@exception,
|
|
45
|
+
tags: { job_class: job_class, queue: @queue, source: "resque" },
|
|
46
|
+
extra: {
|
|
47
|
+
worker: @worker.to_s,
|
|
48
|
+
args: @payload["args"]
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
54
|
+
BrainzLab::Flux.increment("resque.job.failed", tags: { job_class: job_class, queue: @queue })
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Add our failure backend to the chain
|
|
60
|
+
if defined?(::Resque::Failure)
|
|
61
|
+
::Resque::Failure.backend = ::Resque::Failure::Multiple.new(
|
|
62
|
+
::Resque::Failure.backend,
|
|
63
|
+
failure_backend
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Middleware module to include in Resque jobs
|
|
70
|
+
module Middleware
|
|
71
|
+
def self.included(base)
|
|
72
|
+
base.extend(ClassMethods)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module ClassMethods
|
|
76
|
+
def around_perform_brainzlab(*args)
|
|
77
|
+
job_class = self.name
|
|
78
|
+
queue = Resque.queue_from_class(self) || "default"
|
|
79
|
+
started_at = Time.now
|
|
80
|
+
|
|
81
|
+
BrainzLab::Context.current.set_context(
|
|
82
|
+
job_class: job_class,
|
|
83
|
+
queue: queue
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
begin
|
|
87
|
+
yield
|
|
88
|
+
ensure
|
|
89
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
90
|
+
|
|
91
|
+
if BrainzLab.configuration.pulse_effectively_enabled?
|
|
92
|
+
BrainzLab::Pulse.record_trace(
|
|
93
|
+
"job.#{job_class}",
|
|
94
|
+
kind: "job",
|
|
95
|
+
started_at: started_at,
|
|
96
|
+
ended_at: Time.now,
|
|
97
|
+
job_class: job_class,
|
|
98
|
+
queue: queue
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
103
|
+
tags = { job_class: job_class, queue: queue }
|
|
104
|
+
BrainzLab::Flux.distribution("resque.job.duration_ms", duration_ms, tags: tags)
|
|
105
|
+
BrainzLab::Flux.increment("resque.job.processed", tags: tags)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
BrainzLab.clear_context!
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module SolidQueueInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::SolidQueue)
|
|
9
|
+
|
|
10
|
+
install_job_instrumentation!
|
|
11
|
+
install_worker_instrumentation!
|
|
12
|
+
|
|
13
|
+
BrainzLab.debug_log("[Instrumentation] SolidQueue instrumentation installed")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def install_job_instrumentation!
|
|
19
|
+
return unless defined?(::ActiveJob::Base)
|
|
20
|
+
|
|
21
|
+
::ActiveJob::Base.class_eval do
|
|
22
|
+
around_perform do |job, block|
|
|
23
|
+
BrainzLab::Instrumentation::SolidQueueInstrumentation.around_perform(job, &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
around_enqueue do |job, block|
|
|
27
|
+
BrainzLab::Instrumentation::SolidQueueInstrumentation.around_enqueue(job, &block)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def install_worker_instrumentation!
|
|
33
|
+
# Subscribe to ActiveSupport notifications for SolidQueue
|
|
34
|
+
if defined?(::ActiveSupport::Notifications)
|
|
35
|
+
::ActiveSupport::Notifications.subscribe(/solid_queue/) do |name, start, finish, id, payload|
|
|
36
|
+
handle_notification(name, start, finish, payload)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handle_notification(name, start, finish, payload)
|
|
42
|
+
duration_ms = ((finish - start) * 1000).round(2)
|
|
43
|
+
|
|
44
|
+
case name
|
|
45
|
+
when "perform.solid_queue"
|
|
46
|
+
track_job_perform(payload, duration_ms)
|
|
47
|
+
when "enqueue.solid_queue"
|
|
48
|
+
track_job_enqueue(payload)
|
|
49
|
+
when "discard.solid_queue"
|
|
50
|
+
track_job_discard(payload)
|
|
51
|
+
when "retry.solid_queue"
|
|
52
|
+
track_job_retry(payload)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def track_job_perform(payload, duration_ms)
|
|
57
|
+
job_class = payload[:job]&.class&.name || payload[:job_class]
|
|
58
|
+
queue = payload[:queue] || "default"
|
|
59
|
+
|
|
60
|
+
# Track with Pulse
|
|
61
|
+
if BrainzLab.configuration.pulse_effectively_enabled?
|
|
62
|
+
BrainzLab::Pulse.record_trace(
|
|
63
|
+
"job.#{job_class}",
|
|
64
|
+
kind: "job",
|
|
65
|
+
started_at: Time.now - (duration_ms / 1000.0),
|
|
66
|
+
ended_at: Time.now,
|
|
67
|
+
job_class: job_class,
|
|
68
|
+
job_id: payload[:job_id],
|
|
69
|
+
queue: queue,
|
|
70
|
+
executions: payload[:executions] || 1,
|
|
71
|
+
error: payload[:error].present?,
|
|
72
|
+
error_class: payload[:error]&.class&.name,
|
|
73
|
+
error_message: payload[:error]&.message
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Track with Flux
|
|
78
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
79
|
+
tags = { job_class: job_class, queue: queue }
|
|
80
|
+
BrainzLab::Flux.distribution("solid_queue.job.duration_ms", duration_ms, tags: tags)
|
|
81
|
+
BrainzLab::Flux.increment("solid_queue.job.processed", tags: tags)
|
|
82
|
+
|
|
83
|
+
if payload[:error]
|
|
84
|
+
BrainzLab::Flux.increment("solid_queue.job.failed", tags: tags)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Add breadcrumb for Reflex
|
|
89
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
90
|
+
"Job #{job_class} completed in #{duration_ms}ms",
|
|
91
|
+
category: "job",
|
|
92
|
+
level: payload[:error] ? :error : :info,
|
|
93
|
+
data: { queue: queue, job_id: payload[:job_id], duration_ms: duration_ms }
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def track_job_enqueue(payload)
|
|
98
|
+
job_class = payload[:job]&.class&.name || payload[:job_class]
|
|
99
|
+
queue = payload[:queue] || "default"
|
|
100
|
+
|
|
101
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
102
|
+
BrainzLab::Flux.increment("solid_queue.job.enqueued", tags: { job_class: job_class, queue: queue })
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def track_job_discard(payload)
|
|
107
|
+
job_class = payload[:job]&.class&.name || payload[:job_class]
|
|
108
|
+
|
|
109
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
110
|
+
BrainzLab::Flux.increment("solid_queue.job.discarded", tags: { job_class: job_class })
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def track_job_retry(payload)
|
|
115
|
+
job_class = payload[:job]&.class&.name || payload[:job_class]
|
|
116
|
+
|
|
117
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
118
|
+
BrainzLab::Flux.increment("solid_queue.job.retried", tags: { job_class: job_class })
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.around_perform(job)
|
|
124
|
+
job_class = job.class.name
|
|
125
|
+
queue = job.queue_name || "default"
|
|
126
|
+
started_at = Time.now
|
|
127
|
+
|
|
128
|
+
# Set context for the job
|
|
129
|
+
BrainzLab::Context.current.set_context(
|
|
130
|
+
job_class: job_class,
|
|
131
|
+
job_id: job.job_id,
|
|
132
|
+
queue: queue
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
begin
|
|
136
|
+
yield
|
|
137
|
+
rescue StandardError => e
|
|
138
|
+
# Capture error with Reflex
|
|
139
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
140
|
+
BrainzLab::Reflex.capture(e,
|
|
141
|
+
tags: { job_class: job_class, queue: queue },
|
|
142
|
+
extra: {
|
|
143
|
+
job_id: job.job_id,
|
|
144
|
+
arguments: safe_arguments(job),
|
|
145
|
+
executions: job.executions
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
raise
|
|
150
|
+
ensure
|
|
151
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
152
|
+
|
|
153
|
+
# Record trace
|
|
154
|
+
if BrainzLab.configuration.pulse_effectively_enabled?
|
|
155
|
+
BrainzLab::Pulse.record_trace(
|
|
156
|
+
"job.#{job_class}",
|
|
157
|
+
kind: "job",
|
|
158
|
+
started_at: started_at,
|
|
159
|
+
ended_at: Time.now,
|
|
160
|
+
job_class: job_class,
|
|
161
|
+
job_id: job.job_id,
|
|
162
|
+
queue: queue,
|
|
163
|
+
executions: job.executions
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Record metrics
|
|
168
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
169
|
+
tags = { job_class: job_class, queue: queue }
|
|
170
|
+
BrainzLab::Flux.distribution("solid_queue.job.duration_ms", duration_ms, tags: tags)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Clear context
|
|
174
|
+
BrainzLab.clear_context!
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def self.around_enqueue(job)
|
|
179
|
+
yield
|
|
180
|
+
rescue StandardError => e
|
|
181
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
182
|
+
BrainzLab::Reflex.capture(e,
|
|
183
|
+
tags: { job_class: job.class.name, queue: job.queue_name },
|
|
184
|
+
extra: { job_id: job.job_id, arguments: safe_arguments(job) }
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
raise
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def self.safe_arguments(job)
|
|
191
|
+
args = job.arguments
|
|
192
|
+
BrainzLab::Reflex.send(:filter_params, args) if args
|
|
193
|
+
rescue StandardError
|
|
194
|
+
"[Unable to serialize]"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|