logister-ruby 0.5.1 → 0.6.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.
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'digest'
4
4
  require 'time'
5
- require 'set'
6
5
  require 'securerandom'
7
6
  require_relative 'context_helpers'
8
7
  require_relative 'context_store'
@@ -35,12 +34,6 @@ module Logister
35
34
  # always returns a new String — do it exactly once here.
36
35
  app_root = Dir.pwd.to_s.freeze
37
36
  @app_root_re = /\A#{Regexp.escape(app_root)}\//.freeze
38
-
39
- # Register shutdown hook. Guard with a flag so multiple Reporter instances
40
- # (created by repeated Logister.configure calls) each shut down cleanly
41
- # without re-registering more handlers.
42
- @shutdown_registered = false
43
- register_shutdown_hook
44
37
  end
45
38
 
46
39
  def report_error(exception, context: {}, tags: {}, level: 'error', fingerprint: nil)
@@ -67,6 +60,8 @@ module Logister
67
60
  return false unless payload
68
61
 
69
62
  @client.publish(payload)
63
+ rescue StandardError => e
64
+ internal_failure(e)
70
65
  end
71
66
 
72
67
  def report_metric(message:, value: nil, unit: nil, level: 'info', context: {}, tags: {}, fingerprint: nil)
@@ -99,6 +94,8 @@ module Logister
99
94
  return false unless payload
100
95
 
101
96
  @client.publish(payload)
97
+ rescue StandardError => e
98
+ internal_failure(e)
102
99
  end
103
100
 
104
101
  def report_transaction(name:, duration_ms:, level: 'info', context: {}, tags: {}, fingerprint: nil, status: nil)
@@ -123,6 +120,8 @@ module Logister
123
120
  return false unless payload
124
121
 
125
122
  @client.publish(payload)
123
+ rescue StandardError => e
124
+ internal_failure(e)
126
125
  end
127
126
 
128
127
  def report_span(
@@ -186,6 +185,8 @@ module Logister
186
185
  return false unless payload
187
186
 
188
187
  @client.publish(payload)
188
+ rescue StandardError => e
189
+ internal_failure(e)
189
190
  end
190
191
 
191
192
  def report_log(message:, level: 'info', context: {}, tags: {}, fingerprint: nil)
@@ -205,6 +206,8 @@ module Logister
205
206
  return false unless payload
206
207
 
207
208
  @client.publish(payload)
209
+ rescue StandardError => e
210
+ internal_failure(e)
208
211
  end
209
212
 
210
213
  def report_check_in(
@@ -245,6 +248,8 @@ module Logister
245
248
  return false unless payload
246
249
 
247
250
  @client.publish(payload)
251
+ rescue StandardError => e
252
+ internal_failure(e)
248
253
  end
249
254
 
250
255
  def record_deployment(
@@ -281,20 +286,24 @@ module Logister
281
286
  }.compact
282
287
 
283
288
  @client.publish_deployment(payload)
289
+ rescue StandardError => e
290
+ internal_failure(e)
284
291
  end
285
292
 
286
- # Store user info for the current thread so it is automatically attached to
287
- # every error reported during this request.
293
+ # Store user info in the current request scope so it is attached to every
294
+ # error reported during this request or job. The middleware and Active Job
295
+ # integration reset the scope, so identity never carries to the next unit of
296
+ # work on a reused thread.
288
297
  #
289
298
  # Logister.set_user(id: current_user.id, email: current_user.email, name: current_user.name)
290
299
  #
291
300
  def set_user(id: nil, email: nil, name: nil, **extra)
292
301
  ctx = { id: id, email: email, name: name }.merge(extra).compact
293
- Thread.current[:logister_user] = ctx.empty? ? nil : ctx
302
+ Logister::ContextStore.user = ctx.empty? ? nil : ctx
294
303
  end
295
304
 
296
305
  def clear_user
297
- Thread.current[:logister_user] = nil
306
+ Logister::ContextStore.user = nil
298
307
  end
299
308
 
300
309
  def flush(timeout: 2)
@@ -311,18 +320,13 @@ module Logister
311
320
 
312
321
  private
313
322
 
314
- def register_shutdown_hook
315
- return if @shutdown_registered
316
-
317
- @shutdown_registered = true
318
- # Capture @client directly (not self) so the at_exit proc does not
319
- # retain the entire Reporter in the finalizer chain.
320
- client = @client
321
- at_exit { client.shutdown }
323
+ def current_user_context
324
+ Logister::ContextStore.user
322
325
  end
323
326
 
324
- def current_user_context
325
- Thread.current[:logister_user]
327
+ def internal_failure(error)
328
+ Logister.log_internal_error('report', error)
329
+ false
326
330
  end
327
331
 
328
332
  def default_error_context
@@ -335,7 +339,7 @@ module Logister
335
339
  .merge(Logister::ContextHelpers.deployment_context)
336
340
  )
337
341
  rescue StandardError => e
338
- @configuration.logger.warn("logister default error context failed: #{e.class} #{e.message}")
342
+ Logister.log_internal_error('default error context', e)
339
343
  {}
340
344
  end
341
345
 
@@ -360,7 +364,7 @@ module Logister
360
364
  # Merge static config context last so caller-supplied keys are not
361
365
  # overwritten, then merge the static values. The static_context Hash
362
366
  # is frozen and reused — only the new outer Hash is allocated.
363
- context: @static_context.merge(Logister::ContextStore.trace_context&.to_h || {}).merge(context)
367
+ context: @static_context.merge(Logister::ContextStore.trace_context.to_h).merge(context)
364
368
  }
365
369
  end
366
370
 
@@ -377,7 +381,7 @@ module Logister
377
381
 
378
382
  result
379
383
  rescue StandardError => e
380
- @configuration.logger.warn("logister before_notify failed: #{e.class} #{e.message}")
384
+ Logister.log_internal_error('before_notify', e)
381
385
  nil
382
386
  end
383
387
 
@@ -1,5 +1,3 @@
1
- require "logger"
2
-
3
1
  module Logister
4
2
  class RequestSubscriber
5
3
  IGNORED_SQL_NAMES = %w[SCHEMA TRANSACTION].freeze
@@ -57,7 +55,7 @@ module Logister
57
55
 
58
56
  report_request_span(payload, request_id) if capture_request_spans?
59
57
  rescue StandardError => e
60
- logger.warn("logister request subscriber (process_action) failed: #{e.class} #{e.message}")
58
+ Logister.log_internal_error("request subscriber (process_action)", e)
61
59
  end
62
60
 
63
61
  def report_request_span(payload, request_id)
@@ -110,11 +108,11 @@ module Logister
110
108
  message: "#{sql_name} query",
111
109
  data: {
112
110
  durationMs: duration_ms,
113
- sql: payload[:sql].to_s[0, 250]
111
+ sql: Logister::Redaction.strip_sql_literals(payload[:sql])[0, 250]
114
112
  }
115
113
  )
116
114
  rescue StandardError => e
117
- logger.warn("logister request subscriber (sql breadcrumb) failed: #{e.class} #{e.message}")
115
+ Logister.log_internal_error("request subscriber (sql breadcrumb)", e)
118
116
  end
119
117
 
120
118
  def numeric(value)
@@ -136,12 +134,6 @@ module Logister
136
134
  rescue StandardError
137
135
  false
138
136
  end
139
-
140
- def logger
141
- configuration&.logger || Logger.new($stdout)
142
- rescue StandardError
143
- Logger.new($stdout)
144
- end
145
137
  end
146
138
  end
147
139
  end
@@ -53,14 +53,14 @@ module Logister
53
53
  context: {
54
54
  duration_ms: duration_ms.round(2),
55
55
  name: sql_name,
56
- sql: payload[:sql].to_s,
56
+ sql: Logister::Redaction.strip_sql_literals(payload[:sql]),
57
57
  cached: false,
58
58
  binds_count: (payload[:binds] || []).size
59
59
  },
60
60
  tags: TAGS
61
61
  )
62
62
  rescue StandardError => e
63
- config.logger.warn("logister sql subscriber failed: #{e.class} #{e.message}")
63
+ Logister.log_internal_error('sql subscriber', e)
64
64
  end
65
65
 
66
66
  def sampled_out?(sample_rate)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Logister
4
- VERSION = '0.5.1'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/logister.rb CHANGED
@@ -1,5 +1,8 @@
1
+ require 'active_support/core_ext/object/blank'
1
2
  require_relative 'logister/version'
2
3
  require_relative 'logister/configuration'
4
+ require_relative 'logister/redaction'
5
+ require_relative 'logister/event_sanitizer'
3
6
  require_relative 'logister/reporting_scope'
4
7
  require_relative 'logister/client'
5
8
  require_relative 'logister/reporter'
@@ -26,12 +29,18 @@ module Logister
26
29
  @configuration ||= Configuration.new
27
30
  end
28
31
 
32
+ # Reconfiguring replaces the reporter. The previous client drains what it
33
+ # already queued and stops, so repeated calls never leave idle workers.
29
34
  def configure
30
35
  yield(configuration)
36
+ previous = @reporter
31
37
  @reporter = nil
38
+ safely(nil) { previous&.shutdown }
39
+ register_shutdown_hook
32
40
  end
33
41
 
34
42
  def reporter
43
+ register_shutdown_hook
35
44
  @reporter ||= Reporter.new(configuration)
36
45
  end
37
46
 
@@ -44,64 +53,102 @@ module Logister
44
53
  end
45
54
 
46
55
  def report_error(exception, **kwargs)
47
- reporter.report_error(exception, **kwargs)
56
+ safely { reporter.report_error(exception, **kwargs) }
48
57
  end
49
58
 
50
59
  def report_metric(**kwargs)
51
- reporter.report_metric(**kwargs)
60
+ safely { reporter.report_metric(**kwargs) }
52
61
  end
53
62
 
54
63
  def report_transaction(**kwargs)
55
- reporter.report_transaction(**kwargs)
64
+ safely { reporter.report_transaction(**kwargs) }
56
65
  end
57
66
 
58
67
  def report_span(**kwargs)
59
- reporter.report_span(**kwargs)
68
+ safely { reporter.report_span(**kwargs) }
60
69
  end
61
70
 
62
71
  def report_log(**kwargs)
63
- reporter.report_log(**kwargs)
72
+ safely { reporter.report_log(**kwargs) }
64
73
  end
65
74
 
66
75
  def report_check_in(**kwargs)
67
- reporter.report_check_in(**kwargs)
76
+ safely { reporter.report_check_in(**kwargs) }
68
77
  end
69
78
 
70
79
  def record_deployment(**kwargs)
71
- reporter.record_deployment(**kwargs)
80
+ safely { reporter.record_deployment(**kwargs) }
81
+ end
82
+
83
+ # Attaches user identity to errors reported in the current request or job.
84
+ # The Rack middleware and Active Job integration clear it when the request
85
+ # or job ends; call clear_user yourself in other long-lived threads.
86
+ def set_user(**kwargs)
87
+ safely(nil) { reporter.set_user(**kwargs) }
88
+ end
89
+
90
+ def clear_user
91
+ safely(nil) { reporter.clear_user }
72
92
  end
73
93
 
74
94
  def flush(timeout: 2)
75
- reporter.flush(timeout: timeout)
95
+ safely { reporter.flush(timeout: timeout) }
76
96
  end
77
97
 
78
98
  def delivery_stats
79
- reporter.delivery_stats
99
+ safely({}) { reporter.delivery_stats }
80
100
  end
81
101
 
82
102
  def shutdown
83
- reporter.shutdown
103
+ safely { reporter.shutdown }
84
104
  end
85
105
 
86
106
  def add_breadcrumb(category:, message:, data: {}, level: "info")
87
- ContextStore.add_manual_breadcrumb(
88
- category: category,
89
- message: message,
90
- data: data,
91
- level: level
92
- )
107
+ safely(nil) do
108
+ ContextStore.add_manual_breadcrumb(
109
+ category: category,
110
+ message: message,
111
+ data: data,
112
+ level: level
113
+ )
114
+ end
93
115
  end
94
116
 
95
117
  def add_dependency(name:, host: nil, method: nil, status: nil, duration_ms: nil, kind: nil, data: {})
96
- ContextStore.add_manual_dependency(
97
- name: name,
98
- host: host,
99
- method: method,
100
- status: status,
101
- duration_ms: duration_ms,
102
- kind: kind,
103
- data: data
104
- )
118
+ safely(nil) do
119
+ ContextStore.add_manual_dependency(
120
+ name: name,
121
+ host: host,
122
+ method: method,
123
+ status: status,
124
+ duration_ms: duration_ms,
125
+ kind: kind,
126
+ data: data
127
+ )
128
+ end
129
+ end
130
+
131
+ # Telemetry must never raise into the host application.
132
+ def log_internal_error(label, error)
133
+ configuration.logger&.warn("logister #{label} failed: #{error.class}")
134
+ rescue StandardError
135
+ nil
136
+ end
137
+
138
+ private
139
+
140
+ def safely(fallback = false)
141
+ yield
142
+ rescue StandardError => e
143
+ log_internal_error('call', e)
144
+ fallback
145
+ end
146
+
147
+ def register_shutdown_hook
148
+ return if @shutdown_hook_registered
149
+
150
+ @shutdown_hook_registered = true
151
+ at_exit { safely(nil) { @reporter&.shutdown } }
105
152
  end
106
153
  end
107
154
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logister-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Logister
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-09-26 00:00:00.000000000 Z
10
+ date: 2026-10-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -123,8 +123,10 @@ files:
123
123
  - lib/logister/configuration.rb
124
124
  - lib/logister/context_helpers.rb
125
125
  - lib/logister/context_store.rb
126
+ - lib/logister/event_sanitizer.rb
126
127
  - lib/logister/middleware.rb
127
128
  - lib/logister/railtie.rb
129
+ - lib/logister/redaction.rb
128
130
  - lib/logister/reporter.rb
129
131
  - lib/logister/reporting_scope.rb
130
132
  - lib/logister/request_subscriber.rb