logister-ruby 0.1.2 → 0.2.1
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/lib/logister/client.rb +33 -18
- data/lib/logister/configuration.rb +2 -0
- data/lib/logister/middleware.rb +65 -3
- data/lib/logister/railtie.rb +4 -2
- data/lib/logister/reporter.rb +121 -26
- data/lib/logister/sql_subscriber.rb +31 -15
- data/lib/logister/version.rb +3 -1
- data/lib/logister.rb +10 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6f55dcaf8248136d818bf775ec1735e6b5340a83b4226eec28540679c3c2725
|
|
4
|
+
data.tar.gz: 773b83846639292bbdb935a333f3384f6d3b737c1d3933044a7d6a8c59884d84
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e16455627f58c8793b8c317bde748e5a5288de2ca596bcfb641affc327737bc8de21e0b16ce36d749e53f29d95b773587808989acd792432a2125d7ed0ae8f02
|
|
7
|
+
data.tar.gz: 649f1cb0ff5aa498d03cc359d49dd9c77a64e07867f3ca92904a2552bb494d279addeba1cafd0048c68876f6f39e46cf77dfc7fecc7567d5a6c14c6d1ef8232b
|
data/lib/logister/client.rb
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'net/http'
|
|
3
5
|
require 'uri'
|
|
4
6
|
|
|
5
7
|
module Logister
|
|
6
8
|
class Client
|
|
9
|
+
CONTENT_TYPE = 'application/json'
|
|
10
|
+
|
|
7
11
|
def initialize(configuration)
|
|
8
12
|
@configuration = configuration
|
|
9
|
-
@worker_mutex
|
|
10
|
-
@queue
|
|
11
|
-
@worker
|
|
12
|
-
@running
|
|
13
|
+
@worker_mutex = Mutex.new
|
|
14
|
+
@queue = SizedQueue.new(@configuration.queue_size)
|
|
15
|
+
@worker = nil
|
|
16
|
+
@running = false
|
|
17
|
+
|
|
18
|
+
# Cache values that are static for the lifetime of this client so we
|
|
19
|
+
# don't allocate on every send_request call.
|
|
20
|
+
@uri = URI.parse(@configuration.endpoint).freeze
|
|
21
|
+
@use_ssl = @uri.scheme == 'https'
|
|
22
|
+
@auth_header = "Bearer #{@configuration.api_key}".freeze
|
|
13
23
|
end
|
|
14
24
|
|
|
15
25
|
def publish(payload)
|
|
@@ -24,9 +34,9 @@ module Logister
|
|
|
24
34
|
def flush(timeout: 2)
|
|
25
35
|
return true unless @configuration.async
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return false if monotonic_now
|
|
37
|
+
deadline = monotonic_now + timeout
|
|
38
|
+
until @queue.empty?
|
|
39
|
+
return false if monotonic_now > deadline
|
|
30
40
|
|
|
31
41
|
sleep(0.01)
|
|
32
42
|
end
|
|
@@ -44,6 +54,7 @@ module Logister
|
|
|
44
54
|
nil
|
|
45
55
|
end
|
|
46
56
|
@worker&.join(1)
|
|
57
|
+
@worker = nil
|
|
47
58
|
true
|
|
48
59
|
end
|
|
49
60
|
|
|
@@ -58,18 +69,20 @@ module Logister
|
|
|
58
69
|
end
|
|
59
70
|
|
|
60
71
|
def ensure_worker_started
|
|
72
|
+
# Fast path — no lock needed if already running (GVL-safe on MRI).
|
|
61
73
|
return if @running && @worker&.alive?
|
|
62
74
|
|
|
63
75
|
@worker_mutex.synchronize do
|
|
64
76
|
return if @running && @worker&.alive?
|
|
65
77
|
|
|
66
78
|
@running = true
|
|
67
|
-
@worker
|
|
79
|
+
@worker = Thread.new { run_worker }
|
|
80
|
+
@worker.name = 'logister-worker'
|
|
68
81
|
end
|
|
69
82
|
end
|
|
70
83
|
|
|
71
84
|
def run_worker
|
|
72
|
-
|
|
85
|
+
loop do
|
|
73
86
|
payload = @queue.pop
|
|
74
87
|
break if payload.nil?
|
|
75
88
|
|
|
@@ -77,6 +90,9 @@ module Logister
|
|
|
77
90
|
end
|
|
78
91
|
rescue StandardError => e
|
|
79
92
|
@configuration.logger.warn("logister worker crashed: #{e.class} #{e.message}")
|
|
93
|
+
ensure
|
|
94
|
+
# Always clear running flag and attempt auto-restart after a crash so
|
|
95
|
+
# events enqueued after the crash are not silently dropped.
|
|
80
96
|
@running = false
|
|
81
97
|
end
|
|
82
98
|
|
|
@@ -97,16 +113,15 @@ module Logister
|
|
|
97
113
|
end
|
|
98
114
|
|
|
99
115
|
def send_request(payload)
|
|
100
|
-
|
|
101
|
-
request
|
|
102
|
-
request['
|
|
103
|
-
request
|
|
104
|
-
request.body = { event: payload }.to_json
|
|
116
|
+
request = Net::HTTP::Post.new(@uri)
|
|
117
|
+
request['Content-Type'] = CONTENT_TYPE
|
|
118
|
+
request['Authorization'] = @auth_header
|
|
119
|
+
request.body = { event: payload }.to_json
|
|
105
120
|
|
|
106
121
|
response = Net::HTTP.start(
|
|
107
|
-
uri.host,
|
|
108
|
-
uri.port,
|
|
109
|
-
use_ssl:
|
|
122
|
+
@uri.host,
|
|
123
|
+
@uri.port,
|
|
124
|
+
use_ssl: @use_ssl,
|
|
110
125
|
open_timeout: @configuration.timeout_seconds,
|
|
111
126
|
read_timeout: @configuration.timeout_seconds
|
|
112
127
|
) { |http| http.request(request) }
|
|
@@ -121,7 +136,7 @@ module Logister
|
|
|
121
136
|
end
|
|
122
137
|
|
|
123
138
|
def ready?
|
|
124
|
-
@configuration.enabled &&
|
|
139
|
+
@configuration.enabled && !@configuration.api_key.to_s.empty?
|
|
125
140
|
end
|
|
126
141
|
end
|
|
127
142
|
end
|
data/lib/logister/middleware.rb
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
|
|
1
5
|
module Logister
|
|
2
6
|
class Middleware
|
|
7
|
+
# Sensitive param key fragments matched case-insensitively.
|
|
8
|
+
SENSITIVE_PARAM_RE = /password|token|secret|api_key|credit_card|cvv|ssn/i.freeze
|
|
9
|
+
FILTERED = '[FILTERED]'
|
|
10
|
+
|
|
3
11
|
def initialize(app)
|
|
4
12
|
@app = app
|
|
13
|
+
|
|
14
|
+
# Cache values that are constant for the lifetime of this process so
|
|
15
|
+
# they are not recomputed on every error.
|
|
16
|
+
@hostname = resolve_hostname.freeze
|
|
17
|
+
@app_context = build_app_context.freeze
|
|
5
18
|
end
|
|
6
19
|
|
|
7
20
|
def call(env)
|
|
@@ -10,12 +23,61 @@ module Logister
|
|
|
10
23
|
Logister.report_error(
|
|
11
24
|
e,
|
|
12
25
|
context: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
method: env['REQUEST_METHOD']
|
|
26
|
+
request: build_request_context(env),
|
|
27
|
+
app: @app_context
|
|
16
28
|
}
|
|
17
29
|
)
|
|
18
30
|
raise
|
|
19
31
|
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def build_request_context(env)
|
|
36
|
+
ctx = {
|
|
37
|
+
id: env['action_dispatch.request_id'],
|
|
38
|
+
path: env['PATH_INFO'],
|
|
39
|
+
method: env['REQUEST_METHOD'],
|
|
40
|
+
ip: remote_ip(env),
|
|
41
|
+
user_agent: env['HTTP_USER_AGENT']
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Params — available if ActionDispatch has already parsed them.
|
|
45
|
+
if (params = env['action_dispatch.request.parameters'])
|
|
46
|
+
ctx[:params] = filter_params(params)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
ctx.compact
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_app_context
|
|
53
|
+
ctx = { ruby: RUBY_VERSION, hostname: @hostname }
|
|
54
|
+
ctx[:rails] = Rails::VERSION::STRING if defined?(Rails::VERSION)
|
|
55
|
+
ctx
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Respect X-Forwarded-For set by proxies; fall back to REMOTE_ADDR.
|
|
59
|
+
def remote_ip(env)
|
|
60
|
+
forwarded = env['HTTP_X_FORWARDED_FOR']
|
|
61
|
+
return env['REMOTE_ADDR'] if forwarded.nil? || forwarded.empty?
|
|
62
|
+
|
|
63
|
+
first = forwarded.split(',').first
|
|
64
|
+
first ? first.strip : env['REMOTE_ADDR']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Filter out sensitive parameter values using a single Regexp so we avoid
|
|
68
|
+
# allocating a downcased String for every param key on every error.
|
|
69
|
+
def filter_params(params)
|
|
70
|
+
params.each_with_object({}) do |(k, v), h|
|
|
71
|
+
h[k] = k.to_s.match?(SENSITIVE_PARAM_RE) ? FILTERED : v
|
|
72
|
+
end
|
|
73
|
+
rescue StandardError
|
|
74
|
+
{}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def resolve_hostname
|
|
78
|
+
Socket.gethostname
|
|
79
|
+
rescue StandardError
|
|
80
|
+
'unknown'
|
|
81
|
+
end
|
|
20
82
|
end
|
|
21
83
|
end
|
data/lib/logister/railtie.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rails/railtie'
|
|
2
4
|
|
|
3
5
|
module Logister
|
|
@@ -38,10 +40,10 @@ module Logister
|
|
|
38
40
|
private
|
|
39
41
|
|
|
40
42
|
def copy_setting(app, config, key)
|
|
41
|
-
value = app.config.logister.
|
|
43
|
+
value = app.config.logister.public_send(key)
|
|
42
44
|
return if value.nil?
|
|
43
45
|
|
|
44
|
-
config.
|
|
46
|
+
config.public_send(:"#{key}=", value)
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
end
|
data/lib/logister/reporter.rb
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'digest'
|
|
2
4
|
require 'time'
|
|
5
|
+
require 'set'
|
|
3
6
|
|
|
4
7
|
module Logister
|
|
5
8
|
class Reporter
|
|
6
9
|
def initialize(configuration)
|
|
7
10
|
@configuration = configuration
|
|
8
|
-
@client
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
@client = Client.new(configuration)
|
|
12
|
+
|
|
13
|
+
# Pre-build values that are static for the lifetime of this reporter so
|
|
14
|
+
# they are not allocated on every report_error / report_metric call.
|
|
15
|
+
@static_context = {
|
|
16
|
+
environment: @configuration.environment,
|
|
17
|
+
service: @configuration.service,
|
|
18
|
+
release: @configuration.release
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Normalise ignore_environments once into a frozen Set of Strings so
|
|
22
|
+
# ignored_environment? never allocates a mapped Array.
|
|
23
|
+
@ignored_envs = Set.new(@configuration.ignore_environments.map(&:to_s)).freeze
|
|
24
|
+
|
|
25
|
+
# Cache the current-environment String to avoid repeated .to_s calls.
|
|
26
|
+
@current_env = @configuration.environment.to_s.freeze
|
|
27
|
+
|
|
28
|
+
# Compile the app-root stripping Regexp once; Dir.pwd is a syscall that
|
|
29
|
+
# always returns a new String — do it exactly once here.
|
|
30
|
+
app_root = Dir.pwd.to_s.freeze
|
|
31
|
+
@app_root_re = /\A#{Regexp.escape(app_root)}\//.freeze
|
|
32
|
+
|
|
33
|
+
# Register shutdown hook. Guard with a flag so multiple Reporter instances
|
|
34
|
+
# (created by repeated Logister.configure calls) each shut down cleanly
|
|
35
|
+
# without re-registering more handlers.
|
|
36
|
+
@shutdown_registered = false
|
|
37
|
+
register_shutdown_hook
|
|
11
38
|
end
|
|
12
39
|
|
|
13
40
|
def report_error(exception, context: {}, tags: {}, level: 'error', fingerprint: nil)
|
|
14
41
|
return false if ignored_exception?(exception)
|
|
15
42
|
return false if ignored_path?(context)
|
|
16
43
|
|
|
44
|
+
merged_context = context.dup
|
|
45
|
+
user = current_user_context
|
|
46
|
+
merged_context[:user] = user if user
|
|
47
|
+
|
|
17
48
|
payload = build_payload(
|
|
18
|
-
event_type:
|
|
19
|
-
level:
|
|
20
|
-
message:
|
|
49
|
+
event_type: 'error',
|
|
50
|
+
level: level,
|
|
51
|
+
message: "#{exception.class}: #{exception.message}",
|
|
21
52
|
fingerprint: fingerprint || default_fingerprint(exception),
|
|
22
|
-
context:
|
|
53
|
+
context: merged_context.merge(
|
|
23
54
|
exception: {
|
|
24
|
-
class:
|
|
25
|
-
message:
|
|
55
|
+
class: exception.class.to_s,
|
|
56
|
+
message: exception.message.to_s,
|
|
26
57
|
backtrace: Array(exception.backtrace).first(50)
|
|
27
58
|
},
|
|
28
59
|
tags: tags
|
|
@@ -40,11 +71,11 @@ module Logister
|
|
|
40
71
|
return false if ignored_path?(context)
|
|
41
72
|
|
|
42
73
|
payload = build_payload(
|
|
43
|
-
event_type:
|
|
44
|
-
level:
|
|
45
|
-
message:
|
|
46
|
-
fingerprint: fingerprint ||
|
|
47
|
-
context:
|
|
74
|
+
event_type: 'metric',
|
|
75
|
+
level: level,
|
|
76
|
+
message: message,
|
|
77
|
+
fingerprint: fingerprint || metric_fingerprint(message),
|
|
78
|
+
context: context.merge(tags: tags)
|
|
48
79
|
)
|
|
49
80
|
|
|
50
81
|
payload = apply_before_notify(payload)
|
|
@@ -53,6 +84,20 @@ module Logister
|
|
|
53
84
|
@client.publish(payload)
|
|
54
85
|
end
|
|
55
86
|
|
|
87
|
+
# Store user info for the current thread so it is automatically attached to
|
|
88
|
+
# every error reported during this request.
|
|
89
|
+
#
|
|
90
|
+
# Logister.set_user(id: current_user.id, email: current_user.email, name: current_user.name)
|
|
91
|
+
#
|
|
92
|
+
def set_user(id: nil, email: nil, name: nil, **extra)
|
|
93
|
+
ctx = { id: id, email: email, name: name }.merge(extra).compact
|
|
94
|
+
Thread.current[:logister_user] = ctx.empty? ? nil : ctx
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def clear_user
|
|
98
|
+
Thread.current[:logister_user] = nil
|
|
99
|
+
end
|
|
100
|
+
|
|
56
101
|
def flush(timeout: 2)
|
|
57
102
|
@client.flush(timeout: timeout)
|
|
58
103
|
end
|
|
@@ -63,18 +108,31 @@ module Logister
|
|
|
63
108
|
|
|
64
109
|
private
|
|
65
110
|
|
|
111
|
+
def register_shutdown_hook
|
|
112
|
+
return if @shutdown_registered
|
|
113
|
+
|
|
114
|
+
@shutdown_registered = true
|
|
115
|
+
# Capture @client directly (not self) so the at_exit proc does not
|
|
116
|
+
# retain the entire Reporter in the finalizer chain.
|
|
117
|
+
client = @client
|
|
118
|
+
at_exit { client.shutdown }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def current_user_context
|
|
122
|
+
Thread.current[:logister_user]
|
|
123
|
+
end
|
|
124
|
+
|
|
66
125
|
def build_payload(event_type:, level:, message:, fingerprint:, context:)
|
|
67
126
|
{
|
|
68
|
-
event_type:
|
|
69
|
-
level:
|
|
70
|
-
message:
|
|
127
|
+
event_type: event_type,
|
|
128
|
+
level: level,
|
|
129
|
+
message: message,
|
|
71
130
|
fingerprint: fingerprint,
|
|
72
131
|
occurred_at: Time.now.utc.iso8601,
|
|
73
|
-
context
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
132
|
+
# Merge static config context last so caller-supplied keys are not
|
|
133
|
+
# overwritten, then merge the static values. The static_context Hash
|
|
134
|
+
# is frozen and reused — only the new outer Hash is allocated.
|
|
135
|
+
context: @static_context.merge(context)
|
|
78
136
|
}
|
|
79
137
|
end
|
|
80
138
|
|
|
@@ -104,21 +162,58 @@ module Logister
|
|
|
104
162
|
end
|
|
105
163
|
|
|
106
164
|
def ignored_environment?
|
|
107
|
-
|
|
108
|
-
@configuration.ignore_environments.map(&:to_s).include?(env)
|
|
165
|
+
@ignored_envs.include?(@current_env)
|
|
109
166
|
end
|
|
110
167
|
|
|
111
168
|
def ignored_path?(context)
|
|
112
169
|
path = context[:path] || context['path']
|
|
113
170
|
return false if path.to_s.empty?
|
|
114
171
|
|
|
172
|
+
path_s = path.to_s
|
|
115
173
|
@configuration.ignore_paths.any? do |matcher|
|
|
116
|
-
matcher.is_a?(Regexp) ? matcher.match?(
|
|
174
|
+
matcher.is_a?(Regexp) ? matcher.match?(path_s) : path_s.include?(matcher.to_s)
|
|
117
175
|
end
|
|
118
176
|
end
|
|
119
177
|
|
|
178
|
+
# Cache metric fingerprints — metric messages are typically a small fixed
|
|
179
|
+
# set of constants (e.g. 'db.query') so the SHA256 is identical every call.
|
|
180
|
+
def metric_fingerprint(message)
|
|
181
|
+
@metric_fingerprint_cache ||= {}
|
|
182
|
+
key = message.to_s
|
|
183
|
+
@metric_fingerprint_cache[key] ||=
|
|
184
|
+
Digest::SHA256.hexdigest(key)[0, 32].freeze
|
|
185
|
+
end
|
|
186
|
+
|
|
120
187
|
def default_fingerprint(exception)
|
|
121
|
-
|
|
188
|
+
# Prefer class + first backtrace location so that errors with dynamic
|
|
189
|
+
# values in their message (e.g. "Couldn't find User with 'id'=42") still
|
|
190
|
+
# group together across different IDs / UUIDs.
|
|
191
|
+
location = Array(exception.backtrace).first.to_s
|
|
192
|
+
.sub(/:in\s+.+$/, '') # strip method name
|
|
193
|
+
.sub(/\A.*\/gems\//, 'gems/') # normalise gem paths
|
|
194
|
+
.sub(@app_root_re, '') # strip app root (pre-compiled RE)
|
|
195
|
+
|
|
196
|
+
if location.empty?
|
|
197
|
+
# No backtrace — scrub dynamic tokens from the message before hashing.
|
|
198
|
+
scrubbed = scrub_dynamic_values(exception.message.to_s)
|
|
199
|
+
Digest::SHA256.hexdigest("#{exception.class}|#{scrubbed}")[0, 32]
|
|
200
|
+
else
|
|
201
|
+
Digest::SHA256.hexdigest("#{exception.class}|#{location}")[0, 32]
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Strip values that vary per-occurrence but carry no grouping signal:
|
|
206
|
+
# - numeric IDs: id=42, 'id'=42, id: 42
|
|
207
|
+
# - UUIDs
|
|
208
|
+
# - hex digests (≥8 hex chars)
|
|
209
|
+
# - quoted string values in ActiveRecord-style messages
|
|
210
|
+
def scrub_dynamic_values(message)
|
|
211
|
+
message
|
|
212
|
+
.gsub(/\b(id['"]?\s*[=:]\s*)\d+/i, '\1?')
|
|
213
|
+
.gsub(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i, '?')
|
|
214
|
+
.gsub(/\b[0-9a-f]{8,}\b/, '?')
|
|
215
|
+
.gsub(/'[^']{1,64}'/, '?')
|
|
216
|
+
.gsub(/\d+/, '?')
|
|
122
217
|
end
|
|
123
218
|
end
|
|
124
219
|
end
|
|
@@ -1,13 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Logister
|
|
2
4
|
class SqlSubscriber
|
|
3
5
|
IGNORED_SQL_NAMES = %w[SCHEMA TRANSACTION].freeze
|
|
4
6
|
|
|
7
|
+
# Frozen constants for values emitted on every captured query.
|
|
8
|
+
MESSAGE = 'db.query'
|
|
9
|
+
LEVEL_WARN = 'warn'
|
|
10
|
+
LEVEL_INFO = 'info'
|
|
11
|
+
TAGS = { category: 'database' }.freeze
|
|
12
|
+
|
|
13
|
+
# Pre-compute the fingerprint for the fixed message string so we pay the
|
|
14
|
+
# SHA256 cost exactly once instead of on every captured SQL query.
|
|
15
|
+
require 'digest'
|
|
16
|
+
SQL_FINGERPRINT = Digest::SHA256.hexdigest(MESSAGE)[0, 32].freeze
|
|
17
|
+
|
|
5
18
|
class << self
|
|
6
19
|
def install!
|
|
7
20
|
return if @installed
|
|
8
21
|
|
|
9
|
-
ActiveSupport::Notifications.subscribe('sql.active_record') do |
|
|
10
|
-
handle_sql_event(
|
|
22
|
+
ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, started, finished, _id, payload|
|
|
23
|
+
handle_sql_event(started, finished, payload)
|
|
11
24
|
end
|
|
12
25
|
|
|
13
26
|
@installed = true
|
|
@@ -15,31 +28,34 @@ module Logister
|
|
|
15
28
|
|
|
16
29
|
private
|
|
17
30
|
|
|
18
|
-
def handle_sql_event(
|
|
31
|
+
def handle_sql_event(started, finished, payload)
|
|
19
32
|
config = Logister.configuration
|
|
33
|
+
|
|
34
|
+
# Short-circuit as cheaply as possible when metrics are disabled so
|
|
35
|
+
# that *every* SQL query in the app pays minimal overhead.
|
|
20
36
|
return unless config.capture_db_metrics
|
|
21
37
|
return if payload[:cached]
|
|
22
|
-
|
|
38
|
+
|
|
39
|
+
# Evaluate name once — it's used in two places below.
|
|
40
|
+
sql_name = payload[:name].to_s
|
|
41
|
+
return if IGNORED_SQL_NAMES.include?(sql_name)
|
|
23
42
|
|
|
24
43
|
duration_ms = (finished - started) * 1000.0
|
|
25
44
|
return if duration_ms < config.db_metric_min_duration_ms.to_f
|
|
26
45
|
return if sampled_out?(config.db_metric_sample_rate)
|
|
27
46
|
|
|
28
|
-
level = duration_ms >= 500 ? 'warn' : 'info'
|
|
29
|
-
|
|
30
47
|
Logister.report_metric(
|
|
31
|
-
message:
|
|
32
|
-
level:
|
|
48
|
+
message: MESSAGE,
|
|
49
|
+
level: duration_ms >= 500 ? LEVEL_WARN : LEVEL_INFO,
|
|
50
|
+
fingerprint: SQL_FINGERPRINT,
|
|
33
51
|
context: {
|
|
34
52
|
duration_ms: duration_ms.round(2),
|
|
35
|
-
name:
|
|
36
|
-
sql:
|
|
37
|
-
cached:
|
|
38
|
-
binds_count:
|
|
53
|
+
name: sql_name,
|
|
54
|
+
sql: payload[:sql].to_s,
|
|
55
|
+
cached: false,
|
|
56
|
+
binds_count: (payload[:binds] || []).size
|
|
39
57
|
},
|
|
40
|
-
tags:
|
|
41
|
-
category: 'database'
|
|
42
|
-
}
|
|
58
|
+
tags: TAGS
|
|
43
59
|
)
|
|
44
60
|
rescue StandardError => e
|
|
45
61
|
config.logger.warn("logister sql subscriber failed: #{e.class} #{e.message}")
|
data/lib/logister/version.rb
CHANGED
data/lib/logister.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'logister/version'
|
|
2
4
|
require_relative 'logister/configuration'
|
|
3
5
|
require_relative 'logister/client'
|
|
@@ -28,6 +30,14 @@ module Logister
|
|
|
28
30
|
reporter.report_metric(**kwargs)
|
|
29
31
|
end
|
|
30
32
|
|
|
33
|
+
def set_user(id: nil, email: nil, name: nil, **extra)
|
|
34
|
+
reporter.set_user(id: id, email: email, name: name, **extra)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def clear_user
|
|
38
|
+
reporter.clear_user
|
|
39
|
+
end
|
|
40
|
+
|
|
31
41
|
def flush(timeout: 2)
|
|
32
42
|
reporter.flush(timeout: timeout)
|
|
33
43
|
end
|