chronos-ruby 0.2.0.pre.1 → 0.4.0.pre.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/README.md +60 -19
  4. data/contracts/rack-context-v1.schema.json +43 -0
  5. data/docs/adr/ADR-005-sanitize-before-backlog.md +2 -2
  6. data/docs/adr/ADR-011-bounded-resilience-and-remote-control.md +27 -0
  7. data/docs/adr/ADR-012-rack-context-isolation.md +27 -0
  8. data/docs/architecture.md +15 -6
  9. data/docs/compatibility.md +6 -6
  10. data/docs/configuration.md +22 -0
  11. data/docs/data-collected.md +8 -2
  12. data/docs/examples/plain-ruby.md +6 -0
  13. data/docs/modules/async-queue.md +6 -2
  14. data/docs/modules/notice-pipeline.md +2 -1
  15. data/docs/modules/rack-context.md +59 -0
  16. data/docs/modules/remote-configuration.md +54 -0
  17. data/docs/modules/retry-backlog.md +60 -0
  18. data/docs/modules/transport.md +5 -3
  19. data/docs/performance.md +39 -2
  20. data/docs/privacy-lgpd.md +10 -2
  21. data/docs/troubleshooting.md +10 -2
  22. data/lib/chronos/adapters/net_http_transport.rb +21 -3
  23. data/lib/chronos/adapters/thread_local_context_store.rb +52 -0
  24. data/lib/chronos/agent.rb +68 -11
  25. data/lib/chronos/application/capture_exception.rb +20 -14
  26. data/lib/chronos/application/circuit_breaker.rb +70 -0
  27. data/lib/chronos/application/delivery_pipeline.rb +248 -0
  28. data/lib/chronos/application/remote_configuration.rb +173 -0
  29. data/lib/chronos/application/retry_policy.rb +57 -0
  30. data/lib/chronos/configuration.rb +128 -20
  31. data/lib/chronos/core/breadcrumb.rb +126 -0
  32. data/lib/chronos/core/payload_serializer.rb +4 -2
  33. data/lib/chronos/integrations/rack/middleware.rb +152 -0
  34. data/lib/chronos/integrations/rack.rb +12 -0
  35. data/lib/chronos/integrations.rb +10 -0
  36. data/lib/chronos/internal/bounded_queue.rb +1 -1
  37. data/lib/chronos/internal/memory_backlog.rb +65 -0
  38. data/lib/chronos/internal/worker_pool.rb +5 -6
  39. data/lib/chronos/ports/context_store.rb +22 -0
  40. data/lib/chronos/ports/transport.rb +4 -3
  41. data/lib/chronos/version.rb +1 -1
  42. data/lib/chronos.rb +26 -1
  43. metadata +18 -1
@@ -0,0 +1,126 @@
1
+ require "json"
2
+ require "time"
3
+
4
+ module Chronos
5
+ module Core
6
+ # Immutable, bounded diagnostic marker attached to an execution.
7
+ #
8
+ # @responsibility Normalize one breadcrumb without retaining raw application objects.
9
+ # @motivation Preserve useful events leading to an exception with predictable memory use.
10
+ # @limits It does not capture logs, SQL, bodies, or HTTP calls automatically.
11
+ # @collaborators SafeSerializer and BreadcrumbBuffer.
12
+ # @thread_safety Immutable after construction.
13
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
14
+ # @example
15
+ # breadcrumb.to_h #=> {"category"=>"custom", ...}
16
+ # @errors Unserializable metadata is replaced by SafeSerializer placeholders.
17
+ # @performance Metadata traversal has strict depth, node, item, and byte limits.
18
+ class Breadcrumb
19
+ CATEGORIES = %w(custom log request query external_http cache job).freeze
20
+
21
+ def initialize(attributes, clock = nil, max_bytes = 2048)
22
+ attributes = {} unless attributes.is_a?(Hash)
23
+ clock ||= proc { Time.now }
24
+ serializer = SafeSerializer.new(
25
+ :max_depth => 5, :max_keys => 20, :max_items => 20,
26
+ :max_string_bytes => 512, :max_nodes => 100
27
+ )
28
+ @data = serializer.call(build_data(attributes, clock))
29
+ @data = compact_data(@data, max_bytes) if JSON.generate(@data).bytesize > max_bytes
30
+ deep_freeze(@data)
31
+ freeze
32
+ end
33
+
34
+ def to_h
35
+ @data
36
+ end
37
+
38
+ private
39
+
40
+ def build_data(attributes, clock)
41
+ category = value(attributes, :category).to_s
42
+ category = "custom" unless CATEGORIES.include?(category)
43
+ {
44
+ "category" => category,
45
+ "message" => value(attributes, :message).to_s,
46
+ "metadata" => value(attributes, :metadata) || {},
47
+ "timestamp" => clock.call.utc.iso8601(6)
48
+ }
49
+ end
50
+
51
+ def compact_data(data, max_bytes)
52
+ message_limit = [max_bytes / 4, 32].max
53
+ compacted = {
54
+ "category" => data["category"],
55
+ "message" => SafeSerializer.new.call(data["message"], :max_string_bytes => message_limit),
56
+ "metadata" => {"_truncated" => true},
57
+ "timestamp" => data["timestamp"]
58
+ }
59
+ trim_compacted_message(compacted, max_bytes)
60
+ end
61
+
62
+ def trim_compacted_message(compacted, max_bytes)
63
+ while JSON.generate(compacted).bytesize > max_bytes && !compacted["message"].empty?
64
+ length = [compacted["message"].bytesize - 16, 0].max
65
+ compacted["message"] = SafeSerializer.new.call(
66
+ compacted["message"].byteslice(0, length).to_s,
67
+ :max_string_bytes => length
68
+ )
69
+ end
70
+ compacted
71
+ end
72
+
73
+ def value(attributes, key)
74
+ attributes.key?(key) ? attributes[key] : attributes[key.to_s]
75
+ end
76
+
77
+ def deep_freeze(value)
78
+ if value.is_a?(Hash)
79
+ value.each do |key, child|
80
+ deep_freeze(key)
81
+ deep_freeze(child)
82
+ end
83
+ elsif value.is_a?(Array)
84
+ value.each { |child| deep_freeze(child) }
85
+ end
86
+ value.freeze
87
+ end
88
+ end
89
+
90
+ # Fixed-size circular collection of execution breadcrumbs.
91
+ #
92
+ # @responsibility Retain only the newest bounded breadcrumbs for one execution.
93
+ # @motivation Prevent long requests or noisy instrumentation from growing memory indefinitely.
94
+ # @limits It is process memory only and does not collect events by itself.
95
+ # @collaborators Breadcrumb and Agent.
96
+ # @thread_safety Intended for one execution thread; snapshots are immutable values.
97
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
98
+ # @example
99
+ # buffer.add(:category => "custom", :message => "started")
100
+ # @errors Invalid attributes are normalized into a safe breadcrumb.
101
+ # @performance Add is constant time and storage never exceeds capacity.
102
+ class BreadcrumbBuffer
103
+ def initialize(capacity, max_bytes = 2048)
104
+ raise ArgumentError, "capacity must be a positive integer" unless capacity.is_a?(Integer) && capacity > 0
105
+
106
+ @capacity = capacity
107
+ @max_bytes = max_bytes
108
+ @items = []
109
+ end
110
+
111
+ def add(attributes)
112
+ @items.shift if @items.length >= @capacity
113
+ @items << Breadcrumb.new(attributes, nil, @max_bytes)
114
+ true
115
+ end
116
+
117
+ def to_a
118
+ @items.map(&:to_h)
119
+ end
120
+
121
+ def size
122
+ @items.size
123
+ end
124
+ end
125
+ end
126
+ end
@@ -43,13 +43,15 @@ module Chronos
43
43
  @clock = clock || proc { Time.now }
44
44
  @sanitizer = options[:sanitizer] || Sanitizer.new(config)
45
45
  @safe_serializer = options[:safe_serializer] || SafeSerializer.new
46
+ @max_payload_size = options[:max_payload_size] || proc { @config.max_payload_size }
46
47
  end
47
48
 
48
49
  def call(notice)
49
50
  envelope = @safe_serializer.call(@sanitizer.call(build_envelope(notice)))
50
51
  body = JSON.generate(envelope)
51
- body = JSON.generate(compact_envelope(envelope)) if body.bytesize > @config.max_payload_size
52
- raise Error, "event exceeds max_payload_size" if body.bytesize > @config.max_payload_size
52
+ limit = @max_payload_size.call
53
+ body = JSON.generate(compact_envelope(envelope)) if body.bytesize > limit
54
+ raise Error, "event exceeds max_payload_size" if body.bytesize > limit
53
55
 
54
56
  SerializedEvent.new(notice.event_id, body)
55
57
  end
@@ -0,0 +1,152 @@
1
+ require "securerandom"
2
+
3
+ module Chronos
4
+ module Integrations
5
+ module Rack
6
+ # Captures unhandled Rack exceptions while preserving Rack semantics.
7
+ #
8
+ # @responsibility Build bounded request context, notify Chronos, and re-raise the original exception.
9
+ # @motivation Provide automatic error capture without coupling the core to Rack.
10
+ # @limits It never reads rack.input, enumerates response bodies, or infers Rails routes.
11
+ # @collaborators Rack application and the Chronos facade or Agent.
12
+ # @thread_safety Shared instances keep no per-request mutable state.
13
+ # @compatibility Rack 1.x/2.x protocol; Ruby 2.2.10 through Ruby 2.6.
14
+ # @example
15
+ # use Chronos::Integrations::Rack::Middleware
16
+ # @errors Notification failures are contained; the application exception is always re-raised.
17
+ # @performance Adds bounded hash construction and monotonic-clock reads per request.
18
+ class Middleware
19
+ def initialize(app, options = {})
20
+ @app = app
21
+ @notifier = options[:notifier] || Chronos
22
+ @include_user_agent = options[:include_user_agent] || false
23
+ @clock = options[:clock] || proc { monotonic_time }
24
+ end
25
+
26
+ def call(env)
27
+ started_at = @clock.call
28
+ base = request_capture_context(env)
29
+ @notifier.with_context(base) do
30
+ add_request_breadcrumb("request started", base)
31
+ call_application(env, base, started_at)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def call_application(env, base, started_at)
38
+ response = @app.call(env)
39
+ status = response[0]
40
+ headers = response[1]
41
+ add_request_breadcrumb("request completed", dynamic_request_context(base, status, headers, started_at))
42
+ response
43
+ rescue Exception => error # rubocop:disable Lint/RescueException
44
+ context = dynamic_request_context(base, 500, nil, started_at)
45
+ notify_safely(error, context)
46
+ raise
47
+ end
48
+
49
+ def notify_safely(error, context)
50
+ @notifier.notify(error, context)
51
+ rescue StandardError
52
+ false
53
+ end
54
+
55
+ def request_capture_context(env)
56
+ request = request_values(env)
57
+ {
58
+ :context => {"request" => request, "trace_id" => trace_id(env)},
59
+ :parameters => parameters(env),
60
+ :user => hash_value(env["chronos.user"])
61
+ }
62
+ end
63
+
64
+ def request_values(env)
65
+ values = {
66
+ "method" => env["REQUEST_METHOD"].to_s,
67
+ "route" => normalized_route(env),
68
+ "request_id" => request_id(env),
69
+ "host" => (env["HTTP_HOST"] || env["SERVER_NAME"]).to_s,
70
+ "path" => env["PATH_INFO"].to_s,
71
+ "controller" => controller_action(env, "controller"),
72
+ "action" => controller_action(env, "action")
73
+ }
74
+ values["user_agent"] = env["HTTP_USER_AGENT"].to_s if @include_user_agent
75
+ values
76
+ end
77
+
78
+ def dynamic_request_context(base, status, headers, started_at)
79
+ request = base[:context]["request"].merge(
80
+ "status" => status.to_i,
81
+ "duration_ms" => ((@clock.call - started_at) * 1000.0).round(3),
82
+ "response_size" => response_size(headers)
83
+ )
84
+ {:context => base[:context].merge("request" => request)}
85
+ end
86
+
87
+ def parameters(env)
88
+ result = {}
89
+ [env["rack.request.query_hash"], env["action_dispatch.request.query_parameters"],
90
+ env["action_dispatch.request.path_parameters"], env["chronos.parameters"]].each do |candidate|
91
+ result.merge!(candidate) if candidate.is_a?(Hash)
92
+ end
93
+ result
94
+ rescue StandardError
95
+ {}
96
+ end
97
+
98
+ def normalized_route(env)
99
+ explicit = env["chronos.route"] || env["action_dispatch.route_uri_pattern"]
100
+ return explicit.to_s unless explicit.to_s.empty?
101
+
102
+ env["PATH_INFO"].to_s.split("/").map { |part| dynamic_segment?(part) ? ":id" : part }.join("/")
103
+ end
104
+
105
+ def dynamic_segment?(segment)
106
+ segment =~ /\A\d+\z/ || segment =~ /\A[0-9a-f]{8}-[0-9a-f-]{27,}\z/i
107
+ end
108
+
109
+ def controller_action(env, key)
110
+ explicit = env["chronos.#{key}"]
111
+ paths = env["action_dispatch.request.path_parameters"]
112
+ explicit || (paths[key] if paths.is_a?(Hash)) || (paths[key.to_sym] if paths.is_a?(Hash))
113
+ end
114
+
115
+ def request_id(env)
116
+ env["chronos.request_id"] || env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
117
+ end
118
+
119
+ def trace_id(env)
120
+ env["chronos.trace_id"] || SecureRandom.uuid
121
+ end
122
+
123
+ def response_size(headers)
124
+ return nil unless headers.respond_to?(:each)
125
+
126
+ pair = headers.find { |key, _value| key.to_s.casecmp("content-length").zero? }
127
+ pair ? pair[1].to_i : nil
128
+ rescue StandardError
129
+ nil
130
+ end
131
+
132
+ def add_request_breadcrumb(message, context)
133
+ request = context[:context]["request"]
134
+ @notifier.add_breadcrumb(
135
+ :category => "request", :message => message,
136
+ :metadata => {"method" => request["method"], "route" => request["route"], "status" => request["status"]}
137
+ )
138
+ end
139
+
140
+ def hash_value(value)
141
+ value.is_a?(Hash) ? value : {}
142
+ end
143
+
144
+ def monotonic_time
145
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
146
+ rescue StandardError
147
+ Time.now.to_f
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,12 @@
1
+ module Chronos
2
+ module Integrations
3
+ # Rack protocol integrations that do not require Rack at gem load time.
4
+ #
5
+ # @responsibility Namespace Rack-compatible middleware.
6
+ # @motivation Permit optional use without adding a runtime Rack dependency.
7
+ # @limits It does not provide Rails-specific route discovery.
8
+ # @thread_safety Middleware instances may be shared by concurrent Rack threads.
9
+ # @compatibility Rack 1.x and 2.x protocol shapes on Ruby 2.2.10 through Ruby 2.6.
10
+ module Rack; end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Chronos
2
+ # Optional framework entry points kept outside the framework-independent core.
3
+ #
4
+ # @responsibility Namespace integration adapters such as Rack middleware.
5
+ # @motivation Keep framework loading optional for plain Ruby applications.
6
+ # @limits Integrations may depend only on documented public agent behavior.
7
+ # @thread_safety Each integration documents its own guarantees.
8
+ # @compatibility Version 0.4 supports Rack protocol behavior on Ruby 2.2.10 through 2.6.
9
+ module Integrations; end
10
+ end
@@ -4,7 +4,7 @@ module Chronos
4
4
  #
5
5
  # @responsibility Accept events up to a limit and count accepted and dropped items.
6
6
  # @motivation Prevent telemetry bursts from growing application memory indefinitely.
7
- # @limits Version 0.2 drops the newest item when full and does not persist to disk.
7
+ # @limits Version 0.3 drops the newest item when full and does not persist to disk.
8
8
  # @collaborators WorkerPool.
9
9
  # @thread_safety Mutex and condition variable protect all mutable state.
10
10
  # @compatibility Ruby 2.2.10 through Ruby 2.6 and fork-aware callers.
@@ -0,0 +1,65 @@
1
+ module Chronos
2
+ module Internal
3
+ # Fixed-capacity retry storage for sanitized serialized events.
4
+ #
5
+ # @responsibility Retain failed deliveries in memory without unbounded growth.
6
+ # @motivation Allow later recovery while preserving the host application's memory limit.
7
+ # @limits It never writes to disk and accepts only SerializedEvent instances.
8
+ # @collaborators DeliveryPipeline and SerializedEvent.
9
+ # @thread_safety A mutex protects storage and counters.
10
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
11
+ # @example
12
+ # backlog.push(serialized_event)
13
+ # @errors Invalid event types raise before entering storage.
14
+ # @performance Push and shift are bounded by the configured capacity.
15
+ class MemoryBacklog
16
+ attr_reader :capacity
17
+
18
+ def initialize(capacity)
19
+ unless capacity.is_a?(Integer) && capacity >= 0
20
+ raise ArgumentError, "capacity must be a non-negative integer"
21
+ end
22
+
23
+ @capacity = capacity
24
+ @items = []
25
+ @accepted = 0
26
+ @dropped = 0
27
+ @mutex = Mutex.new
28
+ end
29
+
30
+ def push(event)
31
+ unless event.is_a?(Core::SerializedEvent)
32
+ raise ArgumentError, "backlog accepts only sanitized serialized events"
33
+ end
34
+
35
+ @mutex.synchronize do
36
+ if @items.length >= capacity
37
+ @dropped += 1
38
+ return false
39
+ end
40
+ @items << event
41
+ @accepted += 1
42
+ true
43
+ end
44
+ end
45
+
46
+ def shift
47
+ @mutex.synchronize { @items.shift }
48
+ end
49
+
50
+ def size
51
+ @mutex.synchronize { @items.size }
52
+ end
53
+
54
+ def empty?
55
+ size.zero?
56
+ end
57
+
58
+ def stats
59
+ @mutex.synchronize do
60
+ {:size => @items.size, :capacity => capacity, :accepted => @accepted, :dropped => @dropped}
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -4,8 +4,8 @@ module Chronos
4
4
  #
5
5
  # @responsibility Start workers on first use, deliver events, flush, and shut down predictably.
6
6
  # @motivation Keep serialization and network delivery outside the caller's critical path.
7
- # @limits Version 0.2 does not retry failed deliveries or persist a backlog.
8
- # @collaborators BoundedQueue, Transport, and SafeLogger.
7
+ # @limits Retry policy belongs to the delivery collaborator.
8
+ # @collaborators BoundedQueue, DeliveryPipeline, and SafeLogger.
9
9
  # @thread_safety Internal state is synchronized and active delivery is counted.
10
10
  # @compatibility Ruby 2.2.10 through Ruby 2.6; workers are recreated after fork.
11
11
  # @example
@@ -16,9 +16,9 @@ module Chronos
16
16
  class WorkerPool
17
17
  POLL_INTERVAL = 0.05
18
18
 
19
- def initialize(queue, transport, worker_count, logger = nil)
19
+ def initialize(queue, delivery, worker_count, logger = nil)
20
20
  @queue = queue
21
- @transport = transport
21
+ @delivery = delivery
22
22
  @worker_count = worker_count
23
23
  @logger = logger || SafeLogger.new(nil)
24
24
  @mutex = Mutex.new
@@ -59,7 +59,6 @@ module Chronos
59
59
  flushed = flush_without_reopening(timeout)
60
60
  @queue.close
61
61
  join_workers(timeout)
62
- @transport.close
63
62
  flushed
64
63
  rescue StandardError => error
65
64
  @logger.warn("Chronos worker shutdown failed: #{error.class}")
@@ -87,7 +86,7 @@ module Chronos
87
86
 
88
87
  increment_active
89
88
  begin
90
- @transport.send_event(event)
89
+ @delivery.send_event(event)
91
90
  rescue StandardError => error
92
91
  @logger.warn("Chronos worker contained #{error.class}")
93
92
  ensure
@@ -0,0 +1,22 @@
1
+ module Chronos
2
+ module Ports
3
+ # Conceptual storage port for execution-scoped context.
4
+ #
5
+ # @responsibility Define get, set, clear, and scoped context behavior.
6
+ # @motivation Let integrations isolate request state without depending on a thread implementation.
7
+ # @limits The port does not prescribe thread, fiber, or distributed propagation semantics.
8
+ # @collaborators Agent and context-store adapters.
9
+ # @thread_safety Implementations must isolate concurrent executions.
10
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
11
+ # @example
12
+ # Chronos::Ports::ContextStore.compatible?(store) #=> true
13
+ # @errors Compatibility checks never invoke application methods.
14
+ module ContextStore
15
+ REQUIRED_METHODS = [:get, :set, :clear, :with_context].freeze
16
+
17
+ def self.compatible?(object)
18
+ REQUIRED_METHODS.all? { |method_name| object.respond_to?(method_name) }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,17 +4,18 @@ module Chronos
4
4
  #
5
5
  # @responsibility Describe delivery outcome and retry classification.
6
6
  # @motivation Keep HTTP implementation details outside the application layer.
7
- # @limits It does not schedule retries; retry is outside version 0.2.
7
+ # @limits It classifies outcomes but does not schedule retries.
8
8
  # @thread_safety Immutable after construction.
9
9
  # @compatibility Ruby 2.2.10 through Ruby 2.6.
10
10
  class TransportResult
11
- attr_reader :status, :status_code, :retry_after, :error
11
+ attr_reader :status, :status_code, :retry_after, :error, :remote_configuration
12
12
 
13
13
  def initialize(status, options = {})
14
14
  @status = status
15
15
  @status_code = options[:status_code]
16
16
  @retry_after = options[:retry_after]
17
17
  @error = options[:error]
18
+ @remote_configuration = options[:remote_configuration]
18
19
  freeze
19
20
  end
20
21
 
@@ -23,7 +24,7 @@ module Chronos
23
24
  end
24
25
 
25
26
  def retryable?
26
- [:rate_limited, :server_error, :network_error].include?(status)
27
+ [:request_timeout, :rate_limited, :server_error, :network_error].include?(status)
27
28
  end
28
29
  end
29
30
 
@@ -1,4 +1,4 @@
1
1
  module Chronos
2
2
  # Current version of the legacy Chronos Ruby agent.
3
- VERSION = "0.2.0.pre.1".freeze
3
+ VERSION = "0.4.0.pre.1".freeze
4
4
  end
data/lib/chronos.rb CHANGED
@@ -16,18 +16,29 @@ require "chronos/core/sanitizer"
16
16
  require "chronos/core/safe_serializer"
17
17
  require "chronos/core/payload_serializer"
18
18
  require "chronos/ports/transport"
19
+ require "chronos/ports/context_store"
19
20
  require "chronos/internal/safe_logger"
20
21
  require "chronos/internal/bounded_queue"
22
+ require "chronos/internal/memory_backlog"
21
23
  require "chronos/internal/worker_pool"
22
24
  require "chronos/adapters/net_http_transport"
25
+ require "chronos/adapters/thread_local_context_store"
26
+ require "chronos/core/breadcrumb"
27
+ require "chronos/application/retry_policy"
28
+ require "chronos/application/circuit_breaker"
29
+ require "chronos/application/remote_configuration"
30
+ require "chronos/application/delivery_pipeline"
23
31
  require "chronos/application/capture_exception"
24
32
  require "chronos/agent"
33
+ require "chronos/integrations"
34
+ require "chronos/integrations/rack"
35
+ require "chronos/integrations/rack/middleware"
25
36
 
26
37
  # Framework-independent public facade for the Chronos Ruby agent.
27
38
  #
28
39
  # @responsibility Configure the agent and expose its small lifecycle API.
29
40
  # @motivation Give applications a stable entry point while internals evolve.
30
- # @limits Version 0.2 captures Ruby exceptions manually; integrations arrive later.
41
+ # @limits Version 0.4 captures Rack failures but does not install middleware automatically.
31
42
  # @collaborators Configuration and Agent.
32
43
  # @thread_safety Agent replacement and lookup are protected by a mutex.
33
44
  # @compatibility Ruby 2.2.10 through Ruby 2.6.
@@ -70,6 +81,20 @@ module Chronos
70
81
  false
71
82
  end
72
83
 
84
+ def with_context(context = {})
85
+ agent = current_agent
86
+ return yield unless agent
87
+
88
+ agent.with_context(context) { yield }
89
+ end
90
+
91
+ def add_breadcrumb(attributes = {})
92
+ agent = current_agent
93
+ agent ? agent.add_breadcrumb(attributes) : false
94
+ rescue StandardError
95
+ false
96
+ end
97
+
73
98
  def configured?
74
99
  !current_agent.nil?
75
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chronos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.pre.1
4
+ version: 0.4.0.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antonio Jefferson
@@ -87,12 +87,15 @@ files:
87
87
  - README.md
88
88
  - SECURITY.md
89
89
  - contracts/event-v1.schema.json
90
+ - contracts/rack-context-v1.schema.json
90
91
  - docs/adr/ADR-001-core-and-integrations.md
91
92
  - docs/adr/ADR-002-version-lines.md
92
93
  - docs/adr/ADR-003-net-http-legacy-transport.md
93
94
  - docs/adr/ADR-004-bounded-queue.md
94
95
  - docs/adr/ADR-005-sanitize-before-backlog.md
95
96
  - docs/adr/ADR-006-versioned-event-contract.md
97
+ - docs/adr/ADR-011-bounded-resilience-and-remote-control.md
98
+ - docs/adr/ADR-012-rack-context-isolation.md
96
99
  - docs/architecture.md
97
100
  - docs/compatibility.md
98
101
  - docs/configuration.md
@@ -101,6 +104,9 @@ files:
101
104
  - docs/modules/async-queue.md
102
105
  - docs/modules/configuration.md
103
106
  - docs/modules/notice-pipeline.md
107
+ - docs/modules/rack-context.md
108
+ - docs/modules/remote-configuration.md
109
+ - docs/modules/retry-backlog.md
104
110
  - docs/modules/sanitization.md
105
111
  - docs/modules/serialization.md
106
112
  - docs/modules/transport.md
@@ -110,12 +116,18 @@ files:
110
116
  - lib/chronos.rb
111
117
  - lib/chronos/adapters.rb
112
118
  - lib/chronos/adapters/net_http_transport.rb
119
+ - lib/chronos/adapters/thread_local_context_store.rb
113
120
  - lib/chronos/agent.rb
114
121
  - lib/chronos/application.rb
115
122
  - lib/chronos/application/capture_exception.rb
123
+ - lib/chronos/application/circuit_breaker.rb
124
+ - lib/chronos/application/delivery_pipeline.rb
125
+ - lib/chronos/application/remote_configuration.rb
126
+ - lib/chronos/application/retry_policy.rb
116
127
  - lib/chronos/configuration.rb
117
128
  - lib/chronos/core.rb
118
129
  - lib/chronos/core/backtrace_parser.rb
130
+ - lib/chronos/core/breadcrumb.rb
119
131
  - lib/chronos/core/exception_cause_collector.rb
120
132
  - lib/chronos/core/notice.rb
121
133
  - lib/chronos/core/notice_builder.rb
@@ -125,11 +137,16 @@ files:
125
137
  - lib/chronos/core/sanitizer.rb
126
138
  - lib/chronos/core/sensitive_value_filter.rb
127
139
  - lib/chronos/errors.rb
140
+ - lib/chronos/integrations.rb
141
+ - lib/chronos/integrations/rack.rb
142
+ - lib/chronos/integrations/rack/middleware.rb
128
143
  - lib/chronos/internal.rb
129
144
  - lib/chronos/internal/bounded_queue.rb
145
+ - lib/chronos/internal/memory_backlog.rb
130
146
  - lib/chronos/internal/safe_logger.rb
131
147
  - lib/chronos/internal/worker_pool.rb
132
148
  - lib/chronos/ports.rb
149
+ - lib/chronos/ports/context_store.rb
133
150
  - lib/chronos/ports/transport.rb
134
151
  - lib/chronos/ruby.rb
135
152
  - lib/chronos/ruby/version.rb