chronos-ruby 0.7.0.pre.1 → 0.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -0
- data/README.md +55 -9
- data/contracts/apm-batch-v1.schema.json +1 -1
- data/contracts/dependencies-v1.schema.json +38 -0
- data/contracts/deploy-v1.schema.json +26 -0
- data/contracts/event-v1.schema.json +15 -1
- data/docs/adr/ADR-016-explicit-observability-integrations.md +25 -0
- data/docs/adr/ADR-017-deploy-events-and-correlation.md +25 -0
- data/docs/architecture.md +12 -1
- data/docs/compatibility.md +4 -0
- data/docs/configuration.md +24 -3
- data/docs/data-collected.md +11 -4
- data/docs/examples/plain-ruby.md +13 -1
- data/docs/modules/apm-aggregation.md +2 -2
- data/docs/modules/cache-observability.md +16 -0
- data/docs/modules/configuration.md +4 -0
- data/docs/modules/dependencies.md +18 -0
- data/docs/modules/deploy-tracking.md +86 -0
- data/docs/modules/external-http.md +27 -0
- data/docs/modules/rails-legacy.md +1 -1
- data/docs/modules/sidekiq-legacy.md +1 -1
- data/docs/modules/telemetry-events.md +4 -2
- data/docs/performance.md +30 -1
- data/docs/privacy-lgpd.md +20 -2
- data/docs/troubleshooting.md +25 -1
- data/lib/chronos/agent.rb +43 -0
- data/lib/chronos/application/apm_aggregator.rb +7 -10
- data/lib/chronos/application/apm_error_classifier.rb +27 -0
- data/lib/chronos/application/capture_telemetry.rb +18 -1
- data/lib/chronos/application/dependency_reporter.rb +140 -0
- data/lib/chronos/application/remote_configuration.rb +3 -1
- data/lib/chronos/capistrano.rb +4 -0
- data/lib/chronos/configuration/apm_validation.rb +28 -1
- data/lib/chronos/configuration.rb +25 -3
- data/lib/chronos/core/cache_normalizer.rb +99 -0
- data/lib/chronos/core/correlation_context.rb +65 -0
- data/lib/chronos/core/deploy_normalizer.rb +102 -0
- data/lib/chronos/core/payload_serializer.rb +4 -1
- data/lib/chronos/core/telemetry_event.rb +31 -4
- data/lib/chronos/integrations/capistrano.rb +61 -0
- data/lib/chronos/integrations/net_http.rb +165 -0
- data/lib/chronos/net_http.rb +4 -0
- data/lib/chronos/observability_facade.rb +50 -0
- data/lib/chronos/rails/notifications_subscriber.rb +6 -5
- data/lib/chronos/version.rb +1 -1
- data/lib/chronos.rb +8 -0
- metadata +19 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Integrations
|
|
3
|
+
# Optional per-instance Net::HTTP instrumentation without a global monkey patch.
|
|
4
|
+
#
|
|
5
|
+
# @responsibility Install one idempotent request wrapper on an explicit HTTP connection.
|
|
6
|
+
# @motivation Legacy Net::HTTP has no middleware callback but must remain safe and optional.
|
|
7
|
+
# @limits Class convenience methods and uninstrumented instances are deliberately untouched.
|
|
8
|
+
# @collaborators Net::HTTP-compatible connection, request objects, and Chronos notifier.
|
|
9
|
+
# @thread_safety Installation is synchronized per object; request state is call-local.
|
|
10
|
+
# @compatibility Net::HTTP on Ruby 2.2.10 through Ruby 2.6.
|
|
11
|
+
# @example
|
|
12
|
+
# Chronos::Integrations::NetHttp.install(Net::HTTP.new("api.example.com"))
|
|
13
|
+
# @errors Instrumentation errors are contained; original HTTP errors are re-raised unchanged.
|
|
14
|
+
# @performance Adds bounded hashes and clock reads; never reads request/response bodies.
|
|
15
|
+
module NetHttp
|
|
16
|
+
INSTALLED_KEY = :@__chronos_net_http_installed
|
|
17
|
+
OPTIONS_KEY = :@__chronos_net_http_options
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def install(connection, options = {})
|
|
21
|
+
notifier = options[:notifier] || Chronos
|
|
22
|
+
return false unless enabled?(notifier)
|
|
23
|
+
return false unless connection.respond_to?(:request) && connection.respond_to?(:address)
|
|
24
|
+
|
|
25
|
+
mutex_for(connection).synchronize do
|
|
26
|
+
return false if connection.instance_variable_get(INSTALLED_KEY)
|
|
27
|
+
|
|
28
|
+
connection.instance_variable_set(OPTIONS_KEY, instrumentation_options(notifier, options))
|
|
29
|
+
connection.singleton_class.send(:prepend, InstrumentedRequest)
|
|
30
|
+
connection.instance_variable_set(INSTALLED_KEY, true)
|
|
31
|
+
end
|
|
32
|
+
true
|
|
33
|
+
rescue StandardError
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def enabled?(notifier)
|
|
40
|
+
return true unless notifier.respond_to?(:external_http_integration_options)
|
|
41
|
+
|
|
42
|
+
notifier.external_http_integration_options[:enabled] == true
|
|
43
|
+
rescue StandardError
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def instrumentation_options(notifier, options)
|
|
48
|
+
configured = if notifier.respond_to?(:external_http_integration_options)
|
|
49
|
+
notifier.external_http_integration_options
|
|
50
|
+
else
|
|
51
|
+
{}
|
|
52
|
+
end
|
|
53
|
+
{
|
|
54
|
+
:notifier => notifier,
|
|
55
|
+
:clock => options[:clock] || proc { monotonic_time },
|
|
56
|
+
:trace_headers => configured.fetch(:trace_headers, true)
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def mutex_for(connection)
|
|
61
|
+
key = :@__chronos_net_http_mutex
|
|
62
|
+
connection.instance_variable_get(key) || connection.instance_variable_set(key, Mutex.new)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def monotonic_time
|
|
66
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
67
|
+
rescue StandardError
|
|
68
|
+
Time.now.to_f
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Request wrapper prepended only to an explicitly selected HTTP object.
|
|
73
|
+
#
|
|
74
|
+
# @responsibility Inject bounded trace headers and record outcome/timing around `request`.
|
|
75
|
+
# @motivation Preserve the native API, streaming block, and exception semantics.
|
|
76
|
+
# @limits It never reads URL path/query, Authorization, request body, headers, or response body.
|
|
77
|
+
# @collaborators NetHttp installation options and Chronos record_event.
|
|
78
|
+
# @thread_safety All request observations use local variables.
|
|
79
|
+
# @compatibility Net::HTTP#request signature on Ruby 2.2.10 through Ruby 2.6.
|
|
80
|
+
# @example
|
|
81
|
+
# connection.request(Net::HTTP::Get.new("/health"))
|
|
82
|
+
# @errors Original StandardError values are recorded by class and re-raised unchanged.
|
|
83
|
+
# @performance Two clock reads and one asynchronous telemetry observation per call.
|
|
84
|
+
module InstrumentedRequest
|
|
85
|
+
def request(request, body = nil, &block)
|
|
86
|
+
options = instance_variable_get(NetHttp::OPTIONS_KEY)
|
|
87
|
+
started_at = options[:clock].call
|
|
88
|
+
inject_trace_headers(request, options) if options[:trace_headers]
|
|
89
|
+
response = super(request, body, &block)
|
|
90
|
+
record_external_http(options, request, started_at, response, nil)
|
|
91
|
+
response
|
|
92
|
+
rescue StandardError => error
|
|
93
|
+
record_external_http(options, request, started_at, nil, error) if options && started_at
|
|
94
|
+
raise
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def inject_trace_headers(request, options)
|
|
100
|
+
return unless request.respond_to?(:[]) && request.respond_to?(:[]=)
|
|
101
|
+
|
|
102
|
+
context = if options[:notifier].respond_to?(:propagation_context)
|
|
103
|
+
options[:notifier].propagation_context
|
|
104
|
+
else
|
|
105
|
+
{}
|
|
106
|
+
end
|
|
107
|
+
set_header(request, "X-Chronos-Trace-ID", context["trace_id"] || context[:trace_id])
|
|
108
|
+
set_header(request, "X-Chronos-Request-ID", context["request_id"] || context[:request_id])
|
|
109
|
+
rescue StandardError
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def set_header(request, name, value)
|
|
114
|
+
request[name] = value.to_s if request[name].to_s.empty? && !value.to_s.empty?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def record_external_http(options, request, started_at, response, error)
|
|
118
|
+
payload = {
|
|
119
|
+
"host" => sanitized_external_host,
|
|
120
|
+
"method" => request.respond_to?(:method) ? request.method.to_s.upcase : "",
|
|
121
|
+
"status" => response_status(response),
|
|
122
|
+
"duration_ms" => ((options[:clock].call - started_at) * 1000.0).round(3),
|
|
123
|
+
"timeout" => timeout_error?(error),
|
|
124
|
+
"connection_error" => connection_error?(error),
|
|
125
|
+
"error_class" => error ? error.class.name.to_s : ""
|
|
126
|
+
}
|
|
127
|
+
payload.delete_if { |_key, value| value.nil? || value == "" }
|
|
128
|
+
options[:notifier].record_event("external_http", payload)
|
|
129
|
+
rescue StandardError
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def response_status(response)
|
|
134
|
+
response.code.to_i if response && response.respond_to?(:code)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def sanitized_external_host
|
|
138
|
+
host = respond_to?(:address) ? address.to_s.downcase.sub(/\.\z/, "") : ""
|
|
139
|
+
host = host.scrub("?") if host.respond_to?(:scrub)
|
|
140
|
+
host.bytesize > 253 ? host.byteslice(0, 253) : host
|
|
141
|
+
rescue StandardError
|
|
142
|
+
""
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def timeout_error?(error)
|
|
146
|
+
return false unless error
|
|
147
|
+
|
|
148
|
+
error.is_a?(Timeout::Error) || !error.class.name.to_s.match(/Timeout/).nil?
|
|
149
|
+
rescue StandardError
|
|
150
|
+
false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def connection_error?(error)
|
|
154
|
+
return false unless error
|
|
155
|
+
return false if timeout_error?(error)
|
|
156
|
+
|
|
157
|
+
error.is_a?(SystemCallError) || error.is_a?(IOError) ||
|
|
158
|
+
!error.class.name.to_s.match(/SocketError|OpenSSL::SSL::SSLError/).nil?
|
|
159
|
+
rescue StandardError
|
|
160
|
+
false
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
# Public entry points for optional observability and deployment integrations.
|
|
3
|
+
#
|
|
4
|
+
# @responsibility Delegate dependency, cache, outbound HTTP, and deploy calls to the agent.
|
|
5
|
+
# @motivation Keep the main public facade small while preserving one stable Chronos namespace.
|
|
6
|
+
# @limits It installs only explicitly supplied Net::HTTP connections and never enables features.
|
|
7
|
+
# @collaborators Chronos::Agent and Chronos::Integrations::NetHttp.
|
|
8
|
+
# @thread_safety Agent access uses the facade's synchronized current_agent lookup.
|
|
9
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
10
|
+
# @example
|
|
11
|
+
# Chronos.instrument_net_http(Net::HTTP.new("api.example.com"))
|
|
12
|
+
# @errors Integration failures return false or safe disabled options.
|
|
13
|
+
# @performance Delegation is constant-time; dependency collection remains at-most-once.
|
|
14
|
+
module ObservabilityFacade
|
|
15
|
+
def report_dependencies
|
|
16
|
+
agent = current_agent
|
|
17
|
+
agent ? agent.report_dependencies : false
|
|
18
|
+
rescue StandardError
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def notify_deploy(attributes = {}, timeout = Agent::DEFAULT_FLUSH_TIMEOUT)
|
|
23
|
+
agent = current_agent
|
|
24
|
+
agent ? agent.notify_deploy(attributes, timeout) : false
|
|
25
|
+
rescue StandardError
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def external_http_integration_options
|
|
30
|
+
agent = current_agent
|
|
31
|
+
agent ? agent.external_http_integration_options : {:enabled => false}
|
|
32
|
+
rescue StandardError
|
|
33
|
+
{:enabled => false}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def cache_integration_options
|
|
37
|
+
agent = current_agent
|
|
38
|
+
agent ? agent.cache_integration_options : {:project_id => "", :key_mode => :none}
|
|
39
|
+
rescue StandardError
|
|
40
|
+
{:project_id => "", :key_mode => :none}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def instrument_net_http(connection, options = {})
|
|
44
|
+
require "chronos/net_http"
|
|
45
|
+
Integrations::NetHttp.install(connection, options.merge(:notifier => self))
|
|
46
|
+
rescue StandardError
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -4,7 +4,7 @@ module Chronos
|
|
|
4
4
|
#
|
|
5
5
|
# @responsibility Subscribe once and normalize controller, view, SQL, mailer, job, and cache events.
|
|
6
6
|
# @motivation Support Rails 4.2 and 5.2 through feature detection and public notification APIs.
|
|
7
|
-
# @limits It never sends SQL text,
|
|
7
|
+
# @limits It never sends SQL text, binds, raw cache keys/values, mail bodies, or job arguments.
|
|
8
8
|
# @collaborators ActiveSupport::Notifications and the Chronos facade.
|
|
9
9
|
# @thread_safety Subscription registry is mutex-protected; callbacks own their state.
|
|
10
10
|
# @compatibility ActiveSupport notification argument shapes from Rails 4.2 through 5.2.
|
|
@@ -30,6 +30,10 @@ module Chronos
|
|
|
30
30
|
@notifier = notifier
|
|
31
31
|
@notifications = notifications || active_support_notifications
|
|
32
32
|
@sql_normalizer = Core::SqlNormalizer.new
|
|
33
|
+
cache_options = notifier.respond_to?(:cache_integration_options) ? notifier.cache_integration_options : {}
|
|
34
|
+
@cache_normalizer = Core::CacheNormalizer.new(
|
|
35
|
+
cache_options[:project_id].to_s, cache_options[:key_mode] || :none
|
|
36
|
+
)
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def install
|
|
@@ -148,10 +152,7 @@ module Chronos
|
|
|
148
152
|
end
|
|
149
153
|
|
|
150
154
|
def cache(name, payload, duration)
|
|
151
|
-
data =
|
|
152
|
-
"operation" => name.split(".").first, "store" => value(payload, :store).to_s,
|
|
153
|
-
"hit" => value(payload, :hit), "duration_ms" => duration
|
|
154
|
-
}
|
|
155
|
+
data = @cache_normalizer.call(name, payload).merge("duration_ms" => duration)
|
|
155
156
|
@notifier.record_event("cache", data)
|
|
156
157
|
end
|
|
157
158
|
|
data/lib/chronos/version.rb
CHANGED
data/lib/chronos.rb
CHANGED
|
@@ -14,10 +14,13 @@ require "chronos/core/notice_builder"
|
|
|
14
14
|
require "chronos/core/sensitive_value_filter"
|
|
15
15
|
require "chronos/core/sanitizer"
|
|
16
16
|
require "chronos/core/safe_serializer"
|
|
17
|
+
require "chronos/core/correlation_context"
|
|
18
|
+
require "chronos/core/deploy_normalizer"
|
|
17
19
|
require "chronos/core/payload_serializer"
|
|
18
20
|
require "chronos/core/telemetry_event"
|
|
19
21
|
require "chronos/core/sql_normalizer"
|
|
20
22
|
require "chronos/core/metric_aggregate"
|
|
23
|
+
require "chronos/core/cache_normalizer"
|
|
21
24
|
require "chronos/ports/transport"
|
|
22
25
|
require "chronos/ports/context_store"
|
|
23
26
|
require "chronos/internal/safe_logger"
|
|
@@ -32,9 +35,12 @@ require "chronos/application/circuit_breaker"
|
|
|
32
35
|
require "chronos/application/remote_configuration"
|
|
33
36
|
require "chronos/application/delivery_pipeline"
|
|
34
37
|
require "chronos/application/capture_exception"
|
|
38
|
+
require "chronos/application/apm_error_classifier"
|
|
35
39
|
require "chronos/application/apm_aggregator"
|
|
40
|
+
require "chronos/application/dependency_reporter"
|
|
36
41
|
require "chronos/application/capture_telemetry"
|
|
37
42
|
require "chronos/agent"
|
|
43
|
+
require "chronos/observability_facade"
|
|
38
44
|
require "chronos/integrations"
|
|
39
45
|
require "chronos/integrations/rack"
|
|
40
46
|
require "chronos/integrations/rack/middleware"
|
|
@@ -55,6 +61,8 @@ require "chronos/integrations/rack/middleware"
|
|
|
55
61
|
# end
|
|
56
62
|
# Chronos.notify(RuntimeError.new("failed"))
|
|
57
63
|
module Chronos
|
|
64
|
+
extend ObservabilityFacade
|
|
65
|
+
|
|
58
66
|
@mutex = Mutex.new
|
|
59
67
|
@agent = nil
|
|
60
68
|
|
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.
|
|
4
|
+
version: 0.9.0.pre.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antonio Jefferson
|
|
@@ -87,6 +87,8 @@ files:
|
|
|
87
87
|
- README.md
|
|
88
88
|
- SECURITY.md
|
|
89
89
|
- contracts/apm-batch-v1.schema.json
|
|
90
|
+
- contracts/dependencies-v1.schema.json
|
|
91
|
+
- contracts/deploy-v1.schema.json
|
|
90
92
|
- contracts/event-v1.schema.json
|
|
91
93
|
- contracts/rack-context-v1.schema.json
|
|
92
94
|
- contracts/sidekiq-job-v1.schema.json
|
|
@@ -101,6 +103,8 @@ files:
|
|
|
101
103
|
- docs/adr/ADR-013-legacy-rails-notifications.md
|
|
102
104
|
- docs/adr/ADR-014-sidekiq-envelope-context.md
|
|
103
105
|
- docs/adr/ADR-015-bounded-apm-aggregation.md
|
|
106
|
+
- docs/adr/ADR-016-explicit-observability-integrations.md
|
|
107
|
+
- docs/adr/ADR-017-deploy-events-and-correlation.md
|
|
104
108
|
- docs/architecture.md
|
|
105
109
|
- docs/compatibility.md
|
|
106
110
|
- docs/configuration.md
|
|
@@ -108,7 +112,11 @@ files:
|
|
|
108
112
|
- docs/examples/plain-ruby.md
|
|
109
113
|
- docs/modules/apm-aggregation.md
|
|
110
114
|
- docs/modules/async-queue.md
|
|
115
|
+
- docs/modules/cache-observability.md
|
|
111
116
|
- docs/modules/configuration.md
|
|
117
|
+
- docs/modules/dependencies.md
|
|
118
|
+
- docs/modules/deploy-tracking.md
|
|
119
|
+
- docs/modules/external-http.md
|
|
112
120
|
- docs/modules/notice-pipeline.md
|
|
113
121
|
- docs/modules/rack-context.md
|
|
114
122
|
- docs/modules/rails-legacy.md
|
|
@@ -129,12 +137,15 @@ files:
|
|
|
129
137
|
- lib/chronos/agent.rb
|
|
130
138
|
- lib/chronos/application.rb
|
|
131
139
|
- lib/chronos/application/apm_aggregator.rb
|
|
140
|
+
- lib/chronos/application/apm_error_classifier.rb
|
|
132
141
|
- lib/chronos/application/capture_exception.rb
|
|
133
142
|
- lib/chronos/application/capture_telemetry.rb
|
|
134
143
|
- lib/chronos/application/circuit_breaker.rb
|
|
135
144
|
- lib/chronos/application/delivery_pipeline.rb
|
|
145
|
+
- lib/chronos/application/dependency_reporter.rb
|
|
136
146
|
- lib/chronos/application/remote_configuration.rb
|
|
137
147
|
- lib/chronos/application/retry_policy.rb
|
|
148
|
+
- lib/chronos/capistrano.rb
|
|
138
149
|
- lib/chronos/configuration.rb
|
|
139
150
|
- lib/chronos/configuration/apm_validation.rb
|
|
140
151
|
- lib/chronos/configuration/snapshot.rb
|
|
@@ -142,6 +153,9 @@ files:
|
|
|
142
153
|
- lib/chronos/core.rb
|
|
143
154
|
- lib/chronos/core/backtrace_parser.rb
|
|
144
155
|
- lib/chronos/core/breadcrumb.rb
|
|
156
|
+
- lib/chronos/core/cache_normalizer.rb
|
|
157
|
+
- lib/chronos/core/correlation_context.rb
|
|
158
|
+
- lib/chronos/core/deploy_normalizer.rb
|
|
145
159
|
- lib/chronos/core/exception_cause_collector.rb
|
|
146
160
|
- lib/chronos/core/metric_aggregate.rb
|
|
147
161
|
- lib/chronos/core/notice.rb
|
|
@@ -155,7 +169,9 @@ files:
|
|
|
155
169
|
- lib/chronos/core/telemetry_event.rb
|
|
156
170
|
- lib/chronos/errors.rb
|
|
157
171
|
- lib/chronos/integrations.rb
|
|
172
|
+
- lib/chronos/integrations/capistrano.rb
|
|
158
173
|
- lib/chronos/integrations/job_payload.rb
|
|
174
|
+
- lib/chronos/integrations/net_http.rb
|
|
159
175
|
- lib/chronos/integrations/rack.rb
|
|
160
176
|
- lib/chronos/integrations/rack/middleware.rb
|
|
161
177
|
- lib/chronos/integrations/sidekiq.rb
|
|
@@ -164,6 +180,8 @@ files:
|
|
|
164
180
|
- lib/chronos/internal/memory_backlog.rb
|
|
165
181
|
- lib/chronos/internal/safe_logger.rb
|
|
166
182
|
- lib/chronos/internal/worker_pool.rb
|
|
183
|
+
- lib/chronos/net_http.rb
|
|
184
|
+
- lib/chronos/observability_facade.rb
|
|
167
185
|
- lib/chronos/ports.rb
|
|
168
186
|
- lib/chronos/ports/context_store.rb
|
|
169
187
|
- lib/chronos/ports/transport.rb
|