elastic-apm 1.1.0 → 2.0.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.

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
@@ -1,5 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.4.2
5
- before_install: gem install bundler -v 1.16.0
@@ -1,28 +0,0 @@
1
- [[debugging]]
2
- === Debugging APM
3
-
4
- The agent not working for you? There are a few settings that might help:
5
-
6
- [float]
7
- [[debugging-debug-transactions]]
8
- ==== `debug_transactions`
9
-
10
- [options="header"]
11
- |============
12
- | Environment | `Config` key | Default
13
- | `ELASTIC_APM_DEBUG_TRANSACTIONS` | `debug_transactions` | `false`
14
- |============
15
-
16
- When on, Elastic APM will log a summary of each transaction when submitted.
17
-
18
- [float]
19
- [[debugging-debug-http]]
20
- ==== `debug_http`
21
-
22
- [options="header"]
23
- |============
24
- | Environment | `Config` key | Default
25
- | `ELASTIC_APM_DEBUG_HTTP` | `debug_http` | `false`
26
- |============
27
-
28
- When on, Elastic APM will log debug information from all the requests it makes to APM Server.
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'elastic_apm/filters/request_body_filter'
4
- require 'elastic_apm/filters/secrets_filter'
5
-
6
- module ElasticAPM
7
- # @api private
8
- module Filters
9
- def self.new(config)
10
- Container.new(config)
11
- end
12
-
13
- # @api private
14
- class Container
15
- def initialize(config)
16
- @config = config
17
- @filters = {
18
- request_body: RequestBodyFilter.new(config),
19
- secrets: SecretsFilter.new(config)
20
- }
21
- end
22
-
23
- attr_reader :config
24
-
25
- def add(key, filter)
26
- @filters[key] = filter
27
- end
28
-
29
- def remove(key)
30
- @filters.delete(key)
31
- end
32
-
33
- def apply(payload)
34
- @filters.reduce(payload) do |result, (_key, filter)|
35
- result = filter.call(result)
36
- break if result.nil?
37
- result
38
- end
39
- end
40
-
41
- def length
42
- @filters.length
43
- end
44
- end
45
- end
46
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticAPM
4
- module Filters
5
- # @api private
6
- class RequestBodyFilter
7
- FILTERED = '[FILTERED]'.freeze
8
-
9
- def initialize(config)
10
- @config = config
11
- end
12
-
13
- def call(payload)
14
- strip_body_from payload[:transactions]
15
- strip_body_from payload[:errors]
16
-
17
- payload
18
- end
19
-
20
- private
21
-
22
- def strip_body_from(arr)
23
- return unless arr
24
-
25
- arr.each do |entity|
26
- next unless (request = entity.dig(:context, :request))
27
-
28
- request[:body] = FILTERED
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticAPM
4
- module Filters
5
- # @api private
6
- class SecretsFilter
7
- FILTERED = '[FILTERED]'.freeze
8
-
9
- KEY_FILTERS = [
10
- /passw(or)?d/i,
11
- /^pw$/,
12
- /secret/i,
13
- /token/i,
14
- /api[-._]?key/i,
15
- /session[-._]?id/i
16
- ].freeze
17
-
18
- VALUE_FILTERS = [
19
- /^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}$/ # (probably) credit card number
20
- ].freeze
21
-
22
- def initialize(config)
23
- @config = config
24
- @key_filters = KEY_FILTERS + config.custom_key_filters
25
- end
26
-
27
- def call(payload)
28
- strip_from payload[:transactions], :context, :request, :headers
29
- strip_from payload[:transactions], :context, :response, :headers
30
- strip_from payload[:errors], :context, :request, :headers
31
- strip_from payload[:errors], :context, :response, :headers
32
-
33
- payload
34
- end
35
-
36
- def strip_from(events, *path)
37
- return unless events
38
-
39
- events.each do |event|
40
- next unless (headers = event.dig(*path))
41
-
42
- headers.each do |k, v|
43
- if filter_key?(k) || filter_value?(v)
44
- headers[k] = FILTERED
45
- end
46
- end
47
- end
48
- end
49
-
50
- def filter_key?(key)
51
- @key_filters.any? { |regex| key.match regex }
52
- end
53
-
54
- def filter_value?(value)
55
- VALUE_FILTERS.any? { |regex| value.match regex }
56
- end
57
- end
58
- end
59
- end
@@ -1,139 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'net/http'
4
- require 'openssl'
5
- require 'zlib'
6
-
7
- require 'elastic_apm/service_info'
8
- require 'elastic_apm/system_info'
9
- require 'elastic_apm/process_info'
10
- require 'elastic_apm/filters'
11
-
12
- module ElasticAPM
13
- # @api private
14
- class Http
15
- include Log
16
-
17
- USER_AGENT = "elastic-apm/ruby #{VERSION}".freeze
18
- ACCEPT = 'application/json'.freeze
19
- CONTENT_TYPE = 'application/json'.freeze
20
-
21
- def initialize(config, adapter: HttpAdapter)
22
- @config = config
23
- @adapter = adapter.new(config)
24
- @base_payload = {
25
- service: ServiceInfo.build(config),
26
- process: ProcessInfo.build(config),
27
- system: SystemInfo.build(config)
28
- }
29
- @filters = Filters.new(config)
30
- end
31
-
32
- attr_reader :filters
33
-
34
- def post(path, payload = {})
35
- payload.merge! @base_payload
36
-
37
- payload = filters.apply(payload)
38
- return if payload.nil?
39
-
40
- request = prepare_request path, payload.to_json
41
- response = @adapter.perform request
42
-
43
- return nil if response == HttpAdapter::DISABLED
44
- return response if response.is_a?(Net::HTTPSuccess)
45
-
46
- error 'POST returned an unsuccessful status code (%d)', response.code
47
- error "apm-server's response: %s", response.body
48
-
49
- response
50
- end
51
-
52
- private
53
-
54
- def prepare_request(path, data)
55
- @adapter.post url_for(path) do |req|
56
- req['Accept'] = ACCEPT
57
- req['Content-Type'] = CONTENT_TYPE
58
- req['User-Agent'] = USER_AGENT
59
-
60
- if (token = @config.secret_token)
61
- req['Authorization'] = "Bearer #{token}"
62
- end
63
-
64
- prepare_request_body! req, data
65
- end
66
- end
67
-
68
- def prepare_request_body!(req, data)
69
- if @config.http_compression &&
70
- data.bytesize > @config.compression_minimum_size
71
- deflated = Zlib.deflate data, @config.compression_level
72
-
73
- req['Content-Encoding'] = 'deflate'
74
- req['Content-Length'] = deflated.bytesize.to_s
75
- req.body = deflated
76
- else
77
- req['Content-Length'] = data.bytesize.to_s
78
- req.body = data
79
- end
80
- end
81
-
82
- def url_for(path)
83
- "#{@config.server_url}#{path}"
84
- end
85
- end
86
-
87
- # @api private
88
- class HttpAdapter
89
- DISABLED = 'disabled'.freeze
90
-
91
- def initialize(conf)
92
- @config = conf
93
- end
94
-
95
- def post(path)
96
- req = Net::HTTP::Post.new path
97
- yield req if block_given?
98
- req
99
- end
100
-
101
- def perform(req)
102
- return DISABLED if @config.disable_send?
103
-
104
- http.start do |http|
105
- http.request req
106
- end
107
- end
108
-
109
- private
110
-
111
- def http
112
- return @http if @http
113
-
114
- http = Net::HTTP.new server_uri.host, server_uri.port
115
- http.use_ssl = @config.use_ssl?
116
- http.verify_mode = verify_mode
117
- http.read_timeout = @config.http_read_timeout
118
- http.open_timeout = @config.http_open_timeout
119
-
120
- if @config.debug_http
121
- http.set_debug_output(@config.logger)
122
- end
123
-
124
- @http = http
125
- end
126
-
127
- def server_uri
128
- @server_uri ||= URI(@config.server_url)
129
- end
130
-
131
- def verify_mode
132
- if @config.use_ssl? && @config.verify_server_cert?
133
- OpenSSL::SSL::VERIFY_PEER
134
- else
135
- OpenSSL::SSL::VERIFY_NONE
136
- end
137
- end
138
- end
139
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticAPM
4
- # @api private
5
- class ProcessInfo
6
- def initialize(config)
7
- @config = config
8
- end
9
-
10
- def build
11
- pid = $PID || Process.pid
12
- return unless pid
13
- {
14
- argv: ARGV,
15
- pid: pid,
16
- title: $PROGRAM_NAME
17
- }
18
- end
19
-
20
- def self.build(config)
21
- new(config).build
22
- end
23
- end
24
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module ElasticAPM
6
- # @api private
7
- module Serializers
8
- # @api private
9
- class Serializer
10
- def initialize(config)
11
- @config = config
12
- end
13
-
14
- private
15
-
16
- def micros_to_time(micros)
17
- Time.at(ms(micros) / 1_000)
18
- end
19
-
20
- def ms(micros)
21
- micros.to_f / 1_000
22
- end
23
- end
24
- end
25
- end
26
-
27
- require 'elastic_apm/serializers/transactions'
28
- require 'elastic_apm/serializers/errors'
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticAPM
4
- module Serializers
5
- # @api private
6
- class Errors < Serializer
7
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
8
- def build(error)
9
- base = {
10
- id: error.id,
11
- culprit: error.culprit,
12
- timestamp: micros_to_time(error.timestamp).utc.iso8601(3),
13
- context: error.context.to_h
14
- }
15
-
16
- if (exception = error.exception)
17
- base[:exception] = build_exception exception
18
- end
19
-
20
- if (log = error.log)
21
- base[:log] = build_log log
22
- end
23
-
24
- if (transaction_id = error.transaction_id)
25
- base[:transaction] = { id: transaction_id }
26
- end
27
-
28
- base
29
- end
30
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
31
-
32
- def build_all(errors)
33
- { errors: Array(errors).map { |e| build(e) } }
34
- end
35
-
36
- private
37
-
38
- def build_exception(exception)
39
- {
40
- message: exception.message,
41
- type: exception.type,
42
- module: exception.module,
43
- code: exception.code,
44
- attributes: exception.attributes,
45
- stacktrace: exception.stacktrace.to_a,
46
- handled: exception.handled
47
- }
48
- end
49
-
50
- def build_log(log)
51
- {
52
- message: log.message,
53
- level: log.level,
54
- logger_name: log.logger_name,
55
- param_message: log.param_message,
56
- stacktrace: log.stacktrace.to_a
57
- }
58
- end
59
- end
60
- end
61
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticAPM
4
- module Serializers
5
- # @api private
6
- class Transactions < Serializer
7
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
8
- def build(transaction)
9
- base = {
10
- id: transaction.id,
11
- name: transaction.name,
12
- type: transaction.type,
13
- result: transaction.result.to_s,
14
- duration: ms(transaction.duration),
15
- timestamp: micros_to_time(transaction.timestamp).utc.iso8601(3),
16
- spans: transaction.spans.map { |s| build_span(s) },
17
- sampled: transaction.sampled,
18
- context: transaction.context.to_h
19
- }
20
-
21
- if transaction.dropped_spans > 0
22
- base[:span_count] = { dropped: { total: transaction.dropped_spans } }
23
- end
24
-
25
- base
26
- end
27
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
28
-
29
- def build_all(transactions)
30
- { transactions: Array(transactions).map { |t| build(t) } }
31
- end
32
-
33
- private
34
-
35
- # rubocop:disable Metrics/AbcSize
36
- def build_span(span)
37
- {
38
- id: span.id,
39
- parent: span.parent && span.parent.id,
40
- name: span.name,
41
- type: span.type,
42
- start: ms(span.relative_start),
43
- duration: ms(span.duration),
44
- context: span.context && { db: span.context.to_h },
45
- stacktrace: span.stacktrace.to_a
46
- }
47
- end
48
- # rubocop:enable Metrics/AbcSize
49
- end
50
- end
51
- end