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.

Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +7 -1
  5. data/CHANGELOG.md +45 -0
  6. data/Gemfile +17 -12
  7. data/bench/app.rb +1 -2
  8. data/bench/benchmark.rb +1 -1
  9. data/bench/stackprof.rb +1 -1
  10. data/docs/api.asciidoc +115 -76
  11. data/docs/configuration.asciidoc +232 -167
  12. data/docs/context.asciidoc +7 -3
  13. data/docs/custom-instrumentation.asciidoc +17 -28
  14. data/docs/index.asciidoc +13 -7
  15. data/docs/supported-technologies.asciidoc +65 -0
  16. data/elastic-apm.gemspec +3 -2
  17. data/lib/elastic_apm.rb +272 -121
  18. data/lib/elastic_apm/agent.rb +56 -107
  19. data/lib/elastic_apm/config.rb +130 -106
  20. data/lib/elastic_apm/config/duration.rb +25 -0
  21. data/lib/elastic_apm/config/size.rb +28 -0
  22. data/lib/elastic_apm/context_builder.rb +1 -0
  23. data/lib/elastic_apm/deprecations.rb +19 -0
  24. data/lib/elastic_apm/error.rb +5 -2
  25. data/lib/elastic_apm/error/exception.rb +1 -1
  26. data/lib/elastic_apm/error_builder.rb +5 -0
  27. data/lib/elastic_apm/instrumenter.rb +121 -53
  28. data/lib/elastic_apm/internal_error.rb +1 -0
  29. data/lib/elastic_apm/{log.rb → logging.rb} +16 -11
  30. data/lib/elastic_apm/metadata.rb +20 -0
  31. data/lib/elastic_apm/metadata/process_info.rb +26 -0
  32. data/lib/elastic_apm/metadata/service_info.rb +56 -0
  33. data/lib/elastic_apm/metadata/system_info.rb +30 -0
  34. data/lib/elastic_apm/middleware.rb +31 -15
  35. data/lib/elastic_apm/normalizers/action_controller.rb +1 -1
  36. data/lib/elastic_apm/normalizers/action_mailer.rb +1 -1
  37. data/lib/elastic_apm/normalizers/action_view.rb +3 -3
  38. data/lib/elastic_apm/normalizers/active_record.rb +2 -1
  39. data/lib/elastic_apm/railtie.rb +1 -1
  40. data/lib/elastic_apm/span.rb +59 -29
  41. data/lib/elastic_apm/span/context.rb +30 -4
  42. data/lib/elastic_apm/span_helpers.rb +1 -1
  43. data/lib/elastic_apm/spies/delayed_job.rb +7 -7
  44. data/lib/elastic_apm/spies/elasticsearch.rb +4 -4
  45. data/lib/elastic_apm/spies/http.rb +38 -0
  46. data/lib/elastic_apm/spies/mongo.rb +22 -11
  47. data/lib/elastic_apm/spies/net_http.rb +7 -4
  48. data/lib/elastic_apm/spies/rake.rb +5 -6
  49. data/lib/elastic_apm/spies/redis.rb +1 -1
  50. data/lib/elastic_apm/spies/sequel.rb +9 -7
  51. data/lib/elastic_apm/spies/sidekiq.rb +5 -5
  52. data/lib/elastic_apm/spies/tilt.rb +2 -2
  53. data/lib/elastic_apm/sql_summarizer.rb +3 -3
  54. data/lib/elastic_apm/stacktrace_builder.rb +6 -6
  55. data/lib/elastic_apm/subscriber.rb +3 -3
  56. data/lib/elastic_apm/traceparent.rb +62 -0
  57. data/lib/elastic_apm/transaction.rb +62 -93
  58. data/lib/elastic_apm/transport/base.rb +98 -0
  59. data/lib/elastic_apm/transport/connection.rb +175 -0
  60. data/lib/elastic_apm/transport/filters.rb +45 -0
  61. data/lib/elastic_apm/transport/filters/request_body_filter.rb +31 -0
  62. data/lib/elastic_apm/transport/filters/secrets_filter.rb +59 -0
  63. data/lib/elastic_apm/transport/serializers.rb +58 -0
  64. data/lib/elastic_apm/transport/serializers/error_serializer.rb +59 -0
  65. data/lib/elastic_apm/transport/serializers/span_serializer.rb +30 -0
  66. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +33 -0
  67. data/lib/elastic_apm/transport/worker.rb +73 -0
  68. data/lib/elastic_apm/util.rb +11 -8
  69. data/lib/elastic_apm/version.rb +1 -1
  70. metadata +40 -21
  71. data/.travis.yml +0 -5
  72. data/docs/troubleshooting.asciidoc +0 -28
  73. data/lib/elastic_apm/filters.rb +0 -46
  74. data/lib/elastic_apm/filters/request_body_filter.rb +0 -33
  75. data/lib/elastic_apm/filters/secrets_filter.rb +0 -59
  76. data/lib/elastic_apm/http.rb +0 -139
  77. data/lib/elastic_apm/process_info.rb +0 -24
  78. data/lib/elastic_apm/serializers.rb +0 -28
  79. data/lib/elastic_apm/serializers/errors.rb +0 -61
  80. data/lib/elastic_apm/serializers/transactions.rb +0 -51
  81. data/lib/elastic_apm/service_info.rb +0 -54
  82. data/lib/elastic_apm/system_info.rb +0 -28
  83. data/lib/elastic_apm/util/dig.rb +0 -31
  84. data/lib/elastic_apm/util/inspector.rb +0 -61
  85. data/lib/elastic_apm/worker.rb +0 -106
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/metadata/service_info'
4
+ require 'elastic_apm/metadata/system_info'
5
+ require 'elastic_apm/metadata/process_info'
6
+
7
+ module ElasticAPM
8
+ # @api private
9
+ module Metadata
10
+ def self.build(config)
11
+ {
12
+ metadata: {
13
+ service: Metadata::ServiceInfo.build(config),
14
+ process: Metadata::ProcessInfo.build(config),
15
+ system: Metadata::SystemInfo.build(config)
16
+ }
17
+ }.to_json
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metadata
5
+ # @api private
6
+ class ProcessInfo
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def build
12
+ pid = $PID || Process.pid
13
+ return unless pid
14
+ {
15
+ argv: ARGV,
16
+ pid: pid,
17
+ title: $PROGRAM_NAME
18
+ }
19
+ end
20
+
21
+ def self.build(config)
22
+ new(config).build
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metadata
5
+ # @api private
6
+ class ServiceInfo
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ # rubocop:disable Metrics/MethodLength
12
+ def build
13
+ base = {
14
+ name: @config.service_name,
15
+ environment: @config.environment,
16
+ agent: {
17
+ name: 'ruby',
18
+ version: VERSION
19
+ },
20
+ framework: nil,
21
+ language: {
22
+ name: 'ruby',
23
+ version: RUBY_VERSION
24
+ },
25
+ runtime: runtime,
26
+ version: @config.service_version || Util.git_sha
27
+ }
28
+
29
+ if @config.framework_name
30
+ base[:framework] = {
31
+ name: @config.framework_name,
32
+ version: @config.framework_version
33
+ }
34
+ end
35
+
36
+ base
37
+ end
38
+ # rubocop:enable Metrics/MethodLength
39
+
40
+ def self.build(config)
41
+ new(config).build
42
+ end
43
+
44
+ private
45
+
46
+ def runtime
47
+ case RUBY_ENGINE
48
+ when 'ruby'
49
+ { name: RUBY_ENGINE, version: RUBY_VERSION || RUBY_ENGINE_VERSION }
50
+ when 'jruby'
51
+ { name: RUBY_ENGINE, version: JRUBY_VERSION || RUBY_ENGINE_VERSION }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metadata
5
+ # @api private
6
+ class SystemInfo
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def build
12
+ {
13
+ hostname: @config.hostname || `hostname`.chomp,
14
+ architecture: platform.cpu,
15
+ platform: platform.os
16
+ }
17
+ end
18
+
19
+ def self.build(config)
20
+ new(config).build
21
+ end
22
+
23
+ private
24
+
25
+ def platform
26
+ @platform ||= Gem::Platform.local
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,40 +1,47 @@
1
+ #
1
2
  # frozen_string_literal: true
2
3
 
4
+ require 'elastic_apm/traceparent'
5
+
3
6
  module ElasticAPM
4
7
  # @api private
5
8
  class Middleware
9
+ include Logging
10
+
6
11
  def initialize(app)
7
12
  @app = app
8
13
  end
9
14
 
10
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
15
+ # rubocop:disable Metrics/MethodLength
11
16
  def call(env)
12
17
  begin
13
18
  if running? && !path_ignored?(env)
14
- transaction = build_transaction(env)
19
+ transaction = start_transaction(env)
15
20
  end
16
21
 
17
22
  resp = @app.call env
18
-
19
- status, headers, body = resp
20
- submit_transaction(transaction, status, headers, body) if transaction
21
23
  rescue InternalError
22
24
  raise # Don't report ElasticAPM errors
23
25
  rescue ::Exception => e
24
26
  ElasticAPM.report(e, handled: false)
25
- transaction.submit('HTTP 5xx', status: 500) if transaction
26
27
  raise
27
28
  ensure
28
- transaction.release if transaction
29
+ if resp && transaction
30
+ status, headers, _body = resp
31
+ transaction.add_response(status, headers: headers)
32
+ end
33
+
34
+ ElasticAPM.end_transaction http_result(status)
29
35
  end
30
36
 
31
37
  resp
32
38
  end
33
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
39
+ # rubocop:enable Metrics/MethodLength
40
+
41
+ private
34
42
 
35
- def submit_transaction(transaction, status, headers, _body)
36
- result = "HTTP #{status.to_s[0]}xx"
37
- transaction.submit(result, status: status, headers: headers)
43
+ def http_result(status)
44
+ status && "HTTP #{status.to_s[0]}xx"
38
45
  end
39
46
 
40
47
  def path_ignored?(env)
@@ -43,9 +50,18 @@ module ElasticAPM
43
50
  end
44
51
  end
45
52
 
46
- def build_transaction(env)
47
- ElasticAPM.transaction 'Rack', 'request',
48
- context: ElasticAPM.build_context(env)
53
+ def start_transaction(env)
54
+ ElasticAPM.start_transaction 'Rack', 'request',
55
+ context: ElasticAPM.build_context(env),
56
+ traceparent: traceparent(env)
57
+ end
58
+
59
+ def traceparent(env)
60
+ return unless (header = env['HTTP_ELASTIC_APM_TRACEPARENT'])
61
+ Traceparent.parse(header)
62
+ rescue Traceparent::InvalidTraceparentHeader
63
+ warn "Couldn't parse invalid traceparent header: #{header.inspect}"
64
+ nil
49
65
  end
50
66
 
51
67
  def running?
@@ -53,7 +69,7 @@ module ElasticAPM
53
69
  end
54
70
 
55
71
  def config
56
- ElasticAPM.agent.config
72
+ @config ||= ElasticAPM.agent.config
57
73
  end
58
74
  end
59
75
  end
@@ -6,7 +6,7 @@ module ElasticAPM
6
6
  # @api private
7
7
  class ProcessActionNormalizer < Normalizer
8
8
  register 'process_action.action_controller'
9
- TYPE = 'app.controller.action'.freeze
9
+ TYPE = 'app.controller.action'
10
10
 
11
11
  def normalize(transaction, _name, payload)
12
12
  transaction.name = endpoint(payload)
@@ -6,7 +6,7 @@ module ElasticAPM
6
6
  # @api private
7
7
  class ProcessActionNormalizer < Normalizer
8
8
  register 'process.action_mailer'
9
- TYPE = 'app.mailer.action'.freeze
9
+ TYPE = 'app.mailer.action'
10
10
 
11
11
  def normalize(_transaction, _name, payload)
12
12
  [endpoint(payload), TYPE, nil]
@@ -41,7 +41,7 @@ module ElasticAPM
41
41
  # @api private
42
42
  class RenderTemplateNormalizer < RenderNormalizer
43
43
  register 'render_template.action_view'
44
- TYPE = 'template.view'.freeze
44
+ TYPE = 'template.view'
45
45
 
46
46
  def normalize(_transaction, _name, payload)
47
47
  normalize_render(payload, TYPE)
@@ -51,7 +51,7 @@ module ElasticAPM
51
51
  # @api private
52
52
  class RenderPartialNormalizer < RenderNormalizer
53
53
  register 'render_partial.action_view'
54
- TYPE = 'template.view.partial'.freeze
54
+ TYPE = 'template.view.partial'
55
55
 
56
56
  def normalize(_transaction, _name, payload)
57
57
  normalize_render(payload, TYPE)
@@ -61,7 +61,7 @@ module ElasticAPM
61
61
  # @api private
62
62
  class RenderCollectionNormalizer < RenderNormalizer
63
63
  register 'render_collection.action_view'
64
- TYPE = 'template.view.collection'.freeze
64
+ TYPE = 'template.view.collection'
65
65
 
66
66
  def normalize(_transaction, _name, payload)
67
67
  normalize_render(payload, TYPE)
@@ -20,7 +20,8 @@ module ElasticAPM
20
20
  return :skip if %w[SCHEMA CACHE].include?(payload[:name])
21
21
 
22
22
  name = summarize(payload[:sql]) || payload[:name]
23
- context = Span::Context.new(statement: payload[:sql], type: 'sql')
23
+ context =
24
+ Span::Context.new(db: { statement: payload[:sql], type: 'sql' })
24
25
  [name, @type, context]
25
26
  end
26
27
 
@@ -26,7 +26,7 @@ module ElasticAPM
26
26
  app.middleware.insert 0, Middleware
27
27
  end
28
28
  rescue StandardError => e
29
- Rails.logger.error "#{Log::PREFIX}Failed to start: #{e.message}"
29
+ Rails.logger.error "#{Logging::PREFIX}Failed to start: #{e.message}"
30
30
  Rails.logger.debug e.backtrace.join("\n")
31
31
  end
32
32
  end
@@ -7,61 +7,82 @@ require 'elastic_apm/span/context'
7
7
  module ElasticAPM
8
8
  # @api private
9
9
  class Span
10
- DEFAULT_TYPE = 'custom'.freeze
10
+ DEFAULT_TYPE = 'custom'
11
11
 
12
12
  # rubocop:disable Metrics/ParameterLists
13
13
  def initialize(
14
- transaction,
15
- id,
16
14
  name,
17
15
  type = nil,
16
+ transaction: nil,
18
17
  parent: nil,
19
- context: nil
18
+ context: nil,
19
+ stacktrace_builder: nil
20
20
  )
21
- @transaction = transaction
22
- @id = id
23
21
  @name = name
24
22
  @type = type || DEFAULT_TYPE
25
- @parent = parent
26
- @context = context
27
23
 
28
- @stacktrace = nil
29
- @original_backtrace = nil
24
+ @id = SecureRandom.hex(8)
25
+
26
+ self.transaction = transaction
27
+ self.parent = parent
28
+
29
+ @context = context
30
+ @stacktrace_builder = stacktrace_builder
30
31
  end
31
32
  # rubocop:enable Metrics/ParameterLists
32
33
 
33
- attr_accessor :name, :type, :original_backtrace
34
- attr_reader :id, :context, :stacktrace, :duration, :parent, :relative_start
34
+ attr_accessor :name, :type, :original_backtrace, :parent
35
+ attr_reader :id, :context, :stacktrace, :duration,
36
+ :relative_start, :timestamp, :transaction_id, :trace_id
37
+
38
+ def transaction=(transaction)
39
+ @transaction_id = transaction&.id
40
+ @timestamp = transaction&.timestamp
41
+ @trace_id = transaction&.trace_id
42
+ end
43
+
44
+ def parent_id
45
+ @parent&.id || transaction_id
46
+ end
47
+
48
+ # life cycle
35
49
 
36
50
  def start
37
- @relative_start = Util.micros - @transaction.timestamp
51
+ raise 'Transaction needed to start span' unless transaction_id
52
+
53
+ @relative_start = Util.micros - timestamp
38
54
 
39
55
  self
40
56
  end
41
57
 
58
+ def stop
59
+ @duration = Util.micros - timestamp - relative_start
60
+ end
61
+
42
62
  def done
43
- @duration = Util.micros - @transaction.timestamp - relative_start
63
+ stop
44
64
 
45
- if original_backtrace && long_enough_for_stacktrace?
46
- @stacktrace =
47
- @transaction.instrumenter.agent.stacktrace_builder.build(
48
- original_backtrace, type: :span
49
- )
65
+ if should_build_stacktrace?
66
+ build_stacktrace
50
67
  end
51
68
 
52
- self.original_backtrace = nil # release it
53
-
54
69
  self
55
70
  end
56
71
 
57
- def done?
72
+ def stopped?
58
73
  !!duration
59
74
  end
60
75
 
76
+ def started?
77
+ !!relative_start
78
+ end
79
+
61
80
  def running?
62
- relative_start && !done?
81
+ started? && !stopped?
63
82
  end
64
83
 
84
+ # relations
85
+
65
86
  def inspect
66
87
  "<ElasticAPM::Span id:#{id}" \
67
88
  " name:#{name.inspect}" \
@@ -71,14 +92,23 @@ module ElasticAPM
71
92
 
72
93
  private
73
94
 
95
+ def should_build_stacktrace?
96
+ @stacktrace_builder && original_backtrace && long_enough_for_stacktrace?
97
+ end
98
+
99
+ def build_stacktrace
100
+ @stacktrace = @stacktrace_builder.build(original_backtrace, type: :span)
101
+ self.original_backtrace = nil # release it
102
+ end
103
+
74
104
  def long_enough_for_stacktrace?
75
- min_duration = @transaction.instrumenter.config.span_frames_min_duration
105
+ min_duration =
106
+ @stacktrace_builder.config.span_frames_min_duration_us
76
107
 
77
- case min_duration
78
- when -1 then true
79
- when 0 then false
80
- else duration / 1000 >= min_duration
81
- end
108
+ return true if min_duration < 0
109
+ return false if min_duration == 0
110
+
111
+ duration >= min_duration
82
112
  end
83
113
  end
84
114
  end
@@ -6,13 +6,39 @@ module ElasticAPM
6
6
  class Context
7
7
  include NaivelyHashable
8
8
 
9
- def initialize(args)
10
- args.each do |key, val|
11
- send(:"#{key}=", val)
9
+ def initialize(db: nil, http: nil)
10
+ @db = db && Db.new(db)
11
+ @http = http && Http.new(http)
12
+ end
13
+
14
+ attr_accessor :sync, :db, :http
15
+
16
+ # @api private
17
+ class Db
18
+ include NaivelyHashable
19
+
20
+ def initialize(instance: nil, statement: nil, type: nil, user: nil)
21
+ @instance = instance
22
+ @statement = statement
23
+ @type = type
24
+ @user = user
12
25
  end
26
+
27
+ attr_accessor :instance, :statement, :type, :user
13
28
  end
14
29
 
15
- attr_accessor :instance, :statement, :type, :user
30
+ # @api private
31
+ class Http
32
+ include NaivelyHashable
33
+
34
+ def initialize(url: nil, status_code: nil, method: nil)
35
+ @url = url
36
+ @status_code = status_code
37
+ @method = method
38
+ end
39
+
40
+ attr_accessor :url, :status_code, :method
41
+ end
16
42
  end
17
43
  end
18
44
  end