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.
@@ -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