brainzlab 0.1.1 → 0.1.3
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/LICENSE +6 -21
- data/README.md +24 -2
- data/lib/brainzlab/beacon/client.rb +207 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +372 -32
- data/lib/brainzlab/context.rb +2 -3
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +139 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/dendrite/client.rb +230 -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 +177 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +57 -0
- data/lib/brainzlab/flux.rb +174 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
- data/lib/brainzlab/instrumentation/active_record.rb +28 -13
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
- data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +3 -4
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +24 -24
- data/lib/brainzlab/instrumentation/graphql.rb +24 -23
- data/lib/brainzlab/instrumentation/httparty.rb +13 -14
- data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
- data/lib/brainzlab/instrumentation/net_http.rb +6 -6
- data/lib/brainzlab/instrumentation/redis.rb +14 -21
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
- data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
- data/lib/brainzlab/instrumentation/stripe.rb +163 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
- data/lib/brainzlab/instrumentation.rb +84 -12
- data/lib/brainzlab/nerve/client.rb +215 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +15 -11
- data/lib/brainzlab/pulse/instrumentation.rb +90 -53
- data/lib/brainzlab/pulse/propagation.rb +29 -29
- data/lib/brainzlab/pulse/provisioner.rb +12 -12
- data/lib/brainzlab/pulse/tracer.rb +4 -4
- data/lib/brainzlab/pulse.rb +14 -14
- data/lib/brainzlab/rails/log_formatter.rb +127 -121
- data/lib/brainzlab/rails/log_subscriber.rb +70 -77
- data/lib/brainzlab/rails/railtie.rb +96 -86
- data/lib/brainzlab/recall/buffer.rb +1 -1
- data/lib/brainzlab/recall/client.rb +14 -10
- data/lib/brainzlab/recall/logger.rb +16 -18
- data/lib/brainzlab/recall/provisioner.rb +29 -12
- data/lib/brainzlab/recall.rb +14 -11
- data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
- data/lib/brainzlab/reflex/client.rb +14 -10
- data/lib/brainzlab/reflex/provisioner.rb +12 -12
- data/lib/brainzlab/reflex.rb +31 -31
- data/lib/brainzlab/sentinel/client.rb +216 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +60 -0
- data/lib/brainzlab/signal/provisioner.rb +55 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +288 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +261 -0
- data/lib/brainzlab/utilities/health_check.rb +294 -0
- data/lib/brainzlab/utilities/log_formatter.rb +254 -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 +196 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -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 +155 -0
- data/lib/brainzlab-sdk.rb +1 -1
- data/lib/brainzlab.rb +112 -13
- data/lib/generators/brainzlab/install/install_generator.rb +29 -27
- metadata +60 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module AWSInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::Aws)
|
|
9
|
+
|
|
10
|
+
install_plugin!
|
|
11
|
+
|
|
12
|
+
BrainzLab.debug_log('[Instrumentation] AWS SDK instrumentation installed')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def install_plugin!
|
|
18
|
+
# AWS SDK v3 uses plugins
|
|
19
|
+
if defined?(::Aws::Plugins)
|
|
20
|
+
::Aws.config[:plugins] ||= []
|
|
21
|
+
::Aws.config[:plugins] << BrainzLabPlugin unless ::Aws.config[:plugins].include?(BrainzLabPlugin)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Also hook into Seahorse for lower-level tracking
|
|
25
|
+
return unless defined?(::Seahorse::Client::Base)
|
|
26
|
+
|
|
27
|
+
install_seahorse_handler!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def install_seahorse_handler!
|
|
31
|
+
handler_class = Class.new(::Seahorse::Client::Handler) do
|
|
32
|
+
def call(context)
|
|
33
|
+
started_at = Time.now
|
|
34
|
+
service = context.client.class.name.split('::')[1] || 'AWS'
|
|
35
|
+
operation = context.operation_name.to_s
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
response = @handler.call(context)
|
|
39
|
+
track_success(service, operation, started_at, context, response)
|
|
40
|
+
response
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
track_error(service, operation, started_at, context, e)
|
|
43
|
+
raise
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def track_success(service, operation, started_at, context, _response)
|
|
50
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
51
|
+
|
|
52
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
53
|
+
"AWS #{service}.#{operation}",
|
|
54
|
+
category: 'aws',
|
|
55
|
+
level: :info,
|
|
56
|
+
data: {
|
|
57
|
+
service: service,
|
|
58
|
+
operation: operation,
|
|
59
|
+
region: context.config.region,
|
|
60
|
+
duration_ms: duration_ms
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
65
|
+
|
|
66
|
+
tags = { service: service, operation: operation, region: context.config.region }
|
|
67
|
+
BrainzLab::Flux.distribution('aws.duration_ms', duration_ms, tags: tags)
|
|
68
|
+
BrainzLab::Flux.increment('aws.requests', tags: tags)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def track_error(service, operation, started_at, _context, error)
|
|
72
|
+
((Time.now - started_at) * 1000).round(2)
|
|
73
|
+
|
|
74
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
75
|
+
"AWS #{service}.#{operation} failed: #{error.message}",
|
|
76
|
+
category: 'aws',
|
|
77
|
+
level: :error,
|
|
78
|
+
data: {
|
|
79
|
+
service: service,
|
|
80
|
+
operation: operation,
|
|
81
|
+
error: error.class.name
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
86
|
+
|
|
87
|
+
tags = { service: service, operation: operation, error_class: error.class.name }
|
|
88
|
+
BrainzLab::Flux.increment('aws.errors', tags: tags)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
::Seahorse::Client::Base.add_plugin(
|
|
93
|
+
Class.new(::Seahorse::Client::Plugin) do
|
|
94
|
+
define_method(:add_handlers) do |handlers, _config|
|
|
95
|
+
handlers.add(handler_class, step: :validate, priority: 0)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Aws SDK Plugin
|
|
103
|
+
class BrainzLabPlugin
|
|
104
|
+
def self.add_handlers(handlers, _config)
|
|
105
|
+
handlers.add(Handler, step: :validate, priority: 0)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Handler
|
|
109
|
+
def initialize(handler)
|
|
110
|
+
@handler = handler
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def call(context)
|
|
114
|
+
started_at = Time.now
|
|
115
|
+
service = extract_service(context)
|
|
116
|
+
operation = context.operation_name.to_s
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
response = @handler.call(context)
|
|
120
|
+
track_request(service, operation, started_at, context, response)
|
|
121
|
+
response
|
|
122
|
+
rescue StandardError => e
|
|
123
|
+
track_error(service, operation, started_at, context, e)
|
|
124
|
+
raise
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def extract_service(context)
|
|
131
|
+
context.client.class.name.to_s.split('::')[1] || 'AWS'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def track_request(service, operation, started_at, context, _response)
|
|
135
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
136
|
+
region = begin
|
|
137
|
+
context.config.region
|
|
138
|
+
rescue StandardError
|
|
139
|
+
'unknown'
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
143
|
+
"AWS #{service}.#{operation}",
|
|
144
|
+
category: 'aws',
|
|
145
|
+
level: :info,
|
|
146
|
+
data: {
|
|
147
|
+
service: service,
|
|
148
|
+
operation: operation,
|
|
149
|
+
region: region,
|
|
150
|
+
duration_ms: duration_ms,
|
|
151
|
+
retries: context.retries
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
156
|
+
|
|
157
|
+
tags = { service: service, operation: operation, region: region }
|
|
158
|
+
BrainzLab::Flux.distribution('aws.duration_ms', duration_ms, tags: tags)
|
|
159
|
+
BrainzLab::Flux.increment('aws.requests', tags: tags)
|
|
160
|
+
|
|
161
|
+
return unless context.retries.positive?
|
|
162
|
+
|
|
163
|
+
BrainzLab::Flux.increment('aws.retries', value: context.retries, tags: tags)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def track_error(service, operation, _started_at, _context, error)
|
|
167
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
168
|
+
"AWS #{service}.#{operation} failed",
|
|
169
|
+
category: 'aws',
|
|
170
|
+
level: :error,
|
|
171
|
+
data: { service: service, operation: operation, error: error.class.name }
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
175
|
+
|
|
176
|
+
tags = { service: service, operation: operation, error_class: error.class.name }
|
|
177
|
+
BrainzLab::Flux.increment('aws.errors', tags: tags)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -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
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
64
|
+
|
|
65
|
+
tags = { command: command }
|
|
66
|
+
BrainzLab::Flux.distribution('memcached.duration_ms', duration_ms, tags: tags)
|
|
67
|
+
BrainzLab::Flux.increment('memcached.commands', tags: tags)
|
|
68
|
+
|
|
69
|
+
# Track cache hits/misses for get commands
|
|
70
|
+
return unless command == 'get'
|
|
71
|
+
|
|
72
|
+
if result.nil?
|
|
73
|
+
BrainzLab::Flux.increment('memcached.miss', tags: tags)
|
|
74
|
+
else
|
|
75
|
+
BrainzLab::Flux.increment('memcached.hit', tags: tags)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.track_error(command, args, started_at, error)
|
|
80
|
+
((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
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
91
|
+
|
|
92
|
+
BrainzLab::Flux.increment('memcached.errors', tags: { command: command })
|
|
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
|
|
@@ -11,17 +11,13 @@ module BrainzLab
|
|
|
11
11
|
return if @installed
|
|
12
12
|
|
|
13
13
|
# Install lifecycle hooks
|
|
14
|
-
if defined?(::Delayed::Worker)
|
|
15
|
-
install_lifecycle_hooks!
|
|
16
|
-
end
|
|
14
|
+
install_lifecycle_hooks! if defined?(::Delayed::Worker)
|
|
17
15
|
|
|
18
16
|
# Install plugin if Delayed::Plugin is available
|
|
19
|
-
if defined?(::Delayed::Plugin)
|
|
20
|
-
::Delayed::Worker.plugins << Plugin
|
|
21
|
-
end
|
|
17
|
+
::Delayed::Worker.plugins << Plugin if defined?(::Delayed::Plugin)
|
|
22
18
|
|
|
23
19
|
@installed = true
|
|
24
|
-
BrainzLab.debug_log(
|
|
20
|
+
BrainzLab.debug_log('Delayed::Job instrumentation installed')
|
|
25
21
|
end
|
|
26
22
|
|
|
27
23
|
def installed?
|
|
@@ -35,15 +31,15 @@ module BrainzLab
|
|
|
35
31
|
private
|
|
36
32
|
|
|
37
33
|
def install_lifecycle_hooks!
|
|
38
|
-
::Delayed::Worker.lifecycle.around(:invoke_job) do |job, *
|
|
34
|
+
::Delayed::Worker.lifecycle.around(:invoke_job) do |job, *_args, &block|
|
|
39
35
|
around_invoke(job, &block)
|
|
40
36
|
end
|
|
41
37
|
|
|
42
|
-
::Delayed::Worker.lifecycle.after(:error) do |
|
|
38
|
+
::Delayed::Worker.lifecycle.after(:error) do |_worker, job|
|
|
43
39
|
record_error(job)
|
|
44
40
|
end
|
|
45
41
|
|
|
46
|
-
::Delayed::Worker.lifecycle.after(:failure) do |
|
|
42
|
+
::Delayed::Worker.lifecycle.after(:failure) do |_worker, job|
|
|
47
43
|
record_failure(job)
|
|
48
44
|
end
|
|
49
45
|
rescue StandardError => e
|
|
@@ -53,7 +49,7 @@ module BrainzLab
|
|
|
53
49
|
def around_invoke(job, &block)
|
|
54
50
|
started_at = Time.now.utc
|
|
55
51
|
job_name = extract_job_name(job)
|
|
56
|
-
queue = job.queue ||
|
|
52
|
+
queue = job.queue || 'default'
|
|
57
53
|
|
|
58
54
|
# Calculate queue wait time
|
|
59
55
|
queue_wait_ms = job.created_at ? ((started_at - job.created_at) * 1000).round(2) : nil
|
|
@@ -64,7 +60,7 @@ module BrainzLab
|
|
|
64
60
|
# Add breadcrumb
|
|
65
61
|
BrainzLab::Reflex.add_breadcrumb(
|
|
66
62
|
"DelayedJob #{job_name}",
|
|
67
|
-
category:
|
|
63
|
+
category: 'job.delayed_job',
|
|
68
64
|
level: :info,
|
|
69
65
|
data: { job_id: job.id, queue: queue, attempts: job.attempts }
|
|
70
66
|
)
|
|
@@ -133,7 +129,7 @@ module BrainzLab
|
|
|
133
129
|
payload = {
|
|
134
130
|
trace_id: SecureRandom.uuid,
|
|
135
131
|
name: job_name,
|
|
136
|
-
kind:
|
|
132
|
+
kind: 'job',
|
|
137
133
|
started_at: started_at.utc.iso8601(3),
|
|
138
134
|
ended_at: ended_at.utc.iso8601(3),
|
|
139
135
|
duration_ms: duration_ms,
|
|
@@ -162,7 +158,7 @@ module BrainzLab
|
|
|
162
158
|
|
|
163
159
|
BrainzLab::Reflex.add_breadcrumb(
|
|
164
160
|
"DelayedJob error: #{extract_job_name(job)}",
|
|
165
|
-
category:
|
|
161
|
+
category: 'job.delayed_job.error',
|
|
166
162
|
level: :error,
|
|
167
163
|
data: {
|
|
168
164
|
job_id: job.id,
|
|
@@ -177,7 +173,7 @@ module BrainzLab
|
|
|
177
173
|
def record_failure(job)
|
|
178
174
|
BrainzLab::Reflex.add_breadcrumb(
|
|
179
175
|
"DelayedJob failed permanently: #{extract_job_name(job)}",
|
|
180
|
-
category:
|
|
176
|
+
category: 'job.delayed_job.failure',
|
|
181
177
|
level: :error,
|
|
182
178
|
data: {
|
|
183
179
|
job_id: job.id,
|
|
@@ -200,7 +196,7 @@ module BrainzLab
|
|
|
200
196
|
payload.class.name
|
|
201
197
|
end
|
|
202
198
|
rescue StandardError
|
|
203
|
-
job.name ||
|
|
199
|
+
job.name || 'Unknown'
|
|
204
200
|
end
|
|
205
201
|
|
|
206
202
|
def format_timestamp(ts)
|
|
@@ -216,21 +212,23 @@ module BrainzLab
|
|
|
216
212
|
end
|
|
217
213
|
|
|
218
214
|
# Delayed::Job Plugin (alternative installation method)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
if defined?(::Delayed::Plugin)
|
|
216
|
+
class Plugin < ::Delayed::Plugin
|
|
217
|
+
callbacks do |lifecycle|
|
|
218
|
+
lifecycle.around(:invoke_job) do |job, *_args, &block|
|
|
219
|
+
DelayedJobInstrumentation.send(:around_invoke, job, &block)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
lifecycle.after(:error) do |_worker, job|
|
|
223
|
+
DelayedJobInstrumentation.send(:record_error, job)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
lifecycle.after(:failure) do |_worker, job|
|
|
227
|
+
DelayedJobInstrumentation.send(:record_failure, job)
|
|
228
|
+
end
|
|
231
229
|
end
|
|
232
230
|
end
|
|
233
|
-
end
|
|
231
|
+
end
|
|
234
232
|
end
|
|
235
233
|
end
|
|
236
234
|
end
|
|
@@ -32,7 +32,7 @@ module BrainzLab
|
|
|
32
32
|
return unless installed_any
|
|
33
33
|
|
|
34
34
|
@installed = true
|
|
35
|
-
BrainzLab.debug_log(
|
|
35
|
+
BrainzLab.debug_log('Elasticsearch instrumentation installed')
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def installed?
|
|
@@ -64,14 +64,12 @@ module BrainzLab
|
|
|
64
64
|
return super unless should_track?
|
|
65
65
|
|
|
66
66
|
started_at = Time.now.utc
|
|
67
|
-
error_info = nil
|
|
68
67
|
|
|
69
68
|
begin
|
|
70
69
|
response = super
|
|
71
70
|
record_request(method, path, params, started_at, response.status)
|
|
72
71
|
response
|
|
73
72
|
rescue StandardError => e
|
|
74
|
-
error_info = e
|
|
75
73
|
record_request(method, path, params, started_at, nil, e)
|
|
76
74
|
raise
|
|
77
75
|
end
|
|
@@ -83,7 +81,7 @@ module BrainzLab
|
|
|
83
81
|
BrainzLab.configuration.instrument_elasticsearch
|
|
84
82
|
end
|
|
85
83
|
|
|
86
|
-
def record_request(method, path,
|
|
84
|
+
def record_request(method, path, _params, started_at, status, error = nil)
|
|
87
85
|
duration_ms = ((Time.now.utc - started_at) * 1000).round(2)
|
|
88
86
|
operation = extract_operation(method, path)
|
|
89
87
|
index = extract_index(path)
|
|
@@ -93,7 +91,7 @@ module BrainzLab
|
|
|
93
91
|
if BrainzLab.configuration.reflex_enabled
|
|
94
92
|
BrainzLab::Reflex.add_breadcrumb(
|
|
95
93
|
"ES #{operation}",
|
|
96
|
-
category:
|
|
94
|
+
category: 'elasticsearch',
|
|
97
95
|
level: level,
|
|
98
96
|
data: {
|
|
99
97
|
method: method.to_s.upcase,
|
|
@@ -143,7 +141,7 @@ module BrainzLab
|
|
|
143
141
|
span = {
|
|
144
142
|
span_id: SecureRandom.uuid,
|
|
145
143
|
name: "ES #{operation}",
|
|
146
|
-
kind:
|
|
144
|
+
kind: 'elasticsearch',
|
|
147
145
|
started_at: started_at,
|
|
148
146
|
ended_at: Time.now.utc,
|
|
149
147
|
duration_ms: duration_ms,
|
|
@@ -168,25 +166,25 @@ module BrainzLab
|
|
|
168
166
|
method_str = method.to_s.upcase
|
|
169
167
|
|
|
170
168
|
case path
|
|
171
|
-
when %r{/_search} then
|
|
172
|
-
when %r{/_bulk} then
|
|
173
|
-
when %r{/_count} then
|
|
174
|
-
when %r{/_mget} then
|
|
175
|
-
when %r{/_msearch} then
|
|
176
|
-
when %r{/_update_by_query} then
|
|
177
|
-
when %r{/_delete_by_query} then
|
|
178
|
-
when %r{/_refresh} then
|
|
179
|
-
when %r{/_mapping} then
|
|
180
|
-
when %r{/_settings} then
|
|
181
|
-
when %r{/_alias} then
|
|
182
|
-
when %r{/_analyze} then
|
|
169
|
+
when %r{/_search} then 'search'
|
|
170
|
+
when %r{/_bulk} then 'bulk'
|
|
171
|
+
when %r{/_count} then 'count'
|
|
172
|
+
when %r{/_mget} then 'mget'
|
|
173
|
+
when %r{/_msearch} then 'msearch'
|
|
174
|
+
when %r{/_update_by_query} then 'update_by_query'
|
|
175
|
+
when %r{/_delete_by_query} then 'delete_by_query'
|
|
176
|
+
when %r{/_refresh} then 'refresh'
|
|
177
|
+
when %r{/_mapping} then 'mapping'
|
|
178
|
+
when %r{/_settings} then 'settings'
|
|
179
|
+
when %r{/_alias} then 'alias'
|
|
180
|
+
when %r{/_analyze} then 'analyze'
|
|
183
181
|
else
|
|
184
182
|
case method_str
|
|
185
|
-
when
|
|
186
|
-
when
|
|
187
|
-
when
|
|
188
|
-
when
|
|
189
|
-
when
|
|
183
|
+
when 'GET' then 'get'
|
|
184
|
+
when 'POST' then 'index'
|
|
185
|
+
when 'PUT' then 'update'
|
|
186
|
+
when 'DELETE' then 'delete'
|
|
187
|
+
when 'HEAD' then 'exists'
|
|
190
188
|
else method_str.downcase
|
|
191
189
|
end
|
|
192
190
|
end
|
|
@@ -195,13 +193,14 @@ module BrainzLab
|
|
|
195
193
|
def extract_index(path)
|
|
196
194
|
# Extract index name from path like /my-index/_search
|
|
197
195
|
match = path.match(%r{^/([^/_]+)})
|
|
198
|
-
match[1] if match && !match[1].start_with?(
|
|
196
|
+
match[1] if match && !match[1].start_with?('_')
|
|
199
197
|
rescue StandardError
|
|
200
198
|
nil
|
|
201
199
|
end
|
|
202
200
|
|
|
203
201
|
def truncate_path(path)
|
|
204
202
|
return nil unless path
|
|
203
|
+
|
|
205
204
|
path.to_s[0, 200]
|
|
206
205
|
end
|
|
207
206
|
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
|
+
return unless ::Excon.defaults[:middlewares]
|
|
23
|
+
|
|
24
|
+
::Excon.defaults[:middlewares] = [Middleware] + ::Excon.defaults[:middlewares]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Excon Instrumentor for ActiveSupport-style notifications
|
|
29
|
+
module BrainzLabInstrumentor
|
|
30
|
+
def self.instrument(name, params = {})
|
|
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
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
75
|
+
|
|
76
|
+
tags = { host: host, method: method, status: status.to_s }
|
|
77
|
+
BrainzLab::Flux.distribution('http.excon.duration_ms', duration_ms, tags: tags)
|
|
78
|
+
BrainzLab::Flux.increment('http.excon.requests', tags: tags)
|
|
79
|
+
|
|
80
|
+
return unless error || (status && status >= 400)
|
|
81
|
+
|
|
82
|
+
BrainzLab::Flux.increment('http.excon.errors', tags: tags)
|
|
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
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
137
|
+
|
|
138
|
+
tags = { host: host, method: method }
|
|
139
|
+
tags[:status] = status.to_s if status
|
|
140
|
+
BrainzLab::Flux.distribution('http.excon.duration_ms', duration_ms, tags: tags)
|
|
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
|