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