elastic-apm 3.5.0 → 3.6.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.
@@ -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?
@@ -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 :use_experimental_sql_parser, type: :bool, default: false
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(name: nil, resource: nil, type: nil)
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 :name, :resource, :type
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
@@ -7,12 +7,12 @@ module ElasticAPM
7
7
  # both implementations ~mikker
8
8
  def self.summarizer
9
9
  @summarizer ||=
10
- if ElasticAPM.agent&.config&.use_experimental_sql_parser
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 = defined?(Bundler) ? Bundler.bundle_path.to_s : Gem.dir
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
- def parse(legacy_header = nil, env: nil)
29
- if !legacy_header && !env
30
- raise ArgumentError, 'TraceContext expects either env: or single ' \
31
- 'argument header string'
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
- return legacy_parse_from_header(legacy_header) if legacy_header
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
- return unless (header = get_traceparent_header(env))
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
- private
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