logister-ruby 0.4.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b8f77ab291c0d9132dc6ea5d6f1711644d73a7892e9b7dbc4f5bb3cf418fd8f
4
- data.tar.gz: 83af842795eebffcbfe29135091e89243dcd34f0e61968e8887dc75a027947f5
3
+ metadata.gz: a71c622e00652d3d7cc881b852987bb0c16ce383456e8c6f1aaacb4d195bbca1
4
+ data.tar.gz: 3ce350703b176ce69a72bc96a2a860f29d615a5132ee242c0a0b39fbdb1bb54d
5
5
  SHA512:
6
- metadata.gz: 64bdd3ca1271f84c6495883ad5dac8ca829e29ef07a5dc79391848fa344fbcf39ac757650be7356611d16b285bc45ffa561ecbc804ca0c426a887ce6593f4f57
7
- data.tar.gz: 464876a85252f86ec25e507fde1a38ace3c152596756da77c682ea81a5f872d0a69b86db38404a911c42a331d932506c4162a18df60c118b10dae940542f7d19
6
+ metadata.gz: e62e74979448480d5d383c08b0b53f0f3e39789aa4ee050d85debe6a9a5c4b922779a5866fe4d48632bc96f3346c8b69799b63d92f2bd5c4152e866037d16fe0
7
+ data.tar.gz: ae516f75badb5bc30013410e9c540fb8066a63edd2c637ae320d26dc01016e660178f83a0da8b456b0b0efedd850af34bd6d6703b9693cd16d4c4878cf35b36a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.5.0 - 2026-09-24
4
+
5
+ - Propagate W3C request context through Rack and Rails, including logs, errors, transactions, and server spans.
6
+ - Add immutable request handles and exact-origin outbound header helpers; preserve incoming trace flags and isolate concurrent request scopes.
7
+
8
+ ## v0.4.2 - 2026-09-16
9
+
10
+ - Added process-local delivery counters and an optional recursion-safe observer for queued, acknowledged, unconfirmed, queue-full, and retry outcomes without exporting event contents.
11
+ - Clarified that a drained queue or successful `flush` does not prove every event was acknowledged; timed-out requests may already have reached the server.
12
+ - Removed exception messages from terminal event-publish warnings to avoid exposing transport details.
13
+ - Bounded async shutdown even when the queue is full; retained the worker until it finishes and report incomplete shutdown honestly.
14
+
3
15
  ## v0.4.1 - 2026-09-11
4
16
 
5
17
  - Constrained JSON to the compatible 2.x line while supported ActiveSupport 8 releases use positional JSON.parse options; verified real Rails request decoding.
data/README.md CHANGED
@@ -162,6 +162,24 @@ safe against a Logister server that supports the batch endpoint. Older servers a
162
162
  detected automatically and receive the same stable events through the single-event
163
163
  endpoint. Call `Logister.flush` before a short-lived process exits.
164
164
 
165
+ ### Delivery outcomes
166
+
167
+ `flush` waits for queued and in-flight attempts to finish; it does not guarantee
168
+ server acknowledgement. Inspect `Logister.delivery_stats` for process-local
169
+ `queued_events`, `acknowledged_events`, `unconfirmed_events`, `queue_full_events`,
170
+ `retry_attempts`, and `pending_events`. An unconfirmed event may already have
171
+ reached the server when its response timed out. Counts are not durable and reset
172
+ with the client/process; they cover events, not deployment reports.
173
+
174
+ For local delivery diagnostics, set `config.delivery_observer` to a fast callback
175
+ accepting `{ outcome:, count:, reason: }`. It receives no payload, identifiers,
176
+ credentials, or exception messages. Reporting is suppressed inside the callback
177
+ to prevent feedback loops; callback failure cannot change delivery. Write these
178
+ diagnostics to a local log or counter rather than publishing them through Logister.
179
+ `shutdown` waits at most one second for the worker and returns false if attempts
180
+ are still running. It stops accepting asynchronous events and does not kill an
181
+ in-flight request or block while adding a sentinel to a full queue.
182
+
165
183
  Every HTTP attempt applies `timeout_seconds` to connect, read, and write operations.
166
184
  Retryable responses honor `Retry-After` when present, cap any individual wait at
167
185
  `max_retry_delay`, and add bounded positive jitter controlled by `retry_jitter`.
@@ -446,3 +464,37 @@ package identity before creating the GitHub Release. Never move a consumed tag.
446
464
  Weekly CI audits/tests current dependencies and cannot trigger automatic publication.
447
465
  Dependabot groups compatible minor/patch updates; major toolchain migrations keep
448
466
  separate PRs. Pin Actions to full commits and retain supported runtime floors.
467
+
468
+ ## Request correlation (0.5.0+)
469
+
470
+ The Rack/Rails integration accepts strict W3C version 00 `traceparent` headers,
471
+ creates a local server span, and applies its identity to automatic and manual
472
+ telemetry during the request. It preserves the incoming trace flags. Each request
473
+ has its own scope, cleared even when the request raises.
474
+
475
+ For an outbound call, retain a child handle and apply headers only to the intended
476
+ origin. Disable automatic redirects, or regenerate and validate headers for every
477
+ hop. Keep telemetry and token-issuer calls outside this instrumentation.
478
+
479
+ ```ruby
480
+ trace = Logister.outbound_trace_context
481
+ headers = trace.headers_for("https://api.example.test/orders", allowed_origins: ["https://api.example.test"])
482
+ # Pass headers to your HTTP client with automatic redirects disabled.
483
+ # When that request fails:
484
+ Logister.report_error(StandardError.new("Order request failed"), context: trace.to_h)
485
+ ```
486
+
487
+ `Logister.current_trace_context` returns the immutable active request handle.
488
+ Background jobs require explicitly supplied context; there is no automatic queue
489
+ propagation in this release.
490
+
491
+ A linked-project lookup also requires Logister 3.7+, the instance flag
492
+ `LOGISTER_CROSS_PROJECT_CORRELATIONS=true`, and explicit project/environment
493
+ connections under Settings → Integrations → Connected projects. Enable related
494
+ requests on both projects. A connection never grants project access.
495
+
496
+ Use the returned request handle when reporting a handled HTTP failure later.
497
+ Do not attach the most recent request to an unrelated crash or OS diagnostic.
498
+ Configure each app's own `release` and `environment`; mobile and backend releases
499
+ are independent. The backend shows exact identifier evidence and retention gaps.
500
+ See the [request correlation guide](https://logister.org/docs/request-correlation/).
@@ -32,9 +32,13 @@ module Logister
32
32
  @queue = SizedQueue.new(@configuration.queue_size)
33
33
  @worker = nil
34
34
  @running = false
35
+ @stopping = false
35
36
  @pending_mutex = Mutex.new
36
37
  @pending_condition = ConditionVariable.new
37
38
  @pending_count = 0
39
+ @delivery_mutex = Mutex.new
40
+ @delivery_counts = { queued_events: 0, acknowledged_events: 0, unconfirmed_events: 0,
41
+ queue_full_events: 0, retry_attempts: 0 }
38
42
 
39
43
  # Cache values that are static for the lifetime of this client so we
40
44
  # don't allocate on every send_request call.
@@ -48,6 +52,7 @@ module Logister
48
52
  end
49
53
 
50
54
  def publish(payload)
55
+ return false if ReportingScope.suppressed?
51
56
  return false unless ready?
52
57
 
53
58
  payload = with_stable_uuid(payload)
@@ -83,25 +88,40 @@ module Logister
83
88
  def shutdown
84
89
  return true unless @configuration.async
85
90
 
86
- @running = false
87
- begin
88
- @queue.push(nil)
89
- rescue StandardError
90
- nil
91
+ worker = @worker_mutex.synchronize do
92
+ @stopping = true
93
+ begin
94
+ @queue.push(nil, true)
95
+ rescue ThreadError
96
+ # A full queue will be drained by the worker, which also checks the
97
+ # stopping flag. Never block the caller waiting to enqueue a sentinel.
98
+ end
99
+ @worker
91
100
  end
92
- @worker&.join(1)
93
- @worker = nil
94
- true
101
+ worker&.join(1)
102
+ !worker&.alive?
103
+ end
104
+
105
+ # Process-local counters, never an assertion of exactly-once server receipt.
106
+ def delivery_stats
107
+ @delivery_mutex.synchronize { @delivery_counts.dup }
108
+ .merge(pending_events: @pending_mutex.synchronize { @pending_count })
95
109
  end
96
110
 
97
111
  private
98
112
 
99
113
  def enqueue(payload)
100
- increment_pending
101
- @queue.push(payload, true)
114
+ @worker_mutex.synchronize do
115
+ return false if @stopping
116
+
117
+ increment_pending
118
+ @queue.push(payload, true)
119
+ end
120
+ record_delivery(:queued_events, 1)
102
121
  true
103
122
  rescue ThreadError
104
123
  complete_pending(1)
124
+ record_delivery(:queue_full_events, 1, reason: 'queue_full')
105
125
  @configuration.logger.warn('logister queue full; dropping event')
106
126
  false
107
127
  end
@@ -111,6 +131,7 @@ module Logister
111
131
  return if @running && @worker&.alive?
112
132
 
113
133
  @worker_mutex.synchronize do
134
+ return if @stopping
114
135
  return if @running && @worker&.alive?
115
136
 
116
137
  @running = true
@@ -144,7 +165,7 @@ module Logister
144
165
 
145
166
  publish_batch_sync(batch)
146
167
  complete_pending(batch.length)
147
- break if stop_after_batch
168
+ break if stop_after_batch || (@stopping && @queue.empty?)
148
169
  end
149
170
  rescue StandardError => e
150
171
  @configuration.logger.warn("logister worker crashed: #{e.class} #{e.message}")
@@ -158,14 +179,22 @@ module Logister
158
179
  attempts = 0
159
180
  begin
160
181
  attempts += 1
161
- send_request(payload)
182
+ delivered = send_request(payload)
183
+ unless delivered
184
+ record_delivery(:unconfirmed_events, 1, reason: 'no_acknowledgment')
185
+ return false
186
+ end
187
+ record_delivery(:acknowledged_events, 1)
188
+ true
162
189
  rescue StandardError => e
163
190
  if attempts <= @configuration.max_retries && retryable_error?(e)
191
+ record_delivery(:retry_attempts, 1, reason: failure_reason(e))
164
192
  sleep(retry_delay(e, attempts))
165
193
  retry
166
194
  end
167
195
 
168
- @configuration.logger.warn("logister publish failed: #{e.class} #{e.message}")
196
+ record_delivery(:unconfirmed_events, 1, reason: failure_reason(e))
197
+ @configuration.logger.warn("logister publish failed: #{e.class}")
169
198
  false
170
199
  end
171
200
  end
@@ -174,7 +203,13 @@ module Logister
174
203
  attempts = 0
175
204
  begin
176
205
  attempts += 1
177
- send_batch_request(payloads)
206
+ delivered = send_batch_request(payloads)
207
+ unless delivered
208
+ record_delivery(:unconfirmed_events, payloads.length, reason: 'no_acknowledgment')
209
+ return false
210
+ end
211
+ record_delivery(:acknowledged_events, payloads.length)
212
+ true
178
213
  rescue UnsupportedBatchEndpoint
179
214
  payloads.map { |payload| publish_sync(payload) }.all?
180
215
  rescue RequestError => e
@@ -185,19 +220,23 @@ module Logister
185
220
  return first_half_delivered && second_half_delivered
186
221
  end
187
222
  if attempts <= @configuration.max_retries && retryable_error?(e)
223
+ record_delivery(:retry_attempts, 1, reason: failure_reason(e))
188
224
  sleep(retry_delay(e, attempts))
189
225
  retry
190
226
  end
191
227
 
192
- @configuration.logger.warn("logister batch publish failed: #{e.class} #{e.message}")
228
+ record_delivery(:unconfirmed_events, payloads.length, reason: failure_reason(e))
229
+ @configuration.logger.warn("logister batch publish failed: #{e.class}")
193
230
  false
194
231
  rescue StandardError => e
195
232
  if attempts <= @configuration.max_retries && retryable_error?(e)
233
+ record_delivery(:retry_attempts, 1, reason: failure_reason(e))
196
234
  sleep(retry_delay(e, attempts))
197
235
  retry
198
236
  end
199
237
 
200
- @configuration.logger.warn("logister batch publish failed: #{e.class} #{e.message}")
238
+ record_delivery(:unconfirmed_events, payloads.length, reason: failure_reason(e))
239
+ @configuration.logger.warn("logister batch publish failed: #{e.class}")
201
240
  false
202
241
  end
203
242
  end
@@ -290,6 +329,26 @@ module Logister
290
329
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
291
330
  end
292
331
 
332
+ def failure_reason(error)
333
+ return "http_#{error.status}" if error.is_a?(RequestError)
334
+ return 'timeout' if error.is_a?(Timeout::Error)
335
+
336
+ 'transport_error'
337
+ end
338
+
339
+ def record_delivery(counter, count, reason: nil)
340
+ @delivery_mutex.synchronize { @delivery_counts[counter] += count }
341
+ observer = @configuration.delivery_observer
342
+ return unless observer
343
+
344
+ ReportingScope.suppress do
345
+ observer.call({ outcome: counter.to_s, count: count, reason: reason }.compact.freeze)
346
+ end
347
+ rescue StandardError
348
+ # Counters survive observer failure; never report this through telemetry.
349
+ nil
350
+ end
351
+
293
352
  def with_stable_uuid(payload)
294
353
  attributes = payload.to_h.dup
295
354
  key = attributes.keys.any? { |candidate| candidate.is_a?(String) } ? 'uuid' : :uuid
@@ -10,7 +10,7 @@ module Logister
10
10
  :capture_db_metrics, :db_metric_min_duration_ms, :db_metric_sample_rate,
11
11
  :feature_flags_resolver, :dependency_resolver, :anonymize_ip,
12
12
  :max_breadcrumbs, :max_dependencies, :capture_request_spans,
13
- :capture_sql_breadcrumbs, :sql_breadcrumb_min_duration_ms
13
+ :capture_sql_breadcrumbs, :sql_breadcrumb_min_duration_ms, :delivery_observer
14
14
  attr_writer :deployment_endpoint, :batch_endpoint
15
15
 
16
16
  def initialize
@@ -32,6 +32,7 @@ module Logister
32
32
  @ignore_environments = []
33
33
  @ignore_paths = []
34
34
  @before_notify = nil
35
+ @delivery_observer = nil
35
36
 
36
37
  @async = true
37
38
  @queue_size = 1000
@@ -12,7 +12,7 @@ module Logister
12
12
  {
13
13
  runtime: {
14
14
  rubyVersion: RUBY_VERSION,
15
- railsVersion: defined?(Rails) ? Rails.version : nil,
15
+ railsVersion: defined?(Rails) && Rails.respond_to?(:version) ? Rails.version : nil,
16
16
  rackVersion: defined?(Rack) ? Rack.release : nil,
17
17
  platform: RUBY_PLATFORM
18
18
  }.compact
@@ -44,6 +44,9 @@ module Logister
44
44
  end
45
45
 
46
46
  def trace_context(headers:, env:)
47
+ current = Logister::ContextStore.trace_context
48
+ return current.to_h.merge(trace: {traceId: current.trace_id, spanId: current.span_id, requestId: current.request_id}) if current
49
+
47
50
  traceparent = header_value(headers, "Traceparent")
48
51
  b3_trace_id = header_value(headers, "X-B3-Traceid")
49
52
  b3_span_id = header_value(headers, "X-B3-Spanid")
@@ -196,15 +199,9 @@ module Logister
196
199
  def parse_traceparent(traceparent)
197
200
  return [ nil, nil, nil ] if traceparent.to_s.empty?
198
201
 
199
- parts = traceparent.to_s.split("-")
200
- return [ nil, nil, nil ] unless parts.size == 4
201
-
202
- trace_id = parts[1].to_s
203
- span_id = parts[2].to_s
204
- flags = parts[3].to_s
205
- sampled = flags.end_with?("01")
206
-
207
- [ trace_id.presence, span_id.presence, sampled ]
202
+ parsed = Logister::TraceContext.parse(traceparent)
203
+ return [nil, nil, nil] unless parsed
204
+ [parsed[:trace_id], parsed[:parent_span_id], (parsed[:flags].to_i(16) & 1) == 1]
208
205
  rescue StandardError
209
206
  [ nil, nil, nil ]
210
207
  end
@@ -25,6 +25,14 @@ module Logister
25
25
  trim_collection!(breadcrumbs, max_breadcrumbs)
26
26
  end
27
27
 
28
+ def trace_context
29
+ request_scope[:trace_context]
30
+ end
31
+
32
+ def trace_context=(value)
33
+ request_scope[:trace_context] = value
34
+ end
35
+
28
36
  def breadcrumbs
29
37
  request_scope[:breadcrumbs].dup
30
38
  end
@@ -12,8 +12,14 @@ module Logister
12
12
 
13
13
  def call(env)
14
14
  Logister::ContextStore.reset_request_scope!
15
+ Logister::ContextStore.trace_context = Logister::TraceContext.from_headers(
16
+ traceparent: env["HTTP_TRACEPARENT"], request_id: env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
17
+ )
18
+ env["logister.trace_context"] = Logister::ContextStore.trace_context
15
19
  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
16
- @app.call(env)
20
+ status, headers, body = @app.call(env)
21
+ headers = headers.merge("x-request-id" => Logister::ContextStore.trace_context.request_id)
22
+ [status, headers, body]
17
23
  rescue StandardError => e
18
24
  request = ActionDispatch::Request.new(env)
19
25
  request_context = build_request_context(request, env, error: e, started_at: started_at)
@@ -146,7 +146,10 @@ module Logister
146
146
 
147
147
  started_at ||= Time.now.utc - (duration_ms.to_f / 1000.0)
148
148
  span_id ||= SecureRandom.hex(8)
149
- trace_id ||= span_id
149
+ active = Logister::ContextStore.trace_context
150
+ trace_id ||= active&.trace_id || SecureRandom.hex(16)
151
+ request_id ||= active&.request_id
152
+ parent_span_id ||= active&.span_id unless span_id == active&.span_id
150
153
 
151
154
  payload = build_payload(
152
155
  event_type: 'span',
@@ -298,6 +301,10 @@ module Logister
298
301
  @client.flush(timeout: timeout)
299
302
  end
300
303
 
304
+ def delivery_stats
305
+ @client.delivery_stats
306
+ end
307
+
301
308
  def shutdown
302
309
  @client.shutdown
303
310
  end
@@ -353,7 +360,7 @@ module Logister
353
360
  # Merge static config context last so caller-supplied keys are not
354
361
  # overwritten, then merge the static values. The static_context Hash
355
362
  # is frozen and reused — only the new outer Hash is allocated.
356
- context: @static_context.merge(context)
363
+ context: @static_context.merge(Logister::ContextStore.trace_context&.to_h || {}).merge(context)
357
364
  }
358
365
  end
359
366
 
@@ -8,8 +8,8 @@ module Logister
8
8
  def install!
9
9
  return if @installed
10
10
 
11
- ActiveSupport::Notifications.subscribe("process_action.action_controller") do |_name, _started, _finished, _id, payload|
12
- handle_process_action(payload)
11
+ ActiveSupport::Notifications.subscribe("process_action.action_controller") do |_name, started, finished, _id, payload|
12
+ handle_process_action(payload.merge(duration: (finished - started) * 1000.0, started_at: started))
13
13
  end
14
14
 
15
15
  ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, started, finished, _id, payload|
@@ -25,7 +25,7 @@ module Logister
25
25
  return if Logister.reporting_suppressed?
26
26
  return unless payload.is_a?(Hash)
27
27
 
28
- request_id = payload[:request_id].to_s.presence
28
+ request_id = Logister::ContextStore.trace_context&.request_id || payload[:request_id].to_s.presence || payload[:request]&.request_id
29
29
  return unless request_id
30
30
 
31
31
  Logister::ContextStore.store_request_summary(
@@ -65,13 +65,16 @@ module Logister
65
65
  return unless duration_ms&.positive?
66
66
 
67
67
  route = "#{payload[:controller]}##{payload[:action]}"
68
- started_at = Time.now.utc - (duration_ms / 1000.0)
68
+ started_at = payload[:started_at] || Time.now.utc - (duration_ms / 1000.0)
69
+ trace = Logister::ContextStore.trace_context || Logister::TraceContext.from_headers(request_id: request_id)
69
70
 
70
71
  Logister.report_span(
71
72
  name: route,
72
73
  kind: "server",
73
74
  status: payload[:status].to_i >= 500 ? "error" : "ok",
74
- trace_id: request_id,
75
+ trace_id: trace.trace_id,
76
+ span_id: trace.span_id,
77
+ parent_span_id: trace.parent_span_id,
75
78
  request_id: request_id,
76
79
  duration_ms: duration_ms,
77
80
  started_at: started_at,
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "uri"
5
+
6
+ module Logister
7
+ # Immutable request identity. Capturing this value lets a caller report a failed
8
+ # request later without borrowing another concurrent request's identity.
9
+ class TraceContext
10
+ attr_reader :trace_id, :span_id, :parent_span_id, :request_id, :flags
11
+
12
+ def initialize(trace_id: SecureRandom.hex(16), span_id: SecureRandom.hex(8), parent_span_id: nil, request_id: SecureRandom.uuid, flags: "01")
13
+ @trace_id, @span_id, @parent_span_id, @request_id, @flags = [trace_id, span_id, parent_span_id, request_id, flags].map { |value| value&.dup&.freeze }
14
+ freeze
15
+ end
16
+
17
+ def self.parse(value)
18
+ match = /\A00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})\z/.match(value.to_s)
19
+ return unless match && match[1] != "0" * 32 && match[2] != "0" * 16
20
+ {trace_id: match[1], parent_span_id: match[2], flags: match[3]}
21
+ end
22
+
23
+ def self.from_headers(traceparent: nil, request_id: nil)
24
+ request_id = nil unless request_id.is_a?(String) && request_id.match?(/\A[A-Za-z0-9._:-]{1,200}\z/)
25
+ new(**(parse(traceparent) || {}), request_id: request_id || SecureRandom.uuid)
26
+ end
27
+
28
+ def to_h
29
+ {trace_id: trace_id, span_id: span_id, parent_span_id: parent_span_id, request_id: request_id}.compact
30
+ end
31
+
32
+ def child
33
+ self.class.new(trace_id: trace_id, parent_span_id: span_id, request_id: request_id, flags: flags)
34
+ end
35
+
36
+ def traceparent = "00-#{trace_id}-#{span_id}-#{flags}"
37
+
38
+ # Apply only to the exact destination origin. HTTP clients must call this on
39
+ # each redirect hop, or disable redirects; never forward the returned headers.
40
+ def headers_for(url, allowed_origins:)
41
+ return {} unless self.class.parse(traceparent) && request_id.is_a?(String) && request_id.match?(/\A[A-Za-z0-9._:-]{1,200}\z/)
42
+ origin = self.class.origin(url)
43
+ return {} unless origin && allowed_origins.any? { |allowed| self.class.origin(allowed) == origin }
44
+ {"traceparent" => traceparent, "x-request-id" => request_id}
45
+ end
46
+
47
+ def self.origin(url)
48
+ uri = URI.parse(url.to_s)
49
+ return unless %w[http https].include?(uri.scheme) && uri.host && !uri.userinfo
50
+ [uri.scheme, uri.host.downcase, uri.port]
51
+ rescue URI::InvalidURIError
52
+ nil
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Logister
4
- VERSION = '0.4.1'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/logister.rb CHANGED
@@ -5,12 +5,23 @@ require_relative 'logister/client'
5
5
  require_relative 'logister/reporter'
6
6
  require_relative 'logister/context_helpers'
7
7
  require_relative 'logister/context_store'
8
+ require_relative 'logister/trace_context'
8
9
  require_relative 'logister/middleware'
9
10
  require_relative 'logister/sql_subscriber'
10
11
  require_relative 'logister/request_subscriber'
11
12
 
12
13
  module Logister
13
14
  class << self
15
+ def current_trace_context
16
+ ContextStore.trace_context
17
+ end
18
+
19
+ # The returned child is the handle for this outbound attempt. Supply its
20
+ # to_h as context when reporting an error after the request scope has ended.
21
+ def outbound_trace_context
22
+ (current_trace_context || TraceContext.new).child
23
+ end
24
+
14
25
  def configuration
15
26
  @configuration ||= Configuration.new
16
27
  end
@@ -64,6 +75,10 @@ module Logister
64
75
  reporter.flush(timeout: timeout)
65
76
  end
66
77
 
78
+ def delivery_stats
79
+ reporter.delivery_stats
80
+ end
81
+
67
82
  def shutdown
68
83
  reporter.shutdown
69
84
  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.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Logister
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-09-11 00:00:00.000000000 Z
10
+ date: 2026-09-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -109,6 +109,7 @@ files:
109
109
  - lib/logister/reporting_scope.rb
110
110
  - lib/logister/request_subscriber.rb
111
111
  - lib/logister/sql_subscriber.rb
112
+ - lib/logister/trace_context.rb
112
113
  - lib/logister/version.rb
113
114
  - logister-ruby.gemspec
114
115
  homepage: https://github.com/taimoorq/logister-ruby