koderift-rails 1.3.4 → 2.1.0
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 +67 -0
- data/README.md +183 -3
- data/lib/koderift/rails/breadcrumb.rb +70 -0
- data/lib/koderift/rails/capture.rb +514 -0
- data/lib/koderift/rails/configuration.rb +38 -0
- data/lib/koderift/rails/controller.rb +21 -0
- data/lib/koderift/rails/event.rb +27 -0
- data/lib/koderift/rails/instrumentation.rb +64 -19
- data/lib/koderift/rails/lograge_config.rb +7 -2
- data/lib/koderift/rails/railtie.rb +14 -0
- data/lib/koderift/rails/rake_integration.rb +43 -0
- data/lib/koderift/rails/rum_helper.rb +60 -4
- data/lib/koderift/rails/scope.rb +163 -0
- data/lib/koderift/rails/sidekiq_middleware.rb +24 -1
- data/lib/koderift/rails/tracing.rb +195 -0
- data/lib/koderift/rails/version.rb +1 -1
- data/lib/koderift/rails.rb +187 -0
- metadata +7 -1
|
@@ -10,11 +10,28 @@ module Koderift
|
|
|
10
10
|
# partial render times, SQL query times, and breadcrumbs.
|
|
11
11
|
# Returns a hash of captured data to be merged into the log payload.
|
|
12
12
|
def self.capture
|
|
13
|
+
state = start_capture
|
|
14
|
+
begin
|
|
15
|
+
yield
|
|
16
|
+
ensure
|
|
17
|
+
stop_capture(state)
|
|
18
|
+
end
|
|
19
|
+
build_result(state)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# capture's three phases, exposed separately so a unit of work that is
|
|
23
|
+
# NOT a block runs through the same subscriptions rather than a parallel
|
|
24
|
+
# implementation. Tracing::Transaction is that caller: it has to be
|
|
25
|
+
# started and finished by two separate calls to mirror Sentry's
|
|
26
|
+
# start_transaction / finish, which a block-shaped API cannot express.
|
|
27
|
+
def self.start_capture
|
|
28
|
+
config = Koderift::Rails.configuration
|
|
13
29
|
partials = []
|
|
14
30
|
queries = []
|
|
15
31
|
search_queries = []
|
|
16
32
|
breadcrumbs = []
|
|
17
|
-
|
|
33
|
+
custom_spans = []
|
|
34
|
+
external_calls = []
|
|
18
35
|
|
|
19
36
|
partial_sub = ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
|
|
20
37
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
@@ -73,36 +90,54 @@ module Koderift
|
|
|
73
90
|
search_queries << { index: 'multi_search', duration_ms: duration }
|
|
74
91
|
end
|
|
75
92
|
|
|
76
|
-
external_calls
|
|
93
|
+
# Published for the same reason external_calls is: code that runs
|
|
94
|
+
# somewhere else entirely — the Net::HTTP patch, Koderift.add_breadcrumb,
|
|
95
|
+
# Koderift.with_child_span — has no other way to find the collection
|
|
96
|
+
# belonging to the unit of work currently in flight on this thread.
|
|
97
|
+
Thread.current[:koderift_breadcrumbs] = breadcrumbs
|
|
98
|
+
Thread.current[:koderift_custom_spans] = custom_spans
|
|
77
99
|
Thread.current[:koderift_external_calls] = external_calls
|
|
78
100
|
Thread.current[:koderift_external_config] = config
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
{
|
|
103
|
+
config: config,
|
|
104
|
+
partials: partials,
|
|
105
|
+
queries: queries,
|
|
106
|
+
search_queries: search_queries,
|
|
107
|
+
breadcrumbs: breadcrumbs,
|
|
108
|
+
custom_spans: custom_spans,
|
|
109
|
+
external_calls: external_calls,
|
|
110
|
+
subscriptions: [partial_sub, query_sub, search_sub, multi_search_sub]
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.stop_capture(state)
|
|
115
|
+
state[:subscriptions].each do |sub|
|
|
116
|
+
ActiveSupport::Notifications.unsubscribe(sub)
|
|
89
117
|
end
|
|
118
|
+
Thread.current[:koderift_breadcrumbs] = nil
|
|
119
|
+
Thread.current[:koderift_custom_spans] = nil
|
|
120
|
+
Thread.current[:koderift_external_calls] = nil
|
|
121
|
+
Thread.current[:koderift_external_config] = nil
|
|
122
|
+
state
|
|
123
|
+
end
|
|
90
124
|
|
|
125
|
+
def self.build_result(state)
|
|
91
126
|
spans = []
|
|
92
127
|
|
|
93
|
-
partials.each_with_index do |p, i|
|
|
128
|
+
state[:partials].each_with_index do |p, i|
|
|
94
129
|
spans << { type: 'partial', name: p[:partial], duration_ms: p[:duration_ms], sequence: i }
|
|
95
130
|
end
|
|
96
131
|
|
|
97
|
-
queries.each_with_index do |q, i|
|
|
132
|
+
state[:queries].each_with_index do |q, i|
|
|
98
133
|
spans << { type: 'sql', name: q[:sql], duration_ms: q[:duration_ms], sequence: i }
|
|
99
134
|
end
|
|
100
135
|
|
|
101
|
-
search_queries.each_with_index do |s, i|
|
|
136
|
+
state[:search_queries].each_with_index do |s, i|
|
|
102
137
|
spans << { type: 'search', name: s[:index], duration_ms: s[:duration_ms], sequence: i }
|
|
103
138
|
end
|
|
104
139
|
|
|
105
|
-
external_calls.each_with_index do |c, i|
|
|
140
|
+
state[:external_calls].each_with_index do |c, i|
|
|
106
141
|
spans << {
|
|
107
142
|
type: 'http',
|
|
108
143
|
name: "#{c[:host]}#{c[:endpoint]}",
|
|
@@ -112,12 +147,22 @@ module Koderift
|
|
|
112
147
|
}
|
|
113
148
|
end
|
|
114
149
|
|
|
150
|
+
state[:custom_spans].each_with_index do |c, i|
|
|
151
|
+
spans << {
|
|
152
|
+
type: 'custom',
|
|
153
|
+
name: c[:name],
|
|
154
|
+
duration_ms: c[:duration_ms],
|
|
155
|
+
detail: c[:detail],
|
|
156
|
+
sequence: i
|
|
157
|
+
}.compact
|
|
158
|
+
end
|
|
159
|
+
|
|
115
160
|
{
|
|
116
161
|
spans: spans,
|
|
117
|
-
query_count: queries.size,
|
|
118
|
-
partial_count: partials.size,
|
|
119
|
-
search_count: search_queries.size,
|
|
120
|
-
breadcrumbs: breadcrumbs.last(config.max_breadcrumbs)
|
|
162
|
+
query_count: state[:queries].size,
|
|
163
|
+
partial_count: state[:partials].size,
|
|
164
|
+
search_count: state[:search_queries].size,
|
|
165
|
+
breadcrumbs: state[:breadcrumbs].last(state[:config].max_breadcrumbs)
|
|
121
166
|
}
|
|
122
167
|
end
|
|
123
168
|
|
|
@@ -46,9 +46,14 @@ module Koderift
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def self.format_backtrace(exception)
|
|
49
|
-
|
|
49
|
+
format_frames(exception&.backtrace)
|
|
50
|
+
end
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
# Split out from format_backtrace so a caller holding a raw backtrace
|
|
53
|
+
# array -- Capture, when given an explicit `backtrace:` -- gets the same
|
|
54
|
+
# frame selection instead of a second, subtly different implementation.
|
|
55
|
+
def self.format_frames(backtrace)
|
|
56
|
+
return nil unless backtrace.is_a?(Array) && backtrace.any?
|
|
52
57
|
|
|
53
58
|
leading = backtrace.first(LEADING_FRAMES)
|
|
54
59
|
|
|
@@ -34,6 +34,20 @@ module Koderift
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Capture uncaught rake task failures. Rake is loaded before the Rails
|
|
38
|
+
# environment under `bundle exec rake` / `bin/rails <task>`, so the
|
|
39
|
+
# constant is present by the time initializers run; under a web server it
|
|
40
|
+
# is not, and the guard makes this a no-cost skip.
|
|
41
|
+
initializer 'koderift.rake_integration', after: :load_config_initializers do
|
|
42
|
+
next unless Koderift::Rails.configuration.enabled
|
|
43
|
+
next unless defined?(::Rake::Application)
|
|
44
|
+
|
|
45
|
+
require 'koderift/rails/rake_integration'
|
|
46
|
+
unless ::Rake::Application.include?(Koderift::Rails::RakeIntegration)
|
|
47
|
+
::Rake::Application.prepend(Koderift::Rails::RakeIntegration)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
37
51
|
# Register Sidekiq server middleware lazily when Sidekiq is present.
|
|
38
52
|
initializer 'koderift.sidekiq_middleware', after: :load_config_initializers do
|
|
39
53
|
next unless Koderift::Rails.configuration.enabled
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Koderift
|
|
4
|
+
module Rails
|
|
5
|
+
# Captures uncaught rake task failures.
|
|
6
|
+
#
|
|
7
|
+
# This closes the largest blind spot in the product for scheduled work. A
|
|
8
|
+
# rake task that raises writes its backtrace to STDERR, which under cron
|
|
9
|
+
# goes to a mail spool or /dev/null — never to production.log — so the log
|
|
10
|
+
# agent has never seen it and Koderift has never reported it. The tasks
|
|
11
|
+
# most likely to fail unnoticed are exactly the ones nobody has touched in
|
|
12
|
+
# a year.
|
|
13
|
+
#
|
|
14
|
+
# Rake::Application#display_error_message is the hook Sentry uses for the
|
|
15
|
+
# same purpose. We capture and then call super, so Rake's own error output
|
|
16
|
+
# is completely unchanged — anything already scraping that output, or a
|
|
17
|
+
# human reading the cron mail, sees exactly what it saw before.
|
|
18
|
+
module RakeIntegration
|
|
19
|
+
def display_error_message(exception)
|
|
20
|
+
Koderift::Rails::RakeIntegration.report(exception)
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A clean `exit` inside a task reaches this hook as a SystemExit. It is
|
|
25
|
+
# the task finishing, not failing, and reporting it would file an error
|
|
26
|
+
# for every well-behaved task that exits explicitly.
|
|
27
|
+
def self.report(exception)
|
|
28
|
+
return if exception.is_a?(SystemExit)
|
|
29
|
+
|
|
30
|
+
Koderift.capture_exception(exception, component: task_name, action: 'rake')
|
|
31
|
+
rescue StandardError
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.task_name
|
|
36
|
+
name = ::Rake.application.top_level_tasks.first.to_s
|
|
37
|
+
name.empty? ? 'rake' : name
|
|
38
|
+
rescue StandardError
|
|
39
|
+
'rake'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
module Koderift
|
|
4
4
|
module Rails
|
|
5
5
|
module RumHelper
|
|
6
|
+
MISSING_RUM_TOKEN_WARNING =
|
|
7
|
+
'[Koderift] Browser RUM is off: koderift_rum_meta_tag renders nothing until ' \
|
|
8
|
+
'config.rum_token is set. Since koderift-rails 2.1 the tag no longer publishes ' \
|
|
9
|
+
'config.project_token, which is a secret. Copy the public RUM token (it starts ' \
|
|
10
|
+
"with rum_) from your project's Log Analytics setup page in Koderift, or set " \
|
|
11
|
+
'config.rum_enabled = false.'
|
|
12
|
+
|
|
13
|
+
SECRET_RUM_TOKEN_WARNING =
|
|
14
|
+
'[Koderift] Browser RUM is off: config.rum_token is set to your project token, ' \
|
|
15
|
+
'which is a secret and must not appear in page HTML. Use the public RUM token ' \
|
|
16
|
+
"(it starts with rum_) from your project's Log Analytics setup page in Koderift."
|
|
17
|
+
|
|
6
18
|
# Renders the meta tag that rum.js reads for initialisation.
|
|
7
19
|
# Place once inside <head> in your application layout.
|
|
8
20
|
#
|
|
@@ -11,24 +23,68 @@ module Koderift
|
|
|
11
23
|
#
|
|
12
24
|
# Renders:
|
|
13
25
|
# <meta name="koderift-rum-token"
|
|
14
|
-
# content="
|
|
26
|
+
# content="rum_abc123"
|
|
15
27
|
# data-rum-sampling-rate="0.1"
|
|
16
28
|
# data-environment="production">
|
|
17
29
|
#
|
|
18
|
-
#
|
|
30
|
+
# Publishes config.rum_token only, never config.project_token. Returns an
|
|
31
|
+
# empty string when RUM is disabled or there is no publishable token.
|
|
19
32
|
def koderift_rum_meta_tag
|
|
20
33
|
config = Koderift::Rails.configuration
|
|
21
|
-
return ''.html_safe unless config.rum_enabled
|
|
34
|
+
return ''.html_safe unless config.rum_enabled
|
|
35
|
+
|
|
36
|
+
token = RumHelper.publishable_rum_token(config)
|
|
37
|
+
return ''.html_safe unless token
|
|
22
38
|
|
|
23
39
|
tag.meta(
|
|
24
40
|
name: 'koderift-rum-token',
|
|
25
|
-
content:
|
|
41
|
+
content: token,
|
|
26
42
|
data: {
|
|
27
43
|
rum_sampling_rate: config.rum_sampling_rate.to_f,
|
|
28
44
|
environment: (defined?(::Rails) && ::Rails.respond_to?(:env) ? ::Rails.env.to_s : 'production')
|
|
29
45
|
}
|
|
30
46
|
)
|
|
31
47
|
end
|
|
48
|
+
|
|
49
|
+
class << self
|
|
50
|
+
# The token the meta tag may publish, or nil. Up to 2.0 the tag published
|
|
51
|
+
# project_token, the log agent's ingest secret. It never does now, and a
|
|
52
|
+
# rum_token equal to it is refused for the same reason. There is
|
|
53
|
+
# deliberately no fallback: an app that set up RUM before 2.1 and has
|
|
54
|
+
# not set rum_token gets one warning per process instead.
|
|
55
|
+
def publishable_rum_token(config)
|
|
56
|
+
token = config.rum_token.to_s
|
|
57
|
+
secret = config.project_token.to_s
|
|
58
|
+
|
|
59
|
+
if token.empty?
|
|
60
|
+
warn_once(MISSING_RUM_TOKEN_WARNING) unless secret.empty?
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if token == secret
|
|
65
|
+
warn_once(SECRET_RUM_TOKEN_WARNING)
|
|
66
|
+
return nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
token
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def reset_warnings!
|
|
73
|
+
@warned = false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def warn_once(message)
|
|
79
|
+
return if @warned
|
|
80
|
+
|
|
81
|
+
@warned = true
|
|
82
|
+
logger = defined?(::Rails) && ::Rails.respond_to?(:logger) ? ::Rails.logger : nil
|
|
83
|
+
logger&.warn(message)
|
|
84
|
+
rescue StandardError
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
32
88
|
end
|
|
33
89
|
end
|
|
34
90
|
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'koderift/rails/breadcrumb'
|
|
4
|
+
|
|
5
|
+
module Koderift
|
|
6
|
+
module Rails
|
|
7
|
+
# Sentry-compatible scope: ambient user/tags/extras/contexts merged into
|
|
8
|
+
# every capture made while it is current.
|
|
9
|
+
#
|
|
10
|
+
# The stack is thread-local, and it is RESET at the end of every request and
|
|
11
|
+
# Sidekiq job (see Controller and SidekiqMiddleware). That reset is not
|
|
12
|
+
# housekeeping: Puma and Sidekiq both reuse threads, so without it a tag set
|
|
13
|
+
# for one user's request would silently attach to the next request served by
|
|
14
|
+
# that thread.
|
|
15
|
+
class Scope
|
|
16
|
+
STACK_KEY = :koderift_scope_stack
|
|
17
|
+
|
|
18
|
+
attr_reader :user, :tags, :extras, :contexts
|
|
19
|
+
attr_accessor :level, :fingerprint, :component, :action
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@user = {}
|
|
23
|
+
@tags = {}
|
|
24
|
+
@extras = {}
|
|
25
|
+
@contexts = {}
|
|
26
|
+
@level = nil
|
|
27
|
+
@fingerprint = nil
|
|
28
|
+
@component = nil
|
|
29
|
+
@action = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def stack
|
|
34
|
+
Thread.current[STACK_KEY] ||= [new]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def current
|
|
38
|
+
stack.last
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def push
|
|
42
|
+
stack.push(current.dup)
|
|
43
|
+
current
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def pop
|
|
47
|
+
stack.pop if stack.size > 1
|
|
48
|
+
current
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reset!
|
|
52
|
+
Thread.current[STACK_KEY] = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Runs `block` against a temporary copy of the current scope before
|
|
56
|
+
# yielding, so `capture_exception(e) { |scope| scope.set_tags(...) }`
|
|
57
|
+
# cannot leak those tags into later captures on the same thread.
|
|
58
|
+
def with_temporary(block)
|
|
59
|
+
return yield unless block
|
|
60
|
+
|
|
61
|
+
push
|
|
62
|
+
begin
|
|
63
|
+
block.call(current)
|
|
64
|
+
yield
|
|
65
|
+
ensure
|
|
66
|
+
pop
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_user(hash)
|
|
72
|
+
@user = stringify(hash)
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def set_tags(hash)
|
|
77
|
+
@tags.merge!(stringify(hash))
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def set_tag(key, value)
|
|
82
|
+
@tags[key.to_s] = value
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def set_extras(hash)
|
|
87
|
+
@extras.merge!(stringify(hash))
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def set_extra(key, value)
|
|
92
|
+
@extras[key.to_s] = value
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def set_context(key, value)
|
|
97
|
+
@contexts[key.to_s] = value
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def set_contexts(hash)
|
|
102
|
+
@contexts.merge!(stringify(hash))
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def set_level(level)
|
|
107
|
+
@level = level
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def set_fingerprint(fingerprint)
|
|
112
|
+
@fingerprint = fingerprint
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Sentry's transaction name is "Controller#action". Koderift stores those
|
|
117
|
+
# as two separate columns and groups errors on them, so split rather than
|
|
118
|
+
# keep the joined string.
|
|
119
|
+
def set_transaction_name(name)
|
|
120
|
+
component, _, action = name.to_s.partition('#')
|
|
121
|
+
@component = component.empty? ? nil : component
|
|
122
|
+
@action = action.empty? ? nil : action
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def add_breadcrumb(crumb)
|
|
127
|
+
Breadcrumbs.add(crumb)
|
|
128
|
+
self
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def clear_breadcrumbs
|
|
132
|
+
Breadcrumbs.clear
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def clear
|
|
137
|
+
initialize
|
|
138
|
+
self
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def dup
|
|
142
|
+
copy = self.class.new
|
|
143
|
+
copy.set_user(@user)
|
|
144
|
+
copy.set_tags(@tags)
|
|
145
|
+
copy.set_extras(@extras)
|
|
146
|
+
copy.set_contexts(@contexts)
|
|
147
|
+
copy.level = @level
|
|
148
|
+
copy.fingerprint = @fingerprint
|
|
149
|
+
copy.component = @component
|
|
150
|
+
copy.action = @action
|
|
151
|
+
copy
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def stringify(hash)
|
|
157
|
+
return {} unless hash.respond_to?(:each_pair)
|
|
158
|
+
|
|
159
|
+
hash.each_with_object({}) { |(k, v), out| out[k.to_s] = v }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'securerandom'
|
|
5
5
|
require 'koderift/rails/instrumentation'
|
|
6
|
+
require 'koderift/rails/scope'
|
|
6
7
|
|
|
7
8
|
module Koderift
|
|
8
9
|
module Rails
|
|
@@ -19,6 +20,16 @@ module Koderift
|
|
|
19
20
|
trace_id = SecureRandom.uuid if trace_id.empty?
|
|
20
21
|
Thread.current[:koderift_trace_id] = trace_id
|
|
21
22
|
|
|
23
|
+
# Ambient context for manual captures made anywhere inside the job.
|
|
24
|
+
# The jid stands in for a request id: it is what correlates a capture
|
|
25
|
+
# with the job's own log line, and what carries this job's spans into
|
|
26
|
+
# log_spans (IngestLogBatchJob drops spans on a row with no request id).
|
|
27
|
+
Thread.current[:koderift_context] = {
|
|
28
|
+
component: job_name(job),
|
|
29
|
+
action: 'perform',
|
|
30
|
+
request_id: job['jid'].to_s
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
23
34
|
|
|
24
35
|
begin
|
|
@@ -34,11 +45,23 @@ module Koderift
|
|
|
34
45
|
raise
|
|
35
46
|
ensure
|
|
36
47
|
Thread.current[:koderift_trace_id] = nil
|
|
48
|
+
Thread.current[:koderift_context] = nil
|
|
49
|
+
# Sidekiq reuses threads across jobs; see the same reset in Controller.
|
|
50
|
+
Koderift::Rails::Scope.reset!
|
|
37
51
|
end
|
|
38
52
|
end
|
|
39
53
|
|
|
40
54
|
private
|
|
41
55
|
|
|
56
|
+
# The name Sidekiq's own JobLogger logs the job under. For ActiveJob,
|
|
57
|
+
# job['class'] is the adapter's wrapper (Sidekiq::ActiveJob::Wrapper,
|
|
58
|
+
# ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper) for every job, and
|
|
59
|
+
# job['wrapped'] is the job itself. Matching Sidekiq also lets the agent
|
|
60
|
+
# pair this line with Sidekiq's "done" line for the same attempt.
|
|
61
|
+
def job_name(job)
|
|
62
|
+
(job['wrapped'] || job['class']).to_s
|
|
63
|
+
end
|
|
64
|
+
|
|
42
65
|
def emit_job_log(_worker, job, queue, result, duration_ms, trace_id, error:)
|
|
43
66
|
config = Koderift::Rails.configuration
|
|
44
67
|
|
|
@@ -51,7 +74,7 @@ module Koderift
|
|
|
51
74
|
end
|
|
52
75
|
|
|
53
76
|
payload = {
|
|
54
|
-
job_class: job
|
|
77
|
+
job_class: job_name(job),
|
|
55
78
|
jid: job['jid'].to_s,
|
|
56
79
|
queue: queue.to_s,
|
|
57
80
|
status: error ? 500 : 200,
|