elastic-apm 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
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
|