elastic-apm 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ci/Jenkinsfile +141 -121
- data/.ci/docker/jruby/11-jdk/Dockerfile +40 -0
- data/.ci/docker/jruby/12-jdk/Dockerfile +40 -0
- data/.ci/docker/jruby/13-jdk/Dockerfile +40 -0
- data/.ci/docker/jruby/7-jdk/Dockerfile +40 -0
- data/.ci/docker/jruby/8-jdk/Dockerfile +40 -0
- data/.ci/docker/jruby/README.md +31 -0
- data/.ci/docker/jruby/run.sh +73 -0
- data/.ci/docker/jruby/test.sh +13 -0
- data/.gitignore +3 -1
- data/.rubocop.yml +11 -2
- data/CHANGELOG.asciidoc +16 -0
- data/CONTRIBUTING.md +6 -4
- data/Gemfile +14 -9
- data/Rakefile +10 -5
- data/docker-compose.yml +5 -0
- data/docs/api.asciidoc +14 -2
- data/docs/configuration.asciidoc +5 -11
- data/docs/graphql.asciidoc +23 -0
- data/docs/index.asciidoc +4 -0
- data/docs/supported-technologies.asciidoc +51 -1
- data/docs/upgrading.asciidoc +45 -0
- data/lib/elastic_apm.rb +12 -0
- data/lib/elastic_apm/config.rb +7 -1
- data/lib/elastic_apm/graphql.rb +74 -0
- data/lib/elastic_apm/grpc.rb +82 -0
- data/lib/elastic_apm/resque.rb +12 -0
- data/lib/elastic_apm/span/context/destination.rb +20 -4
- data/lib/elastic_apm/spies/resque.rb +43 -0
- data/lib/elastic_apm/sql.rb +4 -4
- data/lib/elastic_apm/stacktrace_builder.rb +6 -1
- data/lib/elastic_apm/trace_context.rb +34 -11
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -1
- data/lib/elastic_apm/version.rb +1 -1
- metadata +16 -3
- data/CHANGELOG.md +0 -1
@@ -0,0 +1,45 @@
|
|
1
|
+
[[upgrading]]
|
2
|
+
== Upgrading
|
3
|
+
Upgrades between minor versions of the agent, like from 2.1 to 2.2 are always backwards compatible.
|
4
|
+
Upgrades that involve a major version bump often come with some backwards incompatible changes.
|
5
|
+
|
6
|
+
Before upgrading the agent, be sure to review the:
|
7
|
+
|
8
|
+
* <<release-notes,Agent release notes>>
|
9
|
+
* {apm-overview-ref-v}/agent-server-compatibility.html[Agent and Server compatibility chart]
|
10
|
+
|
11
|
+
[float]
|
12
|
+
[[end-of-life-dates]]
|
13
|
+
=== End of life dates
|
14
|
+
|
15
|
+
We love all our products, but sometimes we must say goodbye to a release so that we can continue moving
|
16
|
+
forward on future development and innovation.
|
17
|
+
Our https://www.elastic.co/support/eol[End of life policy] defines how long a given release is considered supported,
|
18
|
+
as well as how long a release is considered still in active development or maintenance.
|
19
|
+
The table below is a simplified description of this policy.
|
20
|
+
|
21
|
+
[options="header"]
|
22
|
+
|====
|
23
|
+
|Agent version |EOL Date |Maintained until
|
24
|
+
|3.5.x |2021-07-17 | 3.6
|
25
|
+
|3.4.x |2021-07-10 | 3.5
|
26
|
+
|3.3.x |2021-06-04 | 3.4
|
27
|
+
|3.2.x |2021-05-19 | 3.3
|
28
|
+
|3.1.x |2021-04-21 | 3.2
|
29
|
+
|3.0.x |2021-04-08 | 3.1
|
30
|
+
|2.12.x |2021-04-01 |4.0
|
31
|
+
|2.11.x |2021-03-23 |2.12
|
32
|
+
|2.10.x |2021-03-03 |2.11
|
33
|
+
|2.9.x |2020-12-25 |2.10
|
34
|
+
|2.8.x |2020-11-20 |2.9
|
35
|
+
|2.7.x |2020-11-07 |2.8
|
36
|
+
|2.6.x |2020-09-19 |2.7
|
37
|
+
|2.5.x |2020-09-01 |2.6
|
38
|
+
|2.4.x |2020-08-27 |2.5
|
39
|
+
|2.3.x |2020-07-29 |2.4
|
40
|
+
|2.2.x |2020-07-22 |2.3
|
41
|
+
|2.1.x |2020-06-04 |2.2
|
42
|
+
|2.0.x |2020-05-14 |2.1
|
43
|
+
|1.1.x |2020-03-07 |3.0
|
44
|
+
|1.0.x |2019-12-29 |1.1
|
45
|
+
|====
|
data/lib/elastic_apm.rb
CHANGED
@@ -22,10 +22,12 @@ require 'elastic_apm/instrumenter'
|
|
22
22
|
require 'elastic_apm/util'
|
23
23
|
|
24
24
|
require 'elastic_apm/middleware'
|
25
|
+
require 'elastic_apm/graphql'
|
25
26
|
|
26
27
|
require 'elastic_apm/rails' if defined?(::Rails::Railtie)
|
27
28
|
require 'elastic_apm/sinatra' if defined?(::Sinatra)
|
28
29
|
require 'elastic_apm/grape' if defined?(::Grape)
|
30
|
+
require 'elastic_apm/grpc' if defined?(::GRPC)
|
29
31
|
|
30
32
|
# ElasticAPM
|
31
33
|
module ElasticAPM
|
@@ -45,6 +47,16 @@ module ElasticAPM
|
|
45
47
|
Agent.stop
|
46
48
|
end
|
47
49
|
|
50
|
+
# Restarts the ElasticAPM Agent using the same config or a new
|
51
|
+
# one, if it is provided.
|
52
|
+
# Starts the agent if it is not running.
|
53
|
+
# Stops and starts the agent if it is running.
|
54
|
+
def restart(config = {})
|
55
|
+
config ||= agent&.config
|
56
|
+
stop if running?
|
57
|
+
start(config)
|
58
|
+
end
|
59
|
+
|
48
60
|
# @return [Boolean] Whether there's an [Agent] running
|
49
61
|
def running?
|
50
62
|
Agent.running?
|
data/lib/elastic_apm/config.rb
CHANGED
@@ -70,7 +70,7 @@ module ElasticAPM
|
|
70
70
|
option :transaction_max_spans, type: :int, default: 500
|
71
71
|
option :transaction_sample_rate, type: :float, default: 1.0
|
72
72
|
option :use_elastic_traceparent_header, type: :bool, default: true
|
73
|
-
option :
|
73
|
+
option :use_legacy_sql_parser, type: :bool, default: false
|
74
74
|
option :verify_server_cert, type: :bool, default: true
|
75
75
|
|
76
76
|
# rubocop:enable Metrics/LineLength, Layout/ExtraSpacing
|
@@ -119,6 +119,7 @@ module ElasticAPM
|
|
119
119
|
net_http
|
120
120
|
rake
|
121
121
|
redis
|
122
|
+
resque
|
122
123
|
sequel
|
123
124
|
shoryuken
|
124
125
|
sidekiq
|
@@ -221,6 +222,11 @@ module ElasticAPM
|
|
221
222
|
self.disable_instrumentations = value
|
222
223
|
end
|
223
224
|
|
225
|
+
def use_experimental_sql_parser=(value)
|
226
|
+
warn '[DEPRECATED] The new SQL parser is now the default. To use the old one, '
|
227
|
+
'use use_legacy_sql_parser and please report why you wish to do so.'
|
228
|
+
end
|
229
|
+
|
224
230
|
private
|
225
231
|
|
226
232
|
def load_config_file
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module GraphQL
|
6
|
+
TYPE = 'app'
|
7
|
+
SUBTYPE = 'graphql'
|
8
|
+
|
9
|
+
PREFIX = 'GraphQL: '
|
10
|
+
UNNAMED = '[unnamed]'
|
11
|
+
MULTIPLE_QUERIES = '[multiple-queries]'
|
12
|
+
CONCATENATOR = '+'
|
13
|
+
MAX_NUMBER_OF_QUERIES_FOR_NAME = 5
|
14
|
+
|
15
|
+
KEYS_TO_NAME = {
|
16
|
+
'lex' => 'graphql.lex',
|
17
|
+
'parse' => 'graphql.parse',
|
18
|
+
'validate' => 'graphql.validate',
|
19
|
+
'analyze_multiplex' => 'graphql.analyze_multiplex',
|
20
|
+
'analyze_query' => 'graphql.analyze_query',
|
21
|
+
'execute_multiplex' => 'graphql.execute_multiplex',
|
22
|
+
'execute_query' => 'graphql.execute_query',
|
23
|
+
'execute_query_lazy' => 'graphql.execute_query_lazy',
|
24
|
+
'authorized_lazy' => 'graphql.authorized_lazy',
|
25
|
+
'resolve_type' => 'graphql.resolve_type',
|
26
|
+
'resolve_type_lazy' => 'graphql.resolve_type_lazy'
|
27
|
+
|
28
|
+
# These are available but deliberately left out as they are mostly noise.
|
29
|
+
# "execute_field" => "graphql.execute_field",
|
30
|
+
# "execute_field_lazy" => "graphql.execute_field_lazy",
|
31
|
+
# "authorized" => "graphql.authorized",
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
def self.trace(key, data)
|
35
|
+
return yield unless KEYS_TO_NAME.key?(key)
|
36
|
+
return yield unless (transaction = ElasticAPM.current_transaction)
|
37
|
+
|
38
|
+
if key == 'execute_query'
|
39
|
+
transaction.name =
|
40
|
+
"#{PREFIX}#{query_name(data[:query])}"
|
41
|
+
end
|
42
|
+
|
43
|
+
results =
|
44
|
+
ElasticAPM.with_span(
|
45
|
+
KEYS_TO_NAME.fetch(key, key),
|
46
|
+
TYPE, subtype: SUBTYPE, action: key
|
47
|
+
) { yield }
|
48
|
+
|
49
|
+
if key == 'execute_multiplex'
|
50
|
+
transaction.name = "#{PREFIX}#{concat_names(results)}"
|
51
|
+
end
|
52
|
+
|
53
|
+
results
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
private
|
58
|
+
|
59
|
+
def query_name(query)
|
60
|
+
query.operation_name || UNNAMED
|
61
|
+
end
|
62
|
+
|
63
|
+
def concat_names(results)
|
64
|
+
if results.length > MAX_NUMBER_OF_QUERIES_FOR_NAME
|
65
|
+
return MULTIPLE_QUERIES
|
66
|
+
end
|
67
|
+
|
68
|
+
results.map do |result|
|
69
|
+
query_name(result.query)
|
70
|
+
end.join(CONCATENATOR)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
class GRPC
|
6
|
+
# @api private
|
7
|
+
class ClientInterceptor < ::GRPC::ClientInterceptor
|
8
|
+
TYPE = 'external'
|
9
|
+
SUBTYPE = 'grpc'
|
10
|
+
|
11
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
12
|
+
def request_response(request:, call:, method:, metadata:)
|
13
|
+
return yield unless (transaction = ElasticAPM.current_transaction)
|
14
|
+
if (trace_context = transaction.trace_context)
|
15
|
+
trace_context.apply_headers { |k, v| metadata[k.downcase] = v }
|
16
|
+
end
|
17
|
+
|
18
|
+
ElasticAPM.with_span(
|
19
|
+
method, TYPE,
|
20
|
+
subtype: SUBTYPE,
|
21
|
+
context: span_context(call)
|
22
|
+
) do
|
23
|
+
yield
|
24
|
+
end
|
25
|
+
end
|
26
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def span_context(call)
|
31
|
+
peer = call.instance_variable_get(:@wrapped)&.peer
|
32
|
+
return unless peer
|
33
|
+
|
34
|
+
split_peer = URI.split(peer)
|
35
|
+
destination = ElasticAPM::Span::Context::Destination.new(
|
36
|
+
type: TYPE,
|
37
|
+
name: SUBTYPE,
|
38
|
+
resource: peer,
|
39
|
+
address: split_peer[0],
|
40
|
+
port: split_peer[6]
|
41
|
+
)
|
42
|
+
ElasticAPM::Span::Context.new(destination: destination)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
class ServerInterceptor < ::GRPC::ClientInterceptor
|
48
|
+
TYPE = 'request'
|
49
|
+
|
50
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
51
|
+
def request_response(request:, call:, method:)
|
52
|
+
transaction = start_transaction(call)
|
53
|
+
yield
|
54
|
+
transaction.done 'success'
|
55
|
+
rescue ::Exception => e
|
56
|
+
ElasticAPM.report(e, handled: false)
|
57
|
+
transaction.done 'error'
|
58
|
+
raise
|
59
|
+
ensure
|
60
|
+
ElasticAPM.end_transaction
|
61
|
+
end
|
62
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def start_transaction(call)
|
67
|
+
ElasticAPM.start_transaction(
|
68
|
+
'grpc',
|
69
|
+
'request',
|
70
|
+
trace_context: trace_context(call)
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def trace_context(call)
|
75
|
+
TraceContext.parse(metadata: call.metadata)
|
76
|
+
rescue TraceContext::InvalidTraceparentHeader
|
77
|
+
warn "Couldn't parse invalid trace context header: #{header.inspect}"
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# Defines a before_first_fork hook for starting the ElasticAPM agent
|
5
|
+
# with Resque.
|
6
|
+
module Resque
|
7
|
+
::Resque.before_first_fork do
|
8
|
+
::Resque.logger.debug('Starting ElasticAPM agent')
|
9
|
+
ElasticAPM.start
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -5,21 +5,37 @@ module ElasticAPM
|
|
5
5
|
class Context
|
6
6
|
# @api private
|
7
7
|
class Destination
|
8
|
-
def initialize(
|
8
|
+
def initialize(
|
9
|
+
name: nil,
|
10
|
+
resource: nil,
|
11
|
+
type: nil,
|
12
|
+
address: nil,
|
13
|
+
port: nil
|
14
|
+
)
|
9
15
|
@name = name
|
10
16
|
@resource = resource
|
11
17
|
@type = type
|
18
|
+
@address = address
|
19
|
+
@port = port
|
12
20
|
end
|
13
21
|
|
14
|
-
attr_reader
|
22
|
+
attr_reader(
|
23
|
+
:name,
|
24
|
+
:resource,
|
25
|
+
:type,
|
26
|
+
:address,
|
27
|
+
:port
|
28
|
+
)
|
15
29
|
|
16
|
-
def self.from_uri(uri_or_str, type: 'external')
|
30
|
+
def self.from_uri(uri_or_str, type: 'external', port: nil)
|
17
31
|
uri = normalize(uri_or_str)
|
18
32
|
|
19
33
|
new(
|
20
34
|
name: only_scheme_and_host(uri),
|
21
35
|
resource: "#{uri.host}:#{uri.port}",
|
22
|
-
type: type
|
36
|
+
type: type,
|
37
|
+
address: uri.hostname,
|
38
|
+
port: port || uri.port
|
23
39
|
)
|
24
40
|
end
|
25
41
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Spies
|
6
|
+
# @api private
|
7
|
+
class ResqueSpy
|
8
|
+
TYPE = 'Resque'
|
9
|
+
|
10
|
+
def install
|
11
|
+
install_perform_spy
|
12
|
+
install_after_fork_hook
|
13
|
+
end
|
14
|
+
|
15
|
+
def install_after_fork_hook
|
16
|
+
::Resque.after_fork do
|
17
|
+
ElasticAPM.restart
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def install_perform_spy
|
22
|
+
::Resque::Job.class_eval do
|
23
|
+
alias :perform_without_elastic_apm :perform
|
24
|
+
|
25
|
+
def perform
|
26
|
+
name = @payload && @payload['class']&.name
|
27
|
+
transaction = ElasticAPM.start_transaction(name, TYPE)
|
28
|
+
perform_without_elastic_apm
|
29
|
+
transaction.done 'success'
|
30
|
+
rescue ::Exception => e
|
31
|
+
ElasticAPM.report(e, handled: false)
|
32
|
+
transaction.done 'error'
|
33
|
+
raise
|
34
|
+
ensure
|
35
|
+
ElasticAPM.end_transaction
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
register 'Resque', 'resque', ResqueSpy.new
|
42
|
+
end
|
43
|
+
end
|
data/lib/elastic_apm/sql.rb
CHANGED
@@ -7,12 +7,12 @@ module ElasticAPM
|
|
7
7
|
# both implementations ~mikker
|
8
8
|
def self.summarizer
|
9
9
|
@summarizer ||=
|
10
|
-
if ElasticAPM.agent&.config&.
|
11
|
-
require 'elastic_apm/sql/signature'
|
12
|
-
Sql::Signature::Summarizer.new
|
13
|
-
else
|
10
|
+
if ElasticAPM.agent&.config&.use_legacy_sql_parser
|
14
11
|
require 'elastic_apm/sql_summarizer'
|
15
12
|
SqlSummarizer.new
|
13
|
+
else
|
14
|
+
require 'elastic_apm/sql/signature'
|
15
|
+
Sql::Signature::Summarizer.new
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -12,7 +12,12 @@ module ElasticAPM
|
|
12
12
|
RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}.freeze
|
13
13
|
JRUBY_ORG_REGEX = %r{org/jruby}.freeze
|
14
14
|
|
15
|
-
GEMS_PATH =
|
15
|
+
GEMS_PATH =
|
16
|
+
if defined?(Bundler) && Bundler.default_bundle_dir
|
17
|
+
Bundler.bundle_path.to_s
|
18
|
+
else
|
19
|
+
Gem.dir
|
20
|
+
end
|
16
21
|
|
17
22
|
def initialize(config)
|
18
23
|
@config = config
|
@@ -25,15 +25,30 @@ module ElasticAPM
|
|
25
25
|
:version, :trace_id, :id, :parent_id, :ensure_parent_id, :recorded?
|
26
26
|
|
27
27
|
class << self
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
29
|
+
def parse(legacy_header = nil, env: nil, metadata: nil)
|
30
|
+
unless legacy_header || env || metadata
|
31
|
+
raise ArgumentError, 'TraceContext expects env:, metadata: ' \
|
32
|
+
'or single argument header string'
|
32
33
|
end
|
33
34
|
|
34
|
-
|
35
|
+
if legacy_header
|
36
|
+
legacy_parse_from_header(legacy_header)
|
37
|
+
elsif env
|
38
|
+
trace_context_from_env(env)
|
39
|
+
elsif metadata
|
40
|
+
trace_context_from_metadata(metadata)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
44
|
+
|
45
|
+
private
|
35
46
|
|
36
|
-
|
47
|
+
def trace_context_from_env(env)
|
48
|
+
return unless (
|
49
|
+
header =
|
50
|
+
env['HTTP_ELASTIC_APM_TRACEPARENT'] || env['HTTP_TRACEPARENT']
|
51
|
+
)
|
37
52
|
|
38
53
|
parent = TraceContext::Traceparent.parse(header)
|
39
54
|
|
@@ -45,16 +60,24 @@ module ElasticAPM
|
|
45
60
|
new(traceparent: parent, tracestate: state)
|
46
61
|
end
|
47
62
|
|
48
|
-
|
63
|
+
def trace_context_from_metadata(metadata)
|
64
|
+
return unless (header = metadata['elastic-apm-traceparent'] ||
|
65
|
+
metadata['traceparent'])
|
66
|
+
|
67
|
+
parent = TraceContext::Traceparent.parse(header)
|
68
|
+
|
69
|
+
state =
|
70
|
+
if (header = metadata['tracestate'])
|
71
|
+
TraceContext::Tracestate.parse(header)
|
72
|
+
end
|
73
|
+
|
74
|
+
new(traceparent: parent, tracestate: state)
|
75
|
+
end
|
49
76
|
|
50
77
|
def legacy_parse_from_header(header)
|
51
78
|
parent = Traceparent.parse(header)
|
52
79
|
new(traceparent: parent)
|
53
80
|
end
|
54
|
-
|
55
|
-
def get_traceparent_header(env)
|
56
|
-
env['HTTP_ELASTIC_APM_TRACEPARENT'] || env['HTTP_TRACEPARENT']
|
57
|
-
end
|
58
81
|
end
|
59
82
|
|
60
83
|
def child
|