atatus 1.0.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 +7 -0
- data/.gitignore +16 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +57 -0
- data/LICENSE +65 -0
- data/LICENSE-THIRD-PARTY +205 -0
- data/README.md +13 -0
- data/Rakefile +19 -0
- data/atatus.gemspec +36 -0
- data/atatus.yml +2 -0
- data/bench/.gitignore +2 -0
- data/bench/app.rb +53 -0
- data/bench/benchmark.rb +36 -0
- data/bench/report.rb +55 -0
- data/bench/rubyprof.rb +39 -0
- data/bench/stackprof.rb +23 -0
- data/bin/build_docs +5 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/with_framework +7 -0
- data/lib/atatus.rb +325 -0
- data/lib/atatus/agent.rb +260 -0
- data/lib/atatus/central_config.rb +141 -0
- data/lib/atatus/central_config/cache_control.rb +34 -0
- data/lib/atatus/collector/base.rb +329 -0
- data/lib/atatus/collector/builder.rb +317 -0
- data/lib/atatus/collector/transport.rb +72 -0
- data/lib/atatus/config.rb +248 -0
- data/lib/atatus/config/bytes.rb +25 -0
- data/lib/atatus/config/duration.rb +23 -0
- data/lib/atatus/config/options.rb +134 -0
- data/lib/atatus/config/regexp_list.rb +13 -0
- data/lib/atatus/context.rb +33 -0
- data/lib/atatus/context/request.rb +11 -0
- data/lib/atatus/context/request/socket.rb +19 -0
- data/lib/atatus/context/request/url.rb +42 -0
- data/lib/atatus/context/response.rb +22 -0
- data/lib/atatus/context/user.rb +42 -0
- data/lib/atatus/context_builder.rb +97 -0
- data/lib/atatus/deprecations.rb +22 -0
- data/lib/atatus/error.rb +22 -0
- data/lib/atatus/error/exception.rb +46 -0
- data/lib/atatus/error/log.rb +24 -0
- data/lib/atatus/error_builder.rb +76 -0
- data/lib/atatus/instrumenter.rb +224 -0
- data/lib/atatus/internal_error.rb +6 -0
- data/lib/atatus/logging.rb +55 -0
- data/lib/atatus/metadata.rb +19 -0
- data/lib/atatus/metadata/process_info.rb +18 -0
- data/lib/atatus/metadata/service_info.rb +61 -0
- data/lib/atatus/metadata/system_info.rb +35 -0
- data/lib/atatus/metadata/system_info/container_info.rb +121 -0
- data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
- data/lib/atatus/metadata/system_info/os_info.rb +31 -0
- data/lib/atatus/metrics.rb +98 -0
- data/lib/atatus/metrics/cpu_mem.rb +240 -0
- data/lib/atatus/metrics/vm.rb +60 -0
- data/lib/atatus/metricset.rb +19 -0
- data/lib/atatus/middleware.rb +76 -0
- data/lib/atatus/naively_hashable.rb +21 -0
- data/lib/atatus/normalizers.rb +68 -0
- data/lib/atatus/normalizers/action_controller.rb +27 -0
- data/lib/atatus/normalizers/action_mailer.rb +26 -0
- data/lib/atatus/normalizers/action_view.rb +77 -0
- data/lib/atatus/normalizers/active_record.rb +45 -0
- data/lib/atatus/opentracing.rb +346 -0
- data/lib/atatus/rails.rb +61 -0
- data/lib/atatus/railtie.rb +30 -0
- data/lib/atatus/span.rb +125 -0
- data/lib/atatus/span/context.rb +40 -0
- data/lib/atatus/span_helpers.rb +44 -0
- data/lib/atatus/spies.rb +86 -0
- data/lib/atatus/spies/action_dispatch.rb +28 -0
- data/lib/atatus/spies/delayed_job.rb +68 -0
- data/lib/atatus/spies/elasticsearch.rb +36 -0
- data/lib/atatus/spies/faraday.rb +70 -0
- data/lib/atatus/spies/http.rb +44 -0
- data/lib/atatus/spies/json.rb +22 -0
- data/lib/atatus/spies/mongo.rb +87 -0
- data/lib/atatus/spies/net_http.rb +70 -0
- data/lib/atatus/spies/rake.rb +45 -0
- data/lib/atatus/spies/redis.rb +27 -0
- data/lib/atatus/spies/sequel.rb +47 -0
- data/lib/atatus/spies/sidekiq.rb +89 -0
- data/lib/atatus/spies/sinatra.rb +41 -0
- data/lib/atatus/spies/tilt.rb +27 -0
- data/lib/atatus/sql_summarizer.rb +35 -0
- data/lib/atatus/stacktrace.rb +16 -0
- data/lib/atatus/stacktrace/frame.rb +52 -0
- data/lib/atatus/stacktrace_builder.rb +104 -0
- data/lib/atatus/subscriber.rb +77 -0
- data/lib/atatus/trace_context.rb +85 -0
- data/lib/atatus/transaction.rb +100 -0
- data/lib/atatus/transport/base.rb +174 -0
- data/lib/atatus/transport/connection.rb +156 -0
- data/lib/atatus/transport/connection/http.rb +116 -0
- data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
- data/lib/atatus/transport/filters.rb +43 -0
- data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
- data/lib/atatus/transport/serializers.rb +93 -0
- data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
- data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
- data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
- data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
- data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
- data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
- data/lib/atatus/transport/worker.rb +73 -0
- data/lib/atatus/util.rb +42 -0
- data/lib/atatus/util/inflector.rb +93 -0
- data/lib/atatus/util/lru_cache.rb +48 -0
- data/lib/atatus/util/prefixed_logger.rb +18 -0
- data/lib/atatus/util/throttle.rb +35 -0
- data/lib/atatus/version.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +190 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
# @api private
|
|
5
|
+
class TraceContext
|
|
6
|
+
class InvalidTraceparentHeader < StandardError; end
|
|
7
|
+
|
|
8
|
+
VERSION = '00'
|
|
9
|
+
HEX_REGEX = /[^[:xdigit:]]/.freeze
|
|
10
|
+
|
|
11
|
+
TRACE_ID_LENGTH = 16
|
|
12
|
+
ID_LENGTH = 8
|
|
13
|
+
|
|
14
|
+
def initialize(
|
|
15
|
+
version: VERSION,
|
|
16
|
+
trace_id: nil,
|
|
17
|
+
span_id: nil,
|
|
18
|
+
id: nil,
|
|
19
|
+
recorded: true
|
|
20
|
+
)
|
|
21
|
+
@version = version
|
|
22
|
+
@trace_id = trace_id || hex(TRACE_ID_LENGTH)
|
|
23
|
+
# TODO: rename to parent_id with next major version bump
|
|
24
|
+
@parent_id = span_id
|
|
25
|
+
@id = id || hex(ID_LENGTH)
|
|
26
|
+
@recorded = recorded
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_accessor :version, :id, :trace_id, :parent_id, :recorded
|
|
30
|
+
|
|
31
|
+
alias :recorded? :recorded
|
|
32
|
+
|
|
33
|
+
# rubocop:disable Metrics/AbcSize
|
|
34
|
+
def self.parse(header)
|
|
35
|
+
raise InvalidTraceparentHeader unless header.length == 55
|
|
36
|
+
raise InvalidTraceparentHeader unless header[0..1] == VERSION
|
|
37
|
+
|
|
38
|
+
new.tap do |t|
|
|
39
|
+
t.version, t.trace_id, t.parent_id, t.flags =
|
|
40
|
+
header.split('-').tap do |values|
|
|
41
|
+
values[-1] = Util.hex_to_bits(values[-1])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
raise InvalidTraceparentHeader if HEX_REGEX =~ t.trace_id
|
|
45
|
+
raise InvalidTraceparentHeader if HEX_REGEX =~ t.parent_id
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
# rubocop:enable Metrics/AbcSize
|
|
49
|
+
|
|
50
|
+
def flags=(flags)
|
|
51
|
+
@flags = flags
|
|
52
|
+
|
|
53
|
+
self.recorded = flags[7] == '1'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def flags
|
|
57
|
+
format('0000000%d', recorded? ? 1 : 0)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def hex_flags
|
|
61
|
+
format('%02x', flags.to_i(2))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ensure_parent_id
|
|
65
|
+
@parent_id ||= hex(ID_LENGTH)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def child
|
|
69
|
+
dup.tap do |tc|
|
|
70
|
+
tc.parent_id = tc.id
|
|
71
|
+
tc.id = hex(ID_LENGTH)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_header
|
|
76
|
+
format('%s-%s-%s-%s', version, trace_id, id, hex_flags)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def hex(len)
|
|
82
|
+
SecureRandom.hex(len)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
|
|
6
|
+
module Atatus
|
|
7
|
+
# @api private
|
|
8
|
+
class Transaction
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
def_delegators :@trace_context,
|
|
12
|
+
:trace_id, :parent_id, :id, :ensure_parent_id
|
|
13
|
+
|
|
14
|
+
DEFAULT_TYPE = 'custom'
|
|
15
|
+
|
|
16
|
+
# rubocop:disable Metrics/ParameterLists
|
|
17
|
+
def initialize(
|
|
18
|
+
name = nil,
|
|
19
|
+
type = nil,
|
|
20
|
+
sampled: true,
|
|
21
|
+
context: nil,
|
|
22
|
+
labels: nil,
|
|
23
|
+
trace_context: nil
|
|
24
|
+
)
|
|
25
|
+
@name = name
|
|
26
|
+
@type = type || DEFAULT_TYPE
|
|
27
|
+
|
|
28
|
+
@sampled = sampled
|
|
29
|
+
|
|
30
|
+
@context = context || Context.new # TODO: Lazy generate this?
|
|
31
|
+
Util.reverse_merge!(@context.labels, labels) if labels
|
|
32
|
+
|
|
33
|
+
@trace_context = trace_context || TraceContext.new(recorded: sampled)
|
|
34
|
+
|
|
35
|
+
@started_spans = 0
|
|
36
|
+
@dropped_spans = 0
|
|
37
|
+
|
|
38
|
+
@notifications = [] # for AS::Notifications
|
|
39
|
+
end
|
|
40
|
+
# rubocop:enable Metrics/ParameterLists
|
|
41
|
+
|
|
42
|
+
attr_accessor :name, :type, :result, :spans
|
|
43
|
+
|
|
44
|
+
attr_reader :context, :duration, :started_spans, :dropped_spans,
|
|
45
|
+
:timestamp, :trace_context, :notifications
|
|
46
|
+
|
|
47
|
+
def sampled?
|
|
48
|
+
@sampled
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stopped?
|
|
52
|
+
!!duration
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# life cycle
|
|
56
|
+
|
|
57
|
+
def start(clock_start = Util.monotonic_micros)
|
|
58
|
+
@timestamp = Util.micros
|
|
59
|
+
@clock_start = clock_start
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def stop(clock_end = Util.monotonic_micros)
|
|
64
|
+
raise 'Transaction not yet start' unless timestamp
|
|
65
|
+
@duration = clock_end - @clock_start
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def done(result = nil, clock_end: Util.monotonic_micros)
|
|
70
|
+
stop clock_end
|
|
71
|
+
self.result = result if result
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# spans
|
|
76
|
+
|
|
77
|
+
def inc_started_spans!
|
|
78
|
+
@started_spans += 1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def inc_dropped_spans!
|
|
82
|
+
@dropped_spans += 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def max_spans_reached?(config)
|
|
86
|
+
started_spans > config.transaction_max_spans
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# context
|
|
90
|
+
|
|
91
|
+
def add_response(*args)
|
|
92
|
+
context.response = Context::Response.new(*args)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def inspect
|
|
96
|
+
"<Atatus::Transaction id:#{id}" \
|
|
97
|
+
" name:#{name.inspect} type:#{type.inspect}>"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'atatus/metadata'
|
|
4
|
+
require 'atatus/transport/connection'
|
|
5
|
+
require 'atatus/transport/worker'
|
|
6
|
+
require 'atatus/transport/serializers'
|
|
7
|
+
require 'atatus/transport/filters'
|
|
8
|
+
require 'atatus/util/throttle'
|
|
9
|
+
|
|
10
|
+
module Atatus
|
|
11
|
+
module Transport
|
|
12
|
+
# rubocop:disable Metrics/ClassLength
|
|
13
|
+
# @api private
|
|
14
|
+
class Base
|
|
15
|
+
include Logging
|
|
16
|
+
|
|
17
|
+
WATCHER_EXECUTION_INTERVAL = 5
|
|
18
|
+
WATCHER_TIMEOUT_INTERVAL = 4
|
|
19
|
+
WORKER_JOIN_TIMEOUT = 5
|
|
20
|
+
|
|
21
|
+
def initialize(config)
|
|
22
|
+
@config = config
|
|
23
|
+
@queue = SizedQueue.new(config.api_buffer_size)
|
|
24
|
+
|
|
25
|
+
@serializers = Serializers.new(config)
|
|
26
|
+
@filters = Filters.new(config)
|
|
27
|
+
|
|
28
|
+
@stopped = Concurrent::AtomicBoolean.new
|
|
29
|
+
@workers = Array.new(config.pool_size)
|
|
30
|
+
|
|
31
|
+
@watcher_mutex = Mutex.new
|
|
32
|
+
@worker_mutex = Mutex.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :config, :queue, :filters, :workers, :watcher, :stopped
|
|
36
|
+
|
|
37
|
+
def start
|
|
38
|
+
debug '%s: Starting Transport', pid_str
|
|
39
|
+
|
|
40
|
+
ensure_watcher_running
|
|
41
|
+
ensure_worker_count
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stop
|
|
45
|
+
debug '%s: Stopping Transport', pid_str
|
|
46
|
+
|
|
47
|
+
@stopped.make_true
|
|
48
|
+
|
|
49
|
+
stop_watcher
|
|
50
|
+
stop_workers
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# rubocop:disable Metrics/MethodLength
|
|
54
|
+
def submit(resource)
|
|
55
|
+
if @stopped.true?
|
|
56
|
+
warn '%s: Transport stopping, no new events accepted', pid_str
|
|
57
|
+
return false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ensure_watcher_running
|
|
61
|
+
queue.push(resource, true)
|
|
62
|
+
|
|
63
|
+
true
|
|
64
|
+
rescue ThreadError
|
|
65
|
+
throttled_queue_full_warning
|
|
66
|
+
nil
|
|
67
|
+
rescue Exception => e
|
|
68
|
+
error '%s: Failed adding to the transport queue: %p', pid_str, e.inspect
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
# rubocop:enable Metrics/MethodLength
|
|
72
|
+
|
|
73
|
+
def add_filter(key, callback)
|
|
74
|
+
@filters.add(key, callback)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def pid_str
|
|
80
|
+
format('[PID:%s]', Process.pid)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ensure_watcher_running
|
|
84
|
+
# pid has changed == we've forked
|
|
85
|
+
return if @pid == Process.pid
|
|
86
|
+
|
|
87
|
+
@watcher_mutex.synchronize do
|
|
88
|
+
return if @pid == Process.pid
|
|
89
|
+
@pid = Process.pid
|
|
90
|
+
|
|
91
|
+
@watcher = Concurrent::TimerTask.execute(
|
|
92
|
+
execution_interval: WATCHER_EXECUTION_INTERVAL,
|
|
93
|
+
timeout_interval: WATCHER_TIMEOUT_INTERVAL
|
|
94
|
+
) { ensure_worker_count }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def ensure_worker_count
|
|
99
|
+
@worker_mutex.synchronize do
|
|
100
|
+
return if all_workers_alive?
|
|
101
|
+
return if stopped.true?
|
|
102
|
+
|
|
103
|
+
@workers.map! do |thread|
|
|
104
|
+
next thread if thread&.alive?
|
|
105
|
+
|
|
106
|
+
boot_worker
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def all_workers_alive?
|
|
112
|
+
!!workers.all? { |t| t&.alive? }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def boot_worker
|
|
116
|
+
debug '%s: Booting worker...', pid_str
|
|
117
|
+
|
|
118
|
+
Thread.new do
|
|
119
|
+
Worker.new(
|
|
120
|
+
config, queue,
|
|
121
|
+
serializers: @serializers,
|
|
122
|
+
filters: @filters
|
|
123
|
+
).work_forever
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# rubocop:disable Metrics/MethodLength
|
|
128
|
+
def stop_workers
|
|
129
|
+
debug '%s: Stopping workers', pid_str
|
|
130
|
+
|
|
131
|
+
send_stop_messages
|
|
132
|
+
|
|
133
|
+
@worker_mutex.synchronize do
|
|
134
|
+
workers.each do |thread|
|
|
135
|
+
next if thread.nil?
|
|
136
|
+
next if thread.join(WORKER_JOIN_TIMEOUT)
|
|
137
|
+
|
|
138
|
+
debug(
|
|
139
|
+
'%s: Worker did not stop in %ds, killing...',
|
|
140
|
+
pid_str, WORKER_JOIN_TIMEOUT
|
|
141
|
+
)
|
|
142
|
+
thread.kill
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
@workers.clear
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
# rubocop:enable Metrics/MethodLength
|
|
149
|
+
|
|
150
|
+
def send_stop_messages
|
|
151
|
+
config.pool_size.times { queue.push(Worker::StopMessage.new, true) }
|
|
152
|
+
rescue ThreadError
|
|
153
|
+
warn 'Cannot push stop messages to worker queue as it is full'
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def stop_watcher
|
|
157
|
+
@watcher_mutex.synchronize do
|
|
158
|
+
return if watcher.nil? || @pid != Process.pid
|
|
159
|
+
watcher.shutdown
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def throttled_queue_full_warning
|
|
164
|
+
(@queue_full_log ||= Util::Throttle.new(5) do
|
|
165
|
+
warn(
|
|
166
|
+
'%s: Queue is full (%i items), skipping…',
|
|
167
|
+
pid_str, config.api_buffer_size
|
|
168
|
+
)
|
|
169
|
+
end).call
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
# rubocop:enable Metrics/ClassLength
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
require 'zlib'
|
|
5
|
+
|
|
6
|
+
require 'atatus/transport/connection/http'
|
|
7
|
+
|
|
8
|
+
module Atatus
|
|
9
|
+
module Transport
|
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
|
11
|
+
# @api private
|
|
12
|
+
class Connection
|
|
13
|
+
include Logging
|
|
14
|
+
|
|
15
|
+
# A connection holds an instance `http` of an Http::Connection.
|
|
16
|
+
#
|
|
17
|
+
# The HTTP::Connection itself is not thread safe.
|
|
18
|
+
#
|
|
19
|
+
# The connection sends write requests and close requests to `http`, and
|
|
20
|
+
# has to ensure no write requests are sent after closing `http`.
|
|
21
|
+
#
|
|
22
|
+
# The connection schedules a separate thread to close an `http`
|
|
23
|
+
# connection some time in the future. To avoid the thread interfering
|
|
24
|
+
# with ongoing write requests to `http`, write and close
|
|
25
|
+
# requests have to be synchronized.
|
|
26
|
+
|
|
27
|
+
HEADERS = {
|
|
28
|
+
'Content-Type' => 'application/x-ndjson',
|
|
29
|
+
'Transfer-Encoding' => 'chunked'
|
|
30
|
+
}.freeze
|
|
31
|
+
GZIP_HEADERS = HEADERS.merge(
|
|
32
|
+
'Content-Encoding' => 'gzip'
|
|
33
|
+
).freeze
|
|
34
|
+
|
|
35
|
+
def initialize(config, metadata)
|
|
36
|
+
@config = config
|
|
37
|
+
@headers = build_headers(metadata)
|
|
38
|
+
@metadata = JSON.fast_generate(metadata)
|
|
39
|
+
@url = config.server_url + '/intake/v2/events'
|
|
40
|
+
@ssl_context = build_ssl_context
|
|
41
|
+
@mutex = Mutex.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
attr_reader :http
|
|
45
|
+
|
|
46
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
47
|
+
def write(str)
|
|
48
|
+
return false if @config.disable_send
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
bytes_written = 0
|
|
52
|
+
|
|
53
|
+
# The request might get closed from timertask so let's make sure we
|
|
54
|
+
# hold it open until we've written.
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
connect if http.nil? || http.closed?
|
|
57
|
+
bytes_written = http.write(str)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
flush(:api_request_size) if bytes_written >= @config.api_request_size
|
|
61
|
+
rescue IOError => e
|
|
62
|
+
error('Connection error: %s', e.inspect)
|
|
63
|
+
flush(:ioerror)
|
|
64
|
+
rescue Errno::EPIPE => e
|
|
65
|
+
error('Connection error: %s', e.inspect)
|
|
66
|
+
flush(:broken_pipe)
|
|
67
|
+
rescue Exception => e
|
|
68
|
+
error('Connection error: %s', e.inspect)
|
|
69
|
+
flush(:connection_error)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
73
|
+
|
|
74
|
+
def flush(reason = :force)
|
|
75
|
+
# Could happen from the timertask so we need to sync
|
|
76
|
+
@mutex.synchronize do
|
|
77
|
+
return if http.nil?
|
|
78
|
+
http.close(reason)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def inspect
|
|
83
|
+
format(
|
|
84
|
+
'@%s http connection closed? :%s>',
|
|
85
|
+
super.split.first,
|
|
86
|
+
http.closed?
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def connect
|
|
93
|
+
schedule_closing if @config.api_request_time
|
|
94
|
+
|
|
95
|
+
@http =
|
|
96
|
+
Http.open(
|
|
97
|
+
@config, @url,
|
|
98
|
+
headers: @headers,
|
|
99
|
+
ssl_context: @ssl_context
|
|
100
|
+
).tap { |http| http.write(@metadata) }
|
|
101
|
+
end
|
|
102
|
+
# rubocop:enable
|
|
103
|
+
|
|
104
|
+
def schedule_closing
|
|
105
|
+
@close_task&.cancel
|
|
106
|
+
@close_task =
|
|
107
|
+
Concurrent::ScheduledTask.execute(@config.api_request_time) do
|
|
108
|
+
flush(:timeout)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_headers(metadata)
|
|
113
|
+
(
|
|
114
|
+
@config.http_compression? ? GZIP_HEADERS : HEADERS
|
|
115
|
+
).dup.tap do |headers|
|
|
116
|
+
headers['User-Agent'] = build_user_agent(metadata)
|
|
117
|
+
|
|
118
|
+
if (token = @config.secret_token)
|
|
119
|
+
headers['Authorization'] = "Bearer #{token}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def build_user_agent(metadata)
|
|
125
|
+
runtime = metadata.dig(:metadata, :service, :runtime)
|
|
126
|
+
|
|
127
|
+
[
|
|
128
|
+
"atatus-ruby/#{VERSION}",
|
|
129
|
+
HTTP::Request::USER_AGENT,
|
|
130
|
+
[runtime[:name], runtime[:version]].join('/')
|
|
131
|
+
].join(' ')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def build_ssl_context # rubocop:disable Metrics/MethodLength
|
|
135
|
+
return unless @config.use_ssl?
|
|
136
|
+
|
|
137
|
+
OpenSSL::SSL::SSLContext.new.tap do |context|
|
|
138
|
+
if @config.server_ca_cert
|
|
139
|
+
context.ca_file = @config.server_ca_cert
|
|
140
|
+
else
|
|
141
|
+
context.cert_store =
|
|
142
|
+
OpenSSL::X509::Store.new.tap(&:set_default_paths)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context.verify_mode =
|
|
146
|
+
if @config.verify_server_cert
|
|
147
|
+
OpenSSL::SSL::VERIFY_PEER
|
|
148
|
+
else
|
|
149
|
+
OpenSSL::SSL::VERIFY_NONE
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
# rubocop:enable Metrics/ClassLength
|
|
155
|
+
end
|
|
156
|
+
end
|