elastic-apm 1.1.0 → 2.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.
Potentially problematic release.
This version of elastic-apm might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +7 -1
- data/CHANGELOG.md +45 -0
- data/Gemfile +17 -12
- data/bench/app.rb +1 -2
- data/bench/benchmark.rb +1 -1
- data/bench/stackprof.rb +1 -1
- data/docs/api.asciidoc +115 -76
- data/docs/configuration.asciidoc +232 -167
- data/docs/context.asciidoc +7 -3
- data/docs/custom-instrumentation.asciidoc +17 -28
- data/docs/index.asciidoc +13 -7
- data/docs/supported-technologies.asciidoc +65 -0
- data/elastic-apm.gemspec +3 -2
- data/lib/elastic_apm.rb +272 -121
- data/lib/elastic_apm/agent.rb +56 -107
- data/lib/elastic_apm/config.rb +130 -106
- data/lib/elastic_apm/config/duration.rb +25 -0
- data/lib/elastic_apm/config/size.rb +28 -0
- data/lib/elastic_apm/context_builder.rb +1 -0
- data/lib/elastic_apm/deprecations.rb +19 -0
- data/lib/elastic_apm/error.rb +5 -2
- data/lib/elastic_apm/error/exception.rb +1 -1
- data/lib/elastic_apm/error_builder.rb +5 -0
- data/lib/elastic_apm/instrumenter.rb +121 -53
- data/lib/elastic_apm/internal_error.rb +1 -0
- data/lib/elastic_apm/{log.rb → logging.rb} +16 -11
- data/lib/elastic_apm/metadata.rb +20 -0
- data/lib/elastic_apm/metadata/process_info.rb +26 -0
- data/lib/elastic_apm/metadata/service_info.rb +56 -0
- data/lib/elastic_apm/metadata/system_info.rb +30 -0
- data/lib/elastic_apm/middleware.rb +31 -15
- data/lib/elastic_apm/normalizers/action_controller.rb +1 -1
- data/lib/elastic_apm/normalizers/action_mailer.rb +1 -1
- data/lib/elastic_apm/normalizers/action_view.rb +3 -3
- data/lib/elastic_apm/normalizers/active_record.rb +2 -1
- data/lib/elastic_apm/railtie.rb +1 -1
- data/lib/elastic_apm/span.rb +59 -29
- data/lib/elastic_apm/span/context.rb +30 -4
- data/lib/elastic_apm/span_helpers.rb +1 -1
- data/lib/elastic_apm/spies/delayed_job.rb +7 -7
- data/lib/elastic_apm/spies/elasticsearch.rb +4 -4
- data/lib/elastic_apm/spies/http.rb +38 -0
- data/lib/elastic_apm/spies/mongo.rb +22 -11
- data/lib/elastic_apm/spies/net_http.rb +7 -4
- data/lib/elastic_apm/spies/rake.rb +5 -6
- data/lib/elastic_apm/spies/redis.rb +1 -1
- data/lib/elastic_apm/spies/sequel.rb +9 -7
- data/lib/elastic_apm/spies/sidekiq.rb +5 -5
- data/lib/elastic_apm/spies/tilt.rb +2 -2
- data/lib/elastic_apm/sql_summarizer.rb +3 -3
- data/lib/elastic_apm/stacktrace_builder.rb +6 -6
- data/lib/elastic_apm/subscriber.rb +3 -3
- data/lib/elastic_apm/traceparent.rb +62 -0
- data/lib/elastic_apm/transaction.rb +62 -93
- data/lib/elastic_apm/transport/base.rb +98 -0
- data/lib/elastic_apm/transport/connection.rb +175 -0
- data/lib/elastic_apm/transport/filters.rb +45 -0
- data/lib/elastic_apm/transport/filters/request_body_filter.rb +31 -0
- data/lib/elastic_apm/transport/filters/secrets_filter.rb +59 -0
- data/lib/elastic_apm/transport/serializers.rb +58 -0
- data/lib/elastic_apm/transport/serializers/error_serializer.rb +59 -0
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +30 -0
- data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +33 -0
- data/lib/elastic_apm/transport/worker.rb +73 -0
- data/lib/elastic_apm/util.rb +11 -8
- data/lib/elastic_apm/version.rb +1 -1
- metadata +40 -21
- data/.travis.yml +0 -5
- data/docs/troubleshooting.asciidoc +0 -28
- data/lib/elastic_apm/filters.rb +0 -46
- data/lib/elastic_apm/filters/request_body_filter.rb +0 -33
- data/lib/elastic_apm/filters/secrets_filter.rb +0 -59
- data/lib/elastic_apm/http.rb +0 -139
- data/lib/elastic_apm/process_info.rb +0 -24
- data/lib/elastic_apm/serializers.rb +0 -28
- data/lib/elastic_apm/serializers/errors.rb +0 -61
- data/lib/elastic_apm/serializers/transactions.rb +0 -51
- data/lib/elastic_apm/service_info.rb +0 -54
- data/lib/elastic_apm/system_info.rb +0 -28
- data/lib/elastic_apm/util/dig.rb +0 -31
- data/lib/elastic_apm/util/inspector.rb +0 -61
- data/lib/elastic_apm/worker.rb +0 -106
@@ -5,144 +5,113 @@ require 'securerandom'
|
|
5
5
|
module ElasticAPM
|
6
6
|
# @api private
|
7
7
|
class Transaction
|
8
|
-
|
8
|
+
extend Deprecations
|
9
9
|
|
10
|
-
|
10
|
+
DEFAULT_TYPE = 'custom'
|
11
|
+
|
12
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
11
13
|
def initialize(
|
12
|
-
instrumenter,
|
13
14
|
name = nil,
|
14
15
|
type = nil,
|
16
|
+
sampled: true,
|
15
17
|
context: nil,
|
16
|
-
|
18
|
+
tags: nil,
|
19
|
+
traceparent: nil
|
17
20
|
)
|
18
|
-
@id = SecureRandom.uuid
|
19
|
-
@instrumenter = instrumenter
|
20
21
|
@name = name
|
21
22
|
@type = type || DEFAULT_TYPE
|
22
23
|
|
23
|
-
@
|
24
|
+
@sampled = sampled
|
24
25
|
|
25
|
-
@
|
26
|
-
@
|
27
|
-
@dropped_spans = 0
|
26
|
+
@context = context || Context.new # TODO: Lazy generate this?
|
27
|
+
Util.reverse_merge!(@context.tags, tags) if tags
|
28
28
|
|
29
|
-
@
|
29
|
+
@id = SecureRandom.hex(8)
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
if traceparent
|
32
|
+
@traceparent = traceparent
|
33
|
+
@parent_id = traceparent.span_id
|
34
|
+
else
|
35
|
+
@traceparent = Traceparent.from_transaction(self)
|
36
|
+
end
|
33
37
|
|
34
|
-
@
|
38
|
+
@started_spans = 0
|
39
|
+
@dropped_spans = 0
|
35
40
|
|
36
|
-
|
41
|
+
@notifications = [] # for AS::Notifications
|
37
42
|
end
|
38
|
-
# rubocop:enable Metrics/MethodLength
|
43
|
+
# rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
|
39
44
|
|
40
|
-
attr_accessor :name, :type
|
41
|
-
attr_reader :id, :context, :duration, :dropped_spans, :root_span,
|
42
|
-
:timestamp, :spans, :result, :notifications, :sampled, :instrumenter
|
45
|
+
attr_accessor :name, :type, :result
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
+
attr_reader :id, :context, :duration, :started_spans, :dropped_spans,
|
48
|
+
:timestamp, :traceparent, :notifications, :parent_id
|
47
49
|
|
48
|
-
def
|
49
|
-
@
|
50
|
-
|
50
|
+
def sampled?
|
51
|
+
@sampled
|
52
|
+
end
|
51
53
|
|
52
|
-
|
54
|
+
def stopped?
|
55
|
+
!!duration
|
53
56
|
end
|
54
57
|
|
55
58
|
def done?
|
56
|
-
|
59
|
+
stopped?
|
57
60
|
end
|
58
61
|
|
59
|
-
|
60
|
-
done result unless duration
|
61
|
-
|
62
|
-
if status
|
63
|
-
context.response = Context::Response.new(status, headers: headers)
|
64
|
-
end
|
65
|
-
|
66
|
-
release
|
67
|
-
|
68
|
-
@instrumenter.submit_transaction self
|
62
|
+
deprecate :done?, :stopped?
|
69
63
|
|
70
|
-
|
64
|
+
def trace_id
|
65
|
+
traceparent&.trace_id
|
71
66
|
end
|
72
67
|
|
73
|
-
#
|
74
|
-
def span(name, type = nil, backtrace: nil, context: nil)
|
75
|
-
unless sampled?
|
76
|
-
return yield if block_given?
|
77
|
-
return
|
78
|
-
end
|
68
|
+
# life cycle
|
79
69
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
return yield if block_given?
|
84
|
-
return
|
85
|
-
end
|
86
|
-
|
87
|
-
span = build_and_start_span(name, type, context, backtrace)
|
88
|
-
|
89
|
-
return span unless block_given?
|
90
|
-
|
91
|
-
begin
|
92
|
-
result = yield span
|
93
|
-
ensure
|
94
|
-
span.done
|
95
|
-
end
|
96
|
-
|
97
|
-
result
|
70
|
+
def start
|
71
|
+
@timestamp = Util.micros
|
72
|
+
self
|
98
73
|
end
|
99
|
-
# rubocop:enable Metrics/MethodLength
|
100
74
|
|
101
|
-
def
|
102
|
-
|
75
|
+
def stop
|
76
|
+
raise 'Transaction not yet start' unless timestamp
|
77
|
+
@duration = Util.micros - timestamp
|
78
|
+
self
|
103
79
|
end
|
104
80
|
|
105
|
-
def
|
106
|
-
|
81
|
+
def done(result = nil)
|
82
|
+
stop
|
83
|
+
self.result = result if result
|
84
|
+
self
|
107
85
|
end
|
108
86
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
" type:#{type.inspect}" \
|
113
|
-
'>'
|
87
|
+
def ensure_parent_id
|
88
|
+
@parent_id ||= SecureRandom.hex(8)
|
89
|
+
@parent_id
|
114
90
|
end
|
115
91
|
|
116
|
-
|
92
|
+
# spans
|
117
93
|
|
118
|
-
def
|
119
|
-
@
|
94
|
+
def inc_started_spans!
|
95
|
+
@started_spans += 1
|
120
96
|
end
|
121
97
|
|
122
|
-
def
|
123
|
-
|
124
|
-
self,
|
125
|
-
next_span_id,
|
126
|
-
name,
|
127
|
-
type,
|
128
|
-
parent: current_span,
|
129
|
-
context: context
|
130
|
-
)
|
98
|
+
def inc_dropped_spans!
|
99
|
+
@dropped_spans += 1
|
131
100
|
end
|
132
101
|
|
133
|
-
def
|
134
|
-
|
102
|
+
def max_spans_reached?(config)
|
103
|
+
started_spans > config.transaction_max_spans
|
135
104
|
end
|
136
105
|
|
137
|
-
|
138
|
-
span = next_span(name, type, context)
|
139
|
-
spans << span
|
106
|
+
# context
|
140
107
|
|
141
|
-
|
142
|
-
|
143
|
-
|
108
|
+
def add_response(*args)
|
109
|
+
context.response = Context::Response.new(*args)
|
110
|
+
end
|
144
111
|
|
145
|
-
|
112
|
+
def inspect
|
113
|
+
"<ElasticAPM::Transaction id:#{id}" \
|
114
|
+
" name:#{name.inspect} type:#{type.inspect}>"
|
146
115
|
end
|
147
116
|
end
|
148
117
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elastic_apm/transport/connection'
|
4
|
+
require 'elastic_apm/transport/worker'
|
5
|
+
|
6
|
+
require 'elastic_apm/transport/serializers'
|
7
|
+
require 'elastic_apm/transport/filters'
|
8
|
+
|
9
|
+
module ElasticAPM
|
10
|
+
module Transport
|
11
|
+
# @api private
|
12
|
+
class Base
|
13
|
+
include Logging
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@config = config
|
17
|
+
@queue = SizedQueue.new(config.api_buffer_size)
|
18
|
+
@pool = Concurrent::FixedThreadPool.new(config.pool_size)
|
19
|
+
@workers = []
|
20
|
+
|
21
|
+
@serializers = Serializers.new(config)
|
22
|
+
@filters = Filters.new(config)
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :config, :queue, :workers, :filters
|
26
|
+
|
27
|
+
def start
|
28
|
+
ensure_worker_count
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
stop_workers
|
33
|
+
end
|
34
|
+
|
35
|
+
def submit(resource)
|
36
|
+
queue.push(resource, true)
|
37
|
+
|
38
|
+
ensure_worker_count
|
39
|
+
rescue ThreadError
|
40
|
+
warn 'Queue is full (%i items), skipping…', config.api_buffer_size
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_filter(key, callback)
|
45
|
+
@filters.add(key, callback)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def ensure_worker_count
|
51
|
+
missing = config.pool_size - @workers.length
|
52
|
+
return unless missing > 0
|
53
|
+
|
54
|
+
info 'Booting %i workers', missing
|
55
|
+
missing.times { boot_worker }
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable Metrics/MethodLength
|
59
|
+
def boot_worker
|
60
|
+
worker = Worker.new(
|
61
|
+
config,
|
62
|
+
queue,
|
63
|
+
serializers: @serializers,
|
64
|
+
filters: @filters
|
65
|
+
)
|
66
|
+
|
67
|
+
@workers.push worker
|
68
|
+
|
69
|
+
@pool.post do
|
70
|
+
worker.work_forever
|
71
|
+
@workers.delete(worker)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# rubocop:enable Metrics/MethodLength
|
75
|
+
|
76
|
+
def stop_workers
|
77
|
+
return unless @pool.running?
|
78
|
+
|
79
|
+
debug 'Stopping workers'
|
80
|
+
send_stop_messages
|
81
|
+
|
82
|
+
debug 'Shutting down pool'
|
83
|
+
@pool.shutdown
|
84
|
+
|
85
|
+
return if @pool.wait_for_termination(5)
|
86
|
+
|
87
|
+
warn "Worker pool didn't close in 5 secs, killing ..."
|
88
|
+
@pool.kill
|
89
|
+
end
|
90
|
+
|
91
|
+
def send_stop_messages
|
92
|
+
@workers.each { queue.push(Worker::StopMessage.new, true) }
|
93
|
+
rescue ThreadError
|
94
|
+
warn 'Cannot push stop messages to worker queue as it is full'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'http'
|
4
|
+
require 'concurrent'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
require 'elastic_apm/metadata'
|
8
|
+
|
9
|
+
module ElasticAPM
|
10
|
+
module Transport
|
11
|
+
# @api private
|
12
|
+
class Connection # rubocop:disable Metrics/ClassLength
|
13
|
+
include Logging
|
14
|
+
|
15
|
+
class FailedToConnectError < InternalError; end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
# HTTP.rb calls #rewind the body stream which IO.pipes don't support
|
19
|
+
class ModdedIO < IO
|
20
|
+
def self.pipe(ext_enc = nil)
|
21
|
+
super(ext_enc).tap do |rw|
|
22
|
+
rw[0].define_singleton_method(:rewind) { nil }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
HEADERS = {
|
28
|
+
'Content-Type' => 'application/x-ndjson',
|
29
|
+
'Transfer-Encoding' => 'chunked'
|
30
|
+
}.freeze
|
31
|
+
GZIP_HEADERS = HEADERS.merge('Content-Encoding' => 'gzip').freeze
|
32
|
+
|
33
|
+
def initialize(config)
|
34
|
+
@config = config
|
35
|
+
|
36
|
+
@url = config.server_url + '/intake/v2/events'
|
37
|
+
|
38
|
+
headers =
|
39
|
+
(@config.http_compression? ? GZIP_HEADERS : HEADERS).dup
|
40
|
+
|
41
|
+
if (token = config.secret_token)
|
42
|
+
headers['Authorization'] = "Bearer #{token}"
|
43
|
+
end
|
44
|
+
|
45
|
+
@client = HTTP.headers(headers).persistent(@url)
|
46
|
+
|
47
|
+
@metadata = Metadata.build(config)
|
48
|
+
|
49
|
+
@mutex = Mutex.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def write(str)
|
53
|
+
connect_unless_connected
|
54
|
+
|
55
|
+
@mutex.synchronize { append(str) }
|
56
|
+
|
57
|
+
return unless @bytes_sent >= @config.api_request_size
|
58
|
+
|
59
|
+
flush
|
60
|
+
rescue FailedToConnectError => e
|
61
|
+
error "Couldn't establish connection to APM Server:\n%p", e
|
62
|
+
flush
|
63
|
+
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def connected?
|
68
|
+
@mutex.synchronize { @connected }
|
69
|
+
end
|
70
|
+
|
71
|
+
def flush
|
72
|
+
@mutex.synchronize do
|
73
|
+
return unless @connected
|
74
|
+
|
75
|
+
debug 'Closing request'
|
76
|
+
@wr.close
|
77
|
+
@conn_thread.join 5 if @conn_thread
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# rubocop:disable Metrics/MethodLength
|
84
|
+
def connect_unless_connected
|
85
|
+
@mutex.synchronize do
|
86
|
+
return true if @connected
|
87
|
+
|
88
|
+
debug 'Opening new request'
|
89
|
+
|
90
|
+
reset!
|
91
|
+
|
92
|
+
@rd, @wr = ModdedIO.pipe
|
93
|
+
|
94
|
+
enable_compression! if @config.http_compression?
|
95
|
+
|
96
|
+
perform_request_in_thread
|
97
|
+
wait_for_connection
|
98
|
+
|
99
|
+
schedule_closing if @config.api_request_time
|
100
|
+
|
101
|
+
append(@metadata)
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# rubocop:enable Metrics/MethodLength
|
107
|
+
|
108
|
+
# rubocop:disable Metrics/MethodLength
|
109
|
+
def perform_request_in_thread
|
110
|
+
@conn_thread = Thread.new do
|
111
|
+
begin
|
112
|
+
@connected = true
|
113
|
+
resp = @client.post(@url, body: @rd).flush
|
114
|
+
rescue Exception => e
|
115
|
+
@connection_error = e
|
116
|
+
ensure
|
117
|
+
@connected = false
|
118
|
+
end
|
119
|
+
|
120
|
+
if resp&.status == 202
|
121
|
+
debug 'APM Server responded with status 202'
|
122
|
+
elsif resp
|
123
|
+
error "APM Server reponded with an error:\n%p", resp.body.to_s
|
124
|
+
end
|
125
|
+
|
126
|
+
resp
|
127
|
+
end
|
128
|
+
end
|
129
|
+
# rubocop:enable Metrics/MethodLength
|
130
|
+
|
131
|
+
def append(str)
|
132
|
+
bytes =
|
133
|
+
if @config.http_compression
|
134
|
+
@bytes_sent = @wr.tell
|
135
|
+
else
|
136
|
+
@bytes_sent += str.bytesize
|
137
|
+
end
|
138
|
+
|
139
|
+
debug 'Bytes sent during this request: %d', bytes
|
140
|
+
|
141
|
+
@wr.puts(str)
|
142
|
+
end
|
143
|
+
|
144
|
+
def schedule_closing
|
145
|
+
@close_task =
|
146
|
+
Concurrent::ScheduledTask.execute(@config.api_request_time) do
|
147
|
+
flush
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def enable_compression!
|
152
|
+
@wr.binmode
|
153
|
+
@wr = Zlib::GzipWriter.new(@wr)
|
154
|
+
end
|
155
|
+
|
156
|
+
def reset!
|
157
|
+
@bytes_sent = 0
|
158
|
+
@connected = false
|
159
|
+
@connection_error = nil
|
160
|
+
@close_task = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def wait_for_connection
|
164
|
+
until @connected
|
165
|
+
if (exception = @connection_error)
|
166
|
+
@wr&.close
|
167
|
+
raise FailedToConnectError, exception
|
168
|
+
end
|
169
|
+
|
170
|
+
sleep 0.01
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|