elastic-apm 3.3.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ci/.jenkins_exclude.yml +4 -4
- data/.ci/.jenkins_ruby.yml +1 -1
- data/.ci/Jenkinsfile +5 -3
- data/.ci/jobs/apm-agent-ruby-downstream.yml +1 -0
- data/.ci/jobs/apm-agent-ruby-linting-mbp.yml +1 -0
- data/.ci/jobs/apm-agent-ruby-mbp.yml +1 -0
- data/.ci/prepare-git-context.sh +5 -2
- data/.github/ISSUE_TEMPLATE/Bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/Feature_request.md +17 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +6 -0
- data/CHANGELOG.asciidoc +25 -1
- data/Gemfile +6 -2
- data/bench/sql.rb +49 -0
- data/bin/build_docs +1 -1
- data/codecov.yml +32 -0
- data/docs/api.asciidoc +37 -0
- data/docs/configuration.asciidoc +18 -1
- data/docs/supported-technologies.asciidoc +20 -1
- data/lib/elastic_apm.rb +29 -5
- data/lib/elastic_apm/agent.rb +6 -2
- data/lib/elastic_apm/child_durations.rb +9 -4
- data/lib/elastic_apm/config.rb +8 -1
- data/lib/elastic_apm/config/options.rb +3 -4
- data/lib/elastic_apm/context/response.rb +10 -2
- data/lib/elastic_apm/instrumenter.rb +20 -11
- data/lib/elastic_apm/normalizers/rails/active_record.rb +12 -5
- data/lib/elastic_apm/rails.rb +1 -10
- data/lib/elastic_apm/railtie.rb +1 -1
- data/lib/elastic_apm/span.rb +3 -2
- data/lib/elastic_apm/span/context.rb +26 -44
- data/lib/elastic_apm/span/context/db.rb +19 -0
- data/lib/elastic_apm/span/context/destination.rb +44 -0
- data/lib/elastic_apm/span/context/http.rb +26 -0
- data/lib/elastic_apm/spies/elasticsearch.rb +18 -5
- data/lib/elastic_apm/spies/faraday.rb +36 -18
- data/lib/elastic_apm/spies/http.rb +16 -2
- data/lib/elastic_apm/spies/mongo.rb +5 -0
- data/lib/elastic_apm/spies/net_http.rb +27 -7
- data/lib/elastic_apm/spies/sequel.rb +25 -15
- data/lib/elastic_apm/spies/shoryuken.rb +48 -0
- data/lib/elastic_apm/spies/sneakers.rb +57 -0
- data/lib/elastic_apm/sql.rb +19 -0
- data/lib/elastic_apm/sql/signature.rb +152 -0
- data/lib/elastic_apm/sql/tokenizer.rb +247 -0
- data/lib/elastic_apm/sql/tokens.rb +46 -0
- data/lib/elastic_apm/sql_summarizer.rb +1 -2
- data/lib/elastic_apm/transaction.rb +11 -11
- data/lib/elastic_apm/transport/connection/proxy_pipe.rb +2 -2
- data/lib/elastic_apm/transport/headers.rb +4 -0
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +24 -7
- data/lib/elastic_apm/version.rb +1 -1
- metadata +16 -3
- data/.github/workflows/main.yml +0 -14
@@ -4,53 +4,35 @@ module ElasticAPM
|
|
4
4
|
class Span
|
5
5
|
# @api private
|
6
6
|
class Context
|
7
|
-
def initialize(
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
def initialize(
|
8
|
+
db: nil,
|
9
|
+
destination: nil,
|
10
|
+
http: nil,
|
11
|
+
labels: {},
|
12
|
+
sync: nil
|
13
|
+
)
|
14
|
+
@sync = sync
|
15
|
+
@db = db && Db.new(**db)
|
16
|
+
@http = http && Http.new(**http)
|
17
|
+
@destination =
|
18
|
+
case destination
|
19
|
+
when Destination then destination
|
20
|
+
when Hash then Destination.new(**destination)
|
21
|
+
end
|
11
22
|
@labels = labels
|
12
23
|
end
|
13
24
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@type = type
|
22
|
-
@user = user
|
23
|
-
end
|
24
|
-
|
25
|
-
attr_accessor :instance, :statement, :type, :user
|
26
|
-
end
|
27
|
-
|
28
|
-
# @api private
|
29
|
-
class Http
|
30
|
-
def initialize(url: nil, status_code: nil, method: nil)
|
31
|
-
@url = sanitize_url(url)
|
32
|
-
@status_code = status_code
|
33
|
-
@method = method
|
34
|
-
end
|
35
|
-
|
36
|
-
attr_accessor :url, :status_code, :method
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def sanitize_url(url)
|
41
|
-
uri = URI(url)
|
42
|
-
|
43
|
-
return url unless uri.userinfo
|
44
|
-
|
45
|
-
format(
|
46
|
-
'%s://%s@%s%s',
|
47
|
-
uri.scheme,
|
48
|
-
uri.user,
|
49
|
-
uri.hostname,
|
50
|
-
uri.path
|
51
|
-
)
|
52
|
-
end
|
53
|
-
end
|
25
|
+
attr_reader(
|
26
|
+
:db,
|
27
|
+
:destination,
|
28
|
+
:http,
|
29
|
+
:labels,
|
30
|
+
:sync
|
31
|
+
)
|
54
32
|
end
|
55
33
|
end
|
56
34
|
end
|
35
|
+
|
36
|
+
require 'elastic_apm/span/context/db'
|
37
|
+
require 'elastic_apm/span/context/http'
|
38
|
+
require 'elastic_apm/span/context/destination'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
class Span
|
5
|
+
class Context
|
6
|
+
# @api private
|
7
|
+
class Db
|
8
|
+
def initialize(instance: nil, statement: nil, type: nil, user: nil)
|
9
|
+
@instance = instance
|
10
|
+
@statement = statement
|
11
|
+
@type = type
|
12
|
+
@user = user
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :instance, :statement, :type, :user
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
class Span
|
5
|
+
class Context
|
6
|
+
# @api private
|
7
|
+
class Destination
|
8
|
+
def initialize(name: nil, resource: nil, type: nil)
|
9
|
+
@name = name
|
10
|
+
@resource = resource
|
11
|
+
@type = type
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :name, :resource, :type
|
15
|
+
|
16
|
+
def self.from_uri(uri_or_str, type: 'external')
|
17
|
+
uri = normalize(uri_or_str)
|
18
|
+
|
19
|
+
new(
|
20
|
+
name: only_scheme_and_host(uri),
|
21
|
+
resource: "#{uri.host}:#{uri.port}",
|
22
|
+
type: type
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.only_scheme_and_host(uri_or_str)
|
27
|
+
uri = normalize(uri_or_str)
|
28
|
+
uri.path = ''
|
29
|
+
uri.password = uri.query = uri.fragment = nil
|
30
|
+
uri.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
private
|
35
|
+
|
36
|
+
def normalize(uri_or_str)
|
37
|
+
return uri_or_str.dup if uri_or_str.is_a?(URI)
|
38
|
+
URI(uri_or_str)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
class Span
|
5
|
+
class Context
|
6
|
+
# @api private
|
7
|
+
class Http
|
8
|
+
def initialize(url: nil, status_code: nil, method: nil)
|
9
|
+
@url = sanitize_url(url)
|
10
|
+
@status_code = status_code
|
11
|
+
@method = method
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :url, :status_code, :method
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def sanitize_url(uri_or_str)
|
19
|
+
uri = uri_or_str.is_a?(URI) ? uri_or_str.dup : URI(uri_or_str)
|
20
|
+
uri.password = nil
|
21
|
+
uri.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -6,7 +6,9 @@ module ElasticAPM
|
|
6
6
|
# @api private
|
7
7
|
class ElasticsearchSpy
|
8
8
|
NAME_FORMAT = '%s %s'
|
9
|
-
TYPE = 'db
|
9
|
+
TYPE = 'db'
|
10
|
+
SUBTYPE = 'elasticsearch'
|
11
|
+
|
10
12
|
def install
|
11
13
|
::Elasticsearch::Transport::Client.class_eval do
|
12
14
|
alias perform_request_without_apm perform_request
|
@@ -14,11 +16,22 @@ module ElasticAPM
|
|
14
16
|
def perform_request(method, path, *args, &block)
|
15
17
|
name = format(NAME_FORMAT, method, path)
|
16
18
|
statement = args[0].is_a?(String) ? args[0] : args[0].to_json
|
17
|
-
context = Span::Context.new(db: { statement: statement })
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
context = Span::Context.new(
|
21
|
+
db: { statement: statement },
|
22
|
+
destination: {
|
23
|
+
name: SUBTYPE,
|
24
|
+
resource: SUBTYPE,
|
25
|
+
type: TYPE
|
26
|
+
}
|
27
|
+
)
|
28
|
+
|
29
|
+
ElasticAPM.with_span(
|
30
|
+
name,
|
31
|
+
TYPE,
|
32
|
+
subtype: SUBTYPE,
|
33
|
+
context: context
|
34
|
+
) { perform_request_without_apm(method, path, *args, &block) }
|
22
35
|
end
|
23
36
|
end
|
24
37
|
end
|
@@ -16,7 +16,6 @@ module ElasticAPM
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
20
19
|
# rubocop:disable Metrics/CyclomaticComplexity
|
21
20
|
def install
|
22
21
|
::Faraday::Connection.class_eval do
|
@@ -27,40 +26,59 @@ module ElasticAPM
|
|
27
26
|
return run_request_without_apm(method, url, body, headers, &block)
|
28
27
|
end
|
29
28
|
|
30
|
-
|
31
|
-
url_prefix.host
|
32
|
-
elsif url.nil?
|
33
|
-
tmp_request = build_request(method) do |req|
|
34
|
-
yield(req) if block_given?
|
35
|
-
end
|
36
|
-
URI(tmp_request.path).host
|
37
|
-
else
|
38
|
-
URI(url).host
|
39
|
-
end
|
29
|
+
uri = URI(build_url(url))
|
40
30
|
|
41
|
-
|
31
|
+
# If url is set inside block it isn't available until yield,
|
32
|
+
# so we temporarily build the request to yield. This could be a
|
33
|
+
# problem if the block has side effects as it will be yielded twice
|
34
|
+
# ~mikker
|
35
|
+
unless uri.host
|
36
|
+
tmp_request = build_request(method) do |req|
|
37
|
+
yield(req) if block_given?
|
38
|
+
end
|
39
|
+
uri = URI(tmp_request.path)
|
40
|
+
end
|
41
|
+
|
42
|
+
host = uri.host
|
43
|
+
|
44
|
+
upcased_method = method.to_s.upcase
|
45
|
+
|
46
|
+
destination = ElasticAPM::Span::Context::Destination.from_uri(uri)
|
47
|
+
|
48
|
+
context =
|
49
|
+
ElasticAPM::Span::Context.new(
|
50
|
+
http: { url: uri, method: upcased_method },
|
51
|
+
destination: destination
|
52
|
+
)
|
42
53
|
|
43
54
|
ElasticAPM.with_span(
|
44
|
-
|
55
|
+
"#{upcased_method} #{host}",
|
45
56
|
TYPE,
|
46
57
|
subtype: SUBTYPE,
|
47
|
-
action:
|
58
|
+
action: upcased_method,
|
59
|
+
context: context
|
48
60
|
) do |span|
|
49
61
|
ElasticAPM::Spies::FaradaySpy.without_net_http do
|
50
62
|
trace_context = span&.trace_context || transaction.trace_context
|
51
63
|
|
52
|
-
|
53
|
-
|
64
|
+
result =
|
65
|
+
run_request_without_apm(method, url, body, headers) do |req|
|
66
|
+
req['Elastic-Apm-Traceparent'] = trace_context.to_header
|
54
67
|
|
55
|
-
|
68
|
+
yield req if block_given?
|
69
|
+
end
|
70
|
+
|
71
|
+
if (http = span&.context&.http)
|
72
|
+
http.status_code = result.status.to_s
|
56
73
|
end
|
74
|
+
|
75
|
+
result
|
57
76
|
end
|
58
77
|
end
|
59
78
|
end
|
60
79
|
end
|
61
80
|
end
|
62
81
|
# rubocop:enable Metrics/CyclomaticComplexity
|
63
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
64
82
|
end
|
65
83
|
|
66
84
|
register 'Faraday', 'faraday', FaradaySpy.new
|
@@ -19,17 +19,31 @@ module ElasticAPM
|
|
19
19
|
method = req.verb.to_s.upcase
|
20
20
|
host = req.uri.host
|
21
21
|
|
22
|
+
destination =
|
23
|
+
ElasticAPM::Span::Context::Destination.from_uri(req.uri)
|
24
|
+
context = ElasticAPM::Span::Context.new(
|
25
|
+
http: { url: req.uri, method: method },
|
26
|
+
destination: destination
|
27
|
+
)
|
28
|
+
|
22
29
|
name = "#{method} #{host}"
|
23
30
|
|
24
31
|
ElasticAPM.with_span(
|
25
32
|
name,
|
26
33
|
TYPE,
|
27
34
|
subtype: SUBTYPE,
|
28
|
-
action: method
|
35
|
+
action: method,
|
36
|
+
context: context
|
29
37
|
) do |span|
|
30
38
|
trace_context = span&.trace_context || transaction.trace_context
|
31
39
|
req['Elastic-Apm-Traceparent'] = trace_context.to_header
|
32
|
-
perform_without_apm(req, options)
|
40
|
+
result = perform_without_apm(req, options)
|
41
|
+
|
42
|
+
if (http = span&.context&.http)
|
43
|
+
http.status_code = result.status.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
result
|
33
47
|
end
|
34
48
|
end
|
35
49
|
end
|
@@ -8,6 +8,7 @@ module ElasticAPM
|
|
8
8
|
KEY = :__elastic_apm_net_http_disabled
|
9
9
|
TYPE = 'ext'
|
10
10
|
SUBTYPE = 'net_http'
|
11
|
+
|
11
12
|
class << self
|
12
13
|
def disabled=(disabled)
|
13
14
|
Thread.current[KEY] = disabled
|
@@ -28,6 +29,7 @@ module ElasticAPM
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
31
33
|
def install
|
32
34
|
Net::HTTP.class_eval do
|
33
35
|
alias request_without_apm request
|
@@ -36,30 +38,48 @@ module ElasticAPM
|
|
36
38
|
unless (transaction = ElasticAPM.current_transaction)
|
37
39
|
return request_without_apm(req, body, &block)
|
38
40
|
end
|
41
|
+
|
39
42
|
if ElasticAPM::Spies::NetHTTPSpy.disabled?
|
40
43
|
return request_without_apm(req, body, &block)
|
41
44
|
end
|
42
45
|
|
43
|
-
host
|
44
|
-
method = req.method
|
46
|
+
host = req['host']&.split(':')&.first || address
|
47
|
+
method = req.method.to_s.upcase
|
48
|
+
path, query = req.path.split('?')
|
49
|
+
|
50
|
+
cls = use_ssl? ? URI::HTTPS : URI::HTTP
|
51
|
+
uri = cls.build([nil, host, port, path, query, nil])
|
45
52
|
|
46
|
-
|
53
|
+
destination =
|
54
|
+
ElasticAPM::Span::Context::Destination.from_uri(uri)
|
47
55
|
|
48
|
-
|
56
|
+
context =
|
57
|
+
ElasticAPM::Span::Context.new(
|
58
|
+
http: { url: uri, method: method },
|
59
|
+
destination: destination
|
60
|
+
)
|
49
61
|
|
50
62
|
ElasticAPM.with_span(
|
51
|
-
|
63
|
+
"#{method} #{host}",
|
52
64
|
TYPE,
|
53
65
|
subtype: SUBTYPE,
|
54
|
-
action: method
|
66
|
+
action: method,
|
67
|
+
context: context
|
55
68
|
) do |span|
|
56
69
|
trace_context = span&.trace_context || transaction.trace_context
|
57
70
|
req['Elastic-Apm-Traceparent'] = trace_context.to_header
|
58
|
-
request_without_apm(req, body, &block)
|
71
|
+
result = request_without_apm(req, body, &block)
|
72
|
+
|
73
|
+
if (http = span&.context&.http)
|
74
|
+
http.status_code = result.code
|
75
|
+
end
|
76
|
+
|
77
|
+
result
|
59
78
|
end
|
60
79
|
end
|
61
80
|
end
|
62
81
|
end
|
82
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
63
83
|
end
|
64
84
|
|
65
85
|
register 'Net::HTTP', 'net/http', NetHTTPSpy.new
|
@@ -1,22 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'elastic_apm/
|
3
|
+
require 'elastic_apm/sql'
|
4
4
|
|
5
5
|
module ElasticAPM
|
6
6
|
# @api private
|
7
7
|
module Spies
|
8
8
|
# @api private
|
9
9
|
class SequelSpy
|
10
|
-
TYPE = 'db
|
10
|
+
TYPE = 'db'
|
11
|
+
ACTION = 'query'
|
11
12
|
|
12
13
|
def self.summarizer
|
13
|
-
@summarizer
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.build_context(sql, opts)
|
17
|
-
Span::Context.new(
|
18
|
-
db: { statement: sql, type: 'sql', user: opts[:user] }
|
19
|
-
)
|
14
|
+
@summarizer = Sql.summarizer
|
20
15
|
end
|
21
16
|
|
22
17
|
def install
|
@@ -25,16 +20,31 @@ module ElasticAPM
|
|
25
20
|
::Sequel::Database.class_eval do
|
26
21
|
alias log_connection_yield_without_apm log_connection_yield
|
27
22
|
|
28
|
-
def log_connection_yield(sql,
|
23
|
+
def log_connection_yield(sql, connection, args = nil, &block)
|
29
24
|
unless ElasticAPM.current_transaction
|
30
|
-
return log_connection_yield_without_apm(
|
25
|
+
return log_connection_yield_without_apm(
|
26
|
+
sql, connection, args, &block
|
27
|
+
)
|
31
28
|
end
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
subtype = database_type.to_s
|
31
|
+
|
32
|
+
name =
|
33
|
+
ElasticAPM::Spies::SequelSpy.summarizer.summarize sql
|
34
|
+
|
35
|
+
context = ElasticAPM::Span::Context.new(
|
36
|
+
db: { statement: sql, type: 'sql', user: opts[:user] },
|
37
|
+
destination: { name: subtype, resource: subtype, type: TYPE }
|
38
|
+
)
|
36
39
|
|
37
|
-
ElasticAPM.with_span(
|
40
|
+
ElasticAPM.with_span(
|
41
|
+
name,
|
42
|
+
TYPE,
|
43
|
+
subtype: subtype,
|
44
|
+
action: ACTION,
|
45
|
+
context: context,
|
46
|
+
&block
|
47
|
+
)
|
38
48
|
end
|
39
49
|
end
|
40
50
|
end
|