chronos-ruby 0.1.0.pre.2

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +34 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/CONTRIBUTING.md +23 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +251 -0
  7. data/SECURITY.md +9 -0
  8. data/contracts/event-v1.schema.json +51 -0
  9. data/docs/adr/ADR-001-core-and-integrations.md +25 -0
  10. data/docs/adr/ADR-002-version-lines.md +25 -0
  11. data/docs/adr/ADR-003-net-http-legacy-transport.md +25 -0
  12. data/docs/adr/ADR-004-bounded-queue.md +25 -0
  13. data/docs/adr/ADR-005-sanitize-before-backlog.md +25 -0
  14. data/docs/adr/ADR-006-versioned-event-contract.md +25 -0
  15. data/docs/architecture.md +31 -0
  16. data/docs/compatibility.md +21 -0
  17. data/docs/configuration.md +40 -0
  18. data/docs/data-collected.md +20 -0
  19. data/docs/examples/plain-ruby.md +12 -0
  20. data/docs/modules/async-queue.md +17 -0
  21. data/docs/modules/configuration.md +9 -0
  22. data/docs/modules/notice-pipeline.md +20 -0
  23. data/docs/modules/transport.md +9 -0
  24. data/docs/performance.md +16 -0
  25. data/docs/privacy-lgpd.md +16 -0
  26. data/docs/troubleshooting.md +25 -0
  27. data/lib/chronos/adapters/net_http_transport.rb +132 -0
  28. data/lib/chronos/adapters.rb +10 -0
  29. data/lib/chronos/agent.rb +57 -0
  30. data/lib/chronos/application/capture_exception.rb +61 -0
  31. data/lib/chronos/application.rb +10 -0
  32. data/lib/chronos/configuration.rb +139 -0
  33. data/lib/chronos/core/backtrace_parser.rb +74 -0
  34. data/lib/chronos/core/exception_cause_collector.rb +63 -0
  35. data/lib/chronos/core/notice.rb +53 -0
  36. data/lib/chronos/core/notice_builder.rb +83 -0
  37. data/lib/chronos/core/payload_serializer.rb +165 -0
  38. data/lib/chronos/core/runtime_info.rb +42 -0
  39. data/lib/chronos/core.rb +10 -0
  40. data/lib/chronos/errors.rb +24 -0
  41. data/lib/chronos/internal/bounded_queue.rb +79 -0
  42. data/lib/chronos/internal/safe_logger.rb +55 -0
  43. data/lib/chronos/internal/worker_pool.rb +155 -0
  44. data/lib/chronos/internal.rb +10 -0
  45. data/lib/chronos/ports/transport.rb +45 -0
  46. data/lib/chronos/ports.rb +10 -0
  47. data/lib/chronos/ruby/version.rb +1 -0
  48. data/lib/chronos/ruby.rb +1 -0
  49. data/lib/chronos/version.rb +4 -0
  50. data/lib/chronos.rb +98 -0
  51. metadata +156 -0
@@ -0,0 +1,155 @@
1
+ module Chronos
2
+ module Internal
3
+ # Fixed-size, lazy worker pool that drains a BoundedQueue.
4
+ #
5
+ # @responsibility Start workers on first use, deliver events, flush, and shut down predictably.
6
+ # @motivation Keep serialization and network delivery outside the caller's critical path.
7
+ # @limits Version 0.1 does not retry failed deliveries or persist a backlog.
8
+ # @collaborators BoundedQueue, Transport, and SafeLogger.
9
+ # @thread_safety Internal state is synchronized and active delivery is counted.
10
+ # @compatibility Ruby 2.2.10 through Ruby 2.6; workers are recreated after fork.
11
+ # @example
12
+ # pool.enqueue(event)
13
+ # pool.flush(2.0)
14
+ # @errors Worker errors are diagnosed and contained inside the pool.
15
+ # @performance Creates a fixed number of threads only after the first accepted event.
16
+ class WorkerPool
17
+ POLL_INTERVAL = 0.05
18
+
19
+ def initialize(queue, transport, worker_count, logger = nil)
20
+ @queue = queue
21
+ @transport = transport
22
+ @worker_count = worker_count
23
+ @logger = logger || SafeLogger.new(nil)
24
+ @mutex = Mutex.new
25
+ @threads = []
26
+ @active = 0
27
+ @closed = false
28
+ @pid = Process.pid
29
+ end
30
+
31
+ def enqueue(event)
32
+ prepare_after_fork
33
+ return false if closed?
34
+
35
+ accepted = @queue.push(event)
36
+ ensure_started if accepted
37
+ accepted
38
+ end
39
+
40
+ def flush(timeout)
41
+ prepare_after_fork
42
+ ensure_started unless @queue.empty?
43
+ deadline = monotonic_time + timeout.to_f
44
+ loop do
45
+ return true if @queue.empty? && active_count.zero?
46
+ return false if monotonic_time >= deadline
47
+ sleep(POLL_INTERVAL)
48
+ end
49
+ end
50
+
51
+ def close(timeout)
52
+ already_closed = @mutex.synchronize do
53
+ was_closed = @closed
54
+ @closed = true
55
+ was_closed
56
+ end
57
+ return true if already_closed
58
+
59
+ flushed = flush_without_reopening(timeout)
60
+ @queue.close
61
+ join_workers(timeout)
62
+ @transport.close
63
+ flushed
64
+ rescue StandardError => error
65
+ @logger.warn("Chronos worker shutdown failed: #{error.class}")
66
+ false
67
+ end
68
+
69
+ def started?
70
+ @mutex.synchronize { !@threads.empty? }
71
+ end
72
+
73
+ private
74
+
75
+ def ensure_started
76
+ @mutex.synchronize do
77
+ return if @closed || !@threads.empty?
78
+ @worker_count.times { @threads << Thread.new { work_loop } }
79
+ end
80
+ end
81
+
82
+ def work_loop
83
+ loop do
84
+ event = @queue.pop(POLL_INTERVAL)
85
+ break if event.nil? && @queue.closed?
86
+ next unless event
87
+
88
+ increment_active
89
+ begin
90
+ @transport.send_event(event)
91
+ rescue StandardError => error
92
+ @logger.warn("Chronos worker contained #{error.class}")
93
+ ensure
94
+ decrement_active
95
+ end
96
+ end
97
+ rescue StandardError => error
98
+ @logger.warn("Chronos worker stopped after #{error.class}")
99
+ end
100
+
101
+ def prepare_after_fork
102
+ return if @pid == Process.pid
103
+
104
+ @mutex.synchronize do
105
+ @pid = Process.pid
106
+ @threads = []
107
+ @active = 0
108
+ end
109
+ end
110
+
111
+ def flush_without_reopening(timeout)
112
+ deadline = monotonic_time + timeout.to_f
113
+ loop do
114
+ return true if @queue.empty? && active_count.zero?
115
+ return false if monotonic_time >= deadline
116
+ sleep(POLL_INTERVAL)
117
+ end
118
+ end
119
+
120
+ def join_workers(timeout)
121
+ deadline = monotonic_time + timeout.to_f
122
+ threads = @mutex.synchronize { @threads.dup }
123
+ threads.each do |thread|
124
+ remaining = deadline - monotonic_time
125
+ thread.join(remaining) if remaining > 0
126
+ thread.kill if thread.alive?
127
+ end
128
+ end
129
+
130
+ def increment_active
131
+ @mutex.synchronize { @active += 1 }
132
+ end
133
+
134
+ def decrement_active
135
+ @mutex.synchronize { @active -= 1 }
136
+ end
137
+
138
+ def active_count
139
+ @mutex.synchronize { @active }
140
+ end
141
+
142
+ def closed?
143
+ @mutex.synchronize { @closed }
144
+ end
145
+
146
+ def monotonic_time
147
+ if Process.respond_to?(:clock_gettime) && defined?(Process::CLOCK_MONOTONIC)
148
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
149
+ else
150
+ Time.now.to_f
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,10 @@
1
+ module Chronos
2
+ # Private runtime infrastructure used by the public agent.
3
+ #
4
+ # @responsibility Host bounded concurrency and defensive diagnostics.
5
+ # @motivation Keep lifecycle mechanisms outside the domain.
6
+ # @limits Constants under Internal are not part of the stable public API.
7
+ # @thread_safety Mutable components synchronize their own state.
8
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
9
+ module Internal; end
10
+ end
@@ -0,0 +1,45 @@
1
+ module Chronos
2
+ module Ports
3
+ # Result returned by transport adapters instead of raising into the application.
4
+ #
5
+ # @responsibility Describe delivery outcome and retry classification.
6
+ # @motivation Keep HTTP implementation details outside the application layer.
7
+ # @limits It does not schedule retries; retry is outside version 0.1.
8
+ # @thread_safety Immutable after construction.
9
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
10
+ class TransportResult
11
+ attr_reader :status, :status_code, :retry_after, :error
12
+
13
+ def initialize(status, options = {})
14
+ @status = status
15
+ @status_code = options[:status_code]
16
+ @retry_after = options[:retry_after]
17
+ @error = options[:error]
18
+ freeze
19
+ end
20
+
21
+ def success?
22
+ status == :success
23
+ end
24
+
25
+ def retryable?
26
+ [:rate_limited, :server_error, :network_error].include?(status)
27
+ end
28
+ end
29
+
30
+ # Conceptual transport port implemented by delivery adapters.
31
+ #
32
+ # @responsibility Define send_event, send_batch, healthy?, and close behavior.
33
+ # @motivation Let capture code depend on a stable boundary instead of Net::HTTP.
34
+ # @limits Ruby does not enforce this interface; adapters are verified by contract tests.
35
+ # @thread_safety Implementations must document their own synchronization.
36
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
37
+ module Transport
38
+ REQUIRED_METHODS = [:send_event, :send_batch, :healthy?, :close].freeze
39
+
40
+ def self.compatible?(object)
41
+ REQUIRED_METHODS.all? { |method_name| object.respond_to?(method_name) }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ module Chronos
2
+ # Stable boundaries implemented by infrastructure adapters.
3
+ #
4
+ # @responsibility Define the behavior expected from external collaborators.
5
+ # @motivation Apply dependency inversion to delivery infrastructure.
6
+ # @limits Interfaces are verified by behavior because Ruby has no interface keyword.
7
+ # @thread_safety Each adapter documents its own guarantees.
8
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
9
+ module Ports; end
10
+ end
@@ -0,0 +1 @@
1
+ require "chronos/version"
@@ -0,0 +1 @@
1
+ require "chronos"
@@ -0,0 +1,4 @@
1
+ module Chronos
2
+ # Current version of the legacy Chronos Ruby agent.
3
+ VERSION = "0.1.0.pre.2".freeze
4
+ end
data/lib/chronos.rb ADDED
@@ -0,0 +1,98 @@
1
+ require "chronos/version"
2
+ require "chronos/errors"
3
+ require "chronos/configuration"
4
+ require "chronos/core"
5
+ require "chronos/application"
6
+ require "chronos/ports"
7
+ require "chronos/adapters"
8
+ require "chronos/internal"
9
+ require "chronos/core/notice"
10
+ require "chronos/core/backtrace_parser"
11
+ require "chronos/core/exception_cause_collector"
12
+ require "chronos/core/runtime_info"
13
+ require "chronos/core/notice_builder"
14
+ require "chronos/core/payload_serializer"
15
+ require "chronos/ports/transport"
16
+ require "chronos/internal/safe_logger"
17
+ require "chronos/internal/bounded_queue"
18
+ require "chronos/internal/worker_pool"
19
+ require "chronos/adapters/net_http_transport"
20
+ require "chronos/application/capture_exception"
21
+ require "chronos/agent"
22
+
23
+ # Framework-independent public facade for the Chronos Ruby agent.
24
+ #
25
+ # @responsibility Configure the agent and expose its small lifecycle API.
26
+ # @motivation Give applications a stable entry point while internals evolve.
27
+ # @limits Version 0.1 captures Ruby exceptions only; integrations arrive later.
28
+ # @collaborators Configuration and Agent.
29
+ # @thread_safety Agent replacement and lookup are protected by a mutex.
30
+ # @compatibility Ruby 2.2.10 through Ruby 2.6.
31
+ # @example
32
+ # Chronos.configure do |config|
33
+ # config.project_id = "project-id"
34
+ # config.project_key = "project-key"
35
+ # config.host = "https://chronos.example.com"
36
+ # end
37
+ # Chronos.notify(RuntimeError.new("failed"))
38
+ module Chronos
39
+ @mutex = Mutex.new
40
+ @agent = nil
41
+
42
+ class << self
43
+ def configure
44
+ configuration = Configuration.new
45
+ yield configuration if block_given?
46
+ agent = Agent.new(configuration.snapshot)
47
+ previous = nil
48
+ @mutex.synchronize do
49
+ previous = @agent
50
+ @agent = agent
51
+ end
52
+ previous.close if previous
53
+ configuration
54
+ end
55
+
56
+ def notify(exception, context = {})
57
+ agent = current_agent
58
+ agent ? agent.notify(exception, context) : false
59
+ rescue StandardError
60
+ false
61
+ end
62
+
63
+ def notify_sync(exception, context = {})
64
+ agent = current_agent
65
+ agent ? agent.notify_sync(exception, context) : false
66
+ rescue StandardError
67
+ false
68
+ end
69
+
70
+ def configured?
71
+ !current_agent.nil?
72
+ end
73
+
74
+ def flush(timeout = Agent::DEFAULT_FLUSH_TIMEOUT)
75
+ agent = current_agent
76
+ agent ? agent.flush(timeout) : true
77
+ rescue StandardError
78
+ false
79
+ end
80
+
81
+ def close(timeout = Agent::DEFAULT_FLUSH_TIMEOUT)
82
+ agent = @mutex.synchronize do
83
+ current = @agent
84
+ @agent = nil
85
+ current
86
+ end
87
+ agent ? agent.close(timeout) : true
88
+ rescue StandardError
89
+ false
90
+ end
91
+
92
+ private
93
+
94
+ def current_agent
95
+ @mutex.synchronize { @agent }
96
+ end
97
+ end
98
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chronos-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.2
5
+ platform: ruby
6
+ authors:
7
+ - Antonio Jefferson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-07-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.47.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.47.1
69
+ description: Base do cliente Chronos para excecoes, telemetria e observabilidade em
70
+ aplicacoes Ruby legadas.
71
+ email:
72
+ - antoniojeferson96@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - CHANGELOG.md
78
+ - CODE_OF_CONDUCT.md
79
+ - CONTRIBUTING.md
80
+ - LICENSE.txt
81
+ - README.md
82
+ - SECURITY.md
83
+ - contracts/event-v1.schema.json
84
+ - docs/adr/ADR-001-core-and-integrations.md
85
+ - docs/adr/ADR-002-version-lines.md
86
+ - docs/adr/ADR-003-net-http-legacy-transport.md
87
+ - docs/adr/ADR-004-bounded-queue.md
88
+ - docs/adr/ADR-005-sanitize-before-backlog.md
89
+ - docs/adr/ADR-006-versioned-event-contract.md
90
+ - docs/architecture.md
91
+ - docs/compatibility.md
92
+ - docs/configuration.md
93
+ - docs/data-collected.md
94
+ - docs/examples/plain-ruby.md
95
+ - docs/modules/async-queue.md
96
+ - docs/modules/configuration.md
97
+ - docs/modules/notice-pipeline.md
98
+ - docs/modules/transport.md
99
+ - docs/performance.md
100
+ - docs/privacy-lgpd.md
101
+ - docs/troubleshooting.md
102
+ - lib/chronos.rb
103
+ - lib/chronos/adapters.rb
104
+ - lib/chronos/adapters/net_http_transport.rb
105
+ - lib/chronos/agent.rb
106
+ - lib/chronos/application.rb
107
+ - lib/chronos/application/capture_exception.rb
108
+ - lib/chronos/configuration.rb
109
+ - lib/chronos/core.rb
110
+ - lib/chronos/core/backtrace_parser.rb
111
+ - lib/chronos/core/exception_cause_collector.rb
112
+ - lib/chronos/core/notice.rb
113
+ - lib/chronos/core/notice_builder.rb
114
+ - lib/chronos/core/payload_serializer.rb
115
+ - lib/chronos/core/runtime_info.rb
116
+ - lib/chronos/errors.rb
117
+ - lib/chronos/internal.rb
118
+ - lib/chronos/internal/bounded_queue.rb
119
+ - lib/chronos/internal/safe_logger.rb
120
+ - lib/chronos/internal/worker_pool.rb
121
+ - lib/chronos/ports.rb
122
+ - lib/chronos/ports/transport.rb
123
+ - lib/chronos/ruby.rb
124
+ - lib/chronos/ruby/version.rb
125
+ - lib/chronos/version.rb
126
+ homepage: https://github.com/antoniojefferson/chronos-ruby
127
+ licenses:
128
+ - MIT
129
+ metadata:
130
+ allowed_push_host: https://rubygems.org
131
+ homepage_uri: https://github.com/antoniojefferson/chronos-ruby
132
+ source_code_uri: https://github.com/antoniojefferson/chronos-ruby
133
+ changelog_uri: https://github.com/antoniojefferson/chronos-ruby/blob/main/CHANGELOG.md
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 2.2.10
143
+ - - "<"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.7'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">"
149
+ - !ruby/object:Gem::Version
150
+ version: 1.3.1
151
+ requirements: []
152
+ rubygems_version: 3.0.3
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Cliente Ruby para captura de eventos do Chronos
156
+ test_files: []