elastic-apm 2.0.1 → 2.1.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +9 -9
  4. data/docs/advanced.asciidoc +8 -0
  5. data/docs/api.asciidoc +1 -1
  6. data/docs/configuration.asciidoc +1 -1
  7. data/docs/context.asciidoc +0 -1
  8. data/docs/index.asciidoc +7 -25
  9. data/docs/introduction.asciidoc +33 -0
  10. data/lib/elastic_apm/agent.rb +0 -1
  11. data/lib/elastic_apm/config.rb +35 -7
  12. data/lib/elastic_apm/context.rb +0 -2
  13. data/lib/elastic_apm/context/request.rb +0 -2
  14. data/lib/elastic_apm/context/request/socket.rb +0 -2
  15. data/lib/elastic_apm/context/request/url.rb +0 -2
  16. data/lib/elastic_apm/context/response.rb +0 -2
  17. data/lib/elastic_apm/context/user.rb +1 -3
  18. data/lib/elastic_apm/context_builder.rb +2 -2
  19. data/lib/elastic_apm/metadata.rb +11 -13
  20. data/lib/elastic_apm/metadata/process_info.rb +5 -13
  21. data/lib/elastic_apm/metadata/service_info.rb +38 -33
  22. data/lib/elastic_apm/metadata/system_info.rb +7 -13
  23. data/lib/elastic_apm/railtie.rb +2 -2
  24. data/lib/elastic_apm/span/context.rb +0 -6
  25. data/lib/elastic_apm/spies/faraday.rb +41 -0
  26. data/lib/elastic_apm/spies/net_http.rb +25 -0
  27. data/lib/elastic_apm/stacktrace/frame.rb +2 -0
  28. data/lib/elastic_apm/transport/base.rb +1 -2
  29. data/lib/elastic_apm/transport/connection.rb +4 -5
  30. data/lib/elastic_apm/transport/serializers.rb +22 -3
  31. data/lib/elastic_apm/transport/serializers/context_serializer.rb +69 -0
  32. data/lib/elastic_apm/transport/serializers/error_serializer.rb +11 -7
  33. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +64 -0
  34. data/lib/elastic_apm/transport/serializers/span_serializer.rb +49 -5
  35. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +10 -6
  36. data/lib/elastic_apm/transport/worker.rb +5 -3
  37. data/lib/elastic_apm/util.rb +7 -0
  38. data/lib/elastic_apm/util/prefixed_logger.rb +18 -0
  39. data/lib/elastic_apm/version.rb +1 -1
  40. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cefd73acad31a9561dbfb964d2654d8f702c8db1783df2fe7dee9849a87609f
4
- data.tar.gz: 8e4be6f5ee71a5972bc964f736d1cfaf652d307e2c080f7c9439273d966fc4d5
3
+ metadata.gz: df3dedd32ff6829fff4db353e0f6990d378d57313112cba444f717cb958565bd
4
+ data.tar.gz: 9e822ce49bc71ebf13ab70e0bf67e8c156a19f0f5c68e5dcca895fcebbf64edb
5
5
  SHA512:
6
- metadata.gz: d73c89eb559553948b4eb628d321e0aee6aeb9d754ba6b8737e628fd2858290b761800a1da761d65d3d83126e0a6cab634ead3a3a763174064d8a16d3ae8dda9
7
- data.tar.gz: 3b8b33c2083e82a433d650bf7708fe5686e47c3a4af71f233bbd52bafb202aa36ca1917e1f676deaeb587ad960b37286ac13e341e65e66c4d39246df99ac342f
6
+ metadata.gz: 9cd54124d6b4278d5cef85347eacc071a1a9d6511dc31c30fa551d9e9827d5288e1f91fad7c89cec32b4642897f13f92c16ed8a61b494f9e3b9bb4a60c8eacfc
7
+ data.tar.gz: ff0c2bff7f2d9f5d1a920a54a9dd120938c8a91e02cc024e3ecb9e420a50673f75b8ec2535643b794f34319ac2f2eb005a9a907dcc0e1b79869178fc3278305c
data/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 2.1.0 (2018-12-04)
8
+
9
+ ### Added
10
+
11
+ - Support for Faraday ([#249](https://github.com/elastic/apm-agent-ruby/pull/249))
12
+
13
+ ### Fixed
14
+
15
+ - Truncate keyword fields to 1024 chars ([#240](https://github.com/elastic/apm-agent-ruby/pull/240))
16
+ - Lazy boot worker threads on first event. Fixes apps using Puma's `preload_app!` ([#239](https://github.com/elastic/apm-agent-ruby/pull/239))
17
+ - Fix missing `disable_send` implementation ([#257](https://github.com/elastic/apm-agent-ruby/pull/257))
18
+ - Add warnings for invalid config options ([#254](https://github.com/elastic/apm-agent-ruby/pull/254))
19
+
7
20
  ## 2.0.1 (2018-11-15)
8
21
 
9
22
  ### Fixed
data/README.md CHANGED
@@ -11,21 +11,21 @@ The official Rubygem for [Elastic][] [APM][].
11
11
 
12
12
  ## Documentation
13
13
 
14
- [Full documentation at Elasti.co](https://www.elastic.co/guide/en/apm/agent/ruby/index.html).
14
+ [Full documentation at Elasti.co](https://www.elastic.co/guide/en/apm/agent/ruby/2.x/index.html).
15
15
 
16
16
  <ul>
17
- <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/introduction.html">Introduction</a></li>
18
- <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/getting-started-rails.html">Getting started with Rails</a></li>
19
- <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/getting-started-rack.html">Getting started with Rack</a></li>
20
- <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/configuration.html">Configuration</a></li>
17
+ <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/introduction.html">Introduction</a></li>
18
+ <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/getting-started-rails.html">Getting started with Rails</a></li>
19
+ <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/getting-started-rack.html">Getting started with Rack</a></li>
20
+ <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/configuration.html">Configuration</a></li>
21
21
  <li>
22
- <a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/advanced.html">Advanced Topics</a>
22
+ <a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/advanced.html">Advanced Topics</a>
23
23
  <ul>
24
- <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/custom-instrumentation.html">Custom instrumentation</a></li>
25
- <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/spies.html">Spies — instrumented libraries</a></li>
24
+ <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/custom-instrumentation.html">Custom instrumentation</a></li>
26
25
  </ul>
27
26
  </li>
28
- <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/1.x/api.html">Public API</a></li>
27
+ <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/supported-technologies.html">Supported Technologies</a></li>
28
+ <li><a href="https://www.elastic.co/guide/en/apm/agent/ruby/2.x/api.html">Public API</a></li>
29
29
  </ul>
30
30
 
31
31
  ---
@@ -0,0 +1,8 @@
1
+ [[advanced]]
2
+ == Advanced Topics
3
+
4
+ * <<context>>
5
+ * <<custom-instrumentation>>
6
+
7
+ include::./context.asciidoc[]
8
+ include::./custom-instrumentation.asciidoc[]
data/docs/api.asciidoc CHANGED
@@ -98,7 +98,7 @@ Wrap a block in a Transaction, starting and ending around the block
98
98
 
99
99
  [source,ruby]
100
100
  ----
101
- ElasticAPM.transaction 'Do things' do |transaction|
101
+ ElasticAPM.with_transaction 'Do things' do |transaction|
102
102
  do_work # ...
103
103
 
104
104
  transaction.result = 'success'
@@ -319,7 +319,7 @@ Elastic APM can instrument your Rake tasks but given that they are used for a mu
319
319
  [options="header"]
320
320
  |============
321
321
  | Environment | `Config` key | Default
322
- | `ELASTIC_APM_LOG_LEVEL` | `log_level` | `Logger::DEBUG # => 0`
322
+ | `ELASTIC_APM_LOG_LEVEL` | `log_level` | `Logger::INFO # => 1`
323
323
  |============
324
324
 
325
325
  By default Elastic APM logs to `stdout` or uses `Rails.log` when used with Rails.
@@ -1,4 +1,3 @@
1
- [float]
2
1
  [[context]]
3
2
  === Adding additional context
4
3
 
data/docs/index.asciidoc CHANGED
@@ -2,42 +2,24 @@
2
2
  include::{asciidoc-dir}/../../shared/attributes.asciidoc[]
3
3
 
4
4
  ifdef::env-github[]
5
- NOTE: For the best reading experience, please view this documentation at
5
+ NOTE: For the best reading experience,
6
+ please view this documentation at
6
7
  https://www.elastic.co/guide/en/apm/agent/ruby[elastic.co]
7
8
  endif::[]
8
9
 
9
10
  = APM Ruby Agent Reference
10
11
 
11
- [[introduction]]
12
- == Introduction
13
-
14
- The Elastic APM Ruby Agent sends performance metrics and error logs to an
15
- Elastic APM Server.
16
-
17
- It has built-in support for <<getting-started-rails,Ruby on Rails>> and other
18
- <<getting-started-rack,Rack-compatible>> applications.
19
-
20
- This agent is one of several components you need to get started collecting APM
21
- data. See also:
22
-
23
- * {apm-server-ref}[APM Server]
24
-
25
- [[framework-support]]
26
- The Elastic APM Ruby Agent officially supports Ruby on Rails versions 4.x on
27
- onwards, see <<getting-started-rails,Getting started with Ruby on Rails>>.
28
-
29
- For Sinatra and other Rack compatible frameworks, see
30
- <<getting-started-rack,Getting started with Rack>>.
12
+ include::./introduction.asciidoc[]
31
13
 
32
14
  include::./getting-started-rails.asciidoc[]
15
+
33
16
  include::./getting-started-rack.asciidoc[]
17
+
34
18
  include::./configuration.asciidoc[]
35
19
 
36
- [[advanced]]
37
- == Advanced Topics
38
- include::./context.asciidoc[]
39
- include::./custom-instrumentation.asciidoc[]
20
+ include::./advanced.asciidoc[]
40
21
 
41
22
  include::./supported-technologies.asciidoc[]
23
+
42
24
  include::./api.asciidoc[]
43
25
 
@@ -0,0 +1,33 @@
1
+ [[introduction]]
2
+
3
+ ifdef::env-github[]
4
+ NOTE: For the best reading experience,
5
+ please view this documentation at
6
+ https://www.elastic.co/guide/en/apm/agent/ruby/current/introduction.html[elastic.co]
7
+ endif::[]
8
+
9
+ == Introduction
10
+
11
+ Welcome to the APM Ruby Agent documentation.
12
+
13
+ The Elastic APM Ruby Agent sends performance metrics and error logs to an
14
+ Elastic APM Server.
15
+ It has built-in support for <<getting-started-rails,Ruby on Rails>> and other
16
+ <<getting-started-rack,Rack-compatible>> applications.
17
+
18
+ [float]
19
+ [[additional-components]]
20
+ === Additional Components
21
+
22
+ APM Agents work in conjunction with the {apm-server-ref}/index.html[APM Server], {ref}/index.html[Elasticsearch], and {kibana-ref}/index.html[Kibana].
23
+ Please view the {apm-get-started-ref}/index.html[APM Overview] for details on how these components work together.
24
+
25
+ [float]
26
+ [[framework-support]]
27
+ === Framework Support
28
+
29
+ The Elastic APM Ruby Agent officially supports Ruby on Rails versions 4.x on
30
+ onwards, see <<getting-started-rails,Getting started with Ruby on Rails>>.
31
+
32
+ For Sinatra and other Rack compatible frameworks, see
33
+ <<getting-started-rack,Getting started with Rack>>.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'elastic_apm/naively_hashable'
4
3
  require 'elastic_apm/context_builder'
5
4
  require 'elastic_apm/error_builder'
6
5
  require 'elastic_apm/stacktrace_builder'
@@ -3,10 +3,13 @@
3
3
  require 'logger'
4
4
  require 'yaml'
5
5
 
6
+ require 'elastic_apm/util/prefixed_logger'
6
7
  require 'elastic_apm/config/duration'
7
8
  require 'elastic_apm/config/size'
8
9
 
9
10
  module ElasticAPM
11
+ class ConfigError < StandardError; end
12
+
10
13
  # rubocop:disable Metrics/ClassLength
11
14
  # @api private
12
15
  class Config
@@ -144,10 +147,10 @@ module ElasticAPM
144
147
  attr_accessor :transaction_sample_rate
145
148
  attr_accessor :verify_server_cert
146
149
 
147
- attr_reader :custom_key_filters
148
- attr_reader :ignore_url_patterns
149
- attr_reader :span_frames_min_duration
150
- attr_reader :span_frames_min_duration_us
150
+ attr_reader :custom_key_filters
151
+ attr_reader :ignore_url_patterns
152
+ attr_reader :span_frames_min_duration
153
+ attr_reader :span_frames_min_duration_us
151
154
 
152
155
  attr_accessor :view_paths
153
156
  attr_accessor :root_path
@@ -157,6 +160,10 @@ module ElasticAPM
157
160
  alias :instrument? :instrument
158
161
  alias :verify_server_cert? :verify_server_cert
159
162
 
163
+ def alert_logger
164
+ @alert_logger ||= PrefixedLogger.new($stdout, prefix: Logging::PREFIX)
165
+ end
166
+
160
167
  def app=(app)
161
168
  case app_type?(app)
162
169
  when :sinatra
@@ -198,6 +205,7 @@ module ElasticAPM
198
205
  action_dispatch
199
206
  delayed_job
200
207
  elasticsearch
208
+ faraday
201
209
  http
202
210
  json
203
211
  mongo
@@ -234,12 +242,22 @@ module ElasticAPM
234
242
  ].freeze
235
243
 
236
244
  def respond_to_missing?(name)
237
- DEPRECATED_OPTIONS.include? name
245
+ return true if DEPRECATED_OPTIONS.include? name
246
+ return true if name.to_s.end_with?('=')
247
+ false
238
248
  end
239
249
 
240
250
  def method_missing(name, *args)
241
- return super unless DEPRECATED_OPTIONS.include?(name)
242
- warn "The option `#{name}' has been removed."
251
+ if DEPRECATED_OPTIONS.include?(name)
252
+ alert_logger.warn "The option `#{name}' has been removed."
253
+ return
254
+ end
255
+
256
+ if name.to_s.end_with?('=')
257
+ raise ConfigError, "No such option `#{name.to_s.delete('=')}'"
258
+ end
259
+
260
+ super
243
261
  end
244
262
 
245
263
  private
@@ -280,11 +298,21 @@ module ElasticAPM
280
298
 
281
299
  def set_from_args(options)
282
300
  assign(options)
301
+ rescue ConfigError => e
302
+ alert_logger.warn format(
303
+ 'Failed to configure from arguments: %s',
304
+ e.message
305
+ )
283
306
  end
284
307
 
285
308
  def set_from_config_file
286
309
  return unless File.exist?(config_file)
287
310
  assign(YAML.load_file(config_file) || {})
311
+ rescue ConfigError => e
312
+ alert_logger.warn format(
313
+ 'Failed to configure from config file: %s',
314
+ e.message
315
+ )
288
316
  end
289
317
 
290
318
  def set_sinatra(app)
@@ -9,8 +9,6 @@ require 'elastic_apm/context/user'
9
9
  module ElasticAPM
10
10
  # @api private
11
11
  class Context
12
- include NaivelyHashable
13
-
14
12
  attr_accessor :request, :response, :user
15
13
  attr_reader :custom, :tags
16
14
 
@@ -4,8 +4,6 @@ module ElasticAPM
4
4
  class Context
5
5
  # @api private
6
6
  class Request
7
- include NaivelyHashable
8
-
9
7
  attr_accessor :body, :cookies, :env, :headers, :http_version, :method,
10
8
  :socket, :url
11
9
  end
@@ -7,8 +7,6 @@ module ElasticAPM
7
7
  class Request
8
8
  # @api private
9
9
  class Socket
10
- include NaivelyHashable
11
-
12
10
  def initialize(req)
13
11
  @remote_addr = req.ip
14
12
  @encrypted = req.scheme == 'https'
@@ -7,8 +7,6 @@ module ElasticAPM
7
7
  class Request
8
8
  # @api private
9
9
  class Url
10
- include NaivelyHashable
11
-
12
10
  SKIPPED_PORTS = {
13
11
  'http' => 80,
14
12
  'https' => 443
@@ -4,8 +4,6 @@ module ElasticAPM
4
4
  class Context
5
5
  # @api private
6
6
  class Response
7
- include NaivelyHashable
8
-
9
7
  def initialize(
10
8
  status_code,
11
9
  headers: {},
@@ -4,12 +4,10 @@ module ElasticAPM
4
4
  class Context
5
5
  # @api private
6
6
  class User
7
- include NaivelyHashable
8
-
9
7
  def initialize(config, record)
10
8
  return unless record
11
9
 
12
- @id = safe_get(record, config.current_user_id_method)
10
+ @id = safe_get(record, config.current_user_id_method)&.to_s
13
11
  @email = safe_get(record, config.current_user_email_method)
14
12
  @username = safe_get(record, config.current_user_username_method)
15
13
  end
@@ -21,10 +21,10 @@ module ElasticAPM
21
21
  context.request = Context::Request.new unless context.request
22
22
  request = context.request
23
23
 
24
- request.socket = Context::Request::Socket.new(req).to_h
24
+ request.socket = Context::Request::Socket.new(req)
25
25
  request.http_version = build_http_version rack_env
26
26
  request.method = req.request_method
27
- request.url = Context::Request::Url.new(req).to_h
27
+ request.url = Context::Request::Url.new(req)
28
28
  request.headers, request.env = get_headers_and_env(rack_env)
29
29
  request.body = get_body(req)
30
30
 
@@ -1,20 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'elastic_apm/metadata/service_info'
4
- require 'elastic_apm/metadata/system_info'
5
- require 'elastic_apm/metadata/process_info'
6
-
7
3
  module ElasticAPM
8
4
  # @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
5
+ class Metadata
6
+ def initialize(config)
7
+ @service = ServiceInfo.new(config)
8
+ @process = ProcessInfo.new(config)
9
+ @system = SystemInfo.new(config)
18
10
  end
11
+
12
+ attr_reader :service, :process, :system
19
13
  end
20
14
  end
15
+
16
+ require 'elastic_apm/metadata/service_info'
17
+ require 'elastic_apm/metadata/system_info'
18
+ require 'elastic_apm/metadata/process_info'
@@ -1,26 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- module Metadata
4
+ class Metadata
5
5
  # @api private
6
6
  class ProcessInfo
7
7
  def initialize(config)
8
8
  @config = config
9
- end
10
9
 
11
- def build
12
- pid = $PID || Process.pid
13
- return unless pid
14
- {
15
- argv: ARGV,
16
- pid: pid,
17
- title: $PROGRAM_NAME
18
- }
10
+ @argv = ARGV
11
+ @pid = $PID || Process.pid
12
+ @title = $PROGRAM_NAME
19
13
  end
20
14
 
21
- def self.build(config)
22
- new(config).build
23
- end
15
+ attr_reader :argv, :pid, :title
24
16
  end
25
17
  end
26
18
  end
@@ -1,56 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- module Metadata
4
+ class Metadata
5
5
  # @api private
6
6
  class ServiceInfo
7
- def initialize(config)
8
- @config = config
7
+ # @api private
8
+ class Versioned
9
+ def initialize(name: nil, version: nil)
10
+ @name = name
11
+ @version = version
12
+ end
13
+
14
+ attr_reader :name, :version
9
15
  end
16
+ class Agent < Versioned; end
17
+ class Framework < Versioned; end
18
+ class Language < Versioned; end
19
+ class Runtime < Versioned; end
10
20
 
11
21
  # 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
22
+ def initialize(config)
23
+ @config = config
35
24
 
36
- base
25
+ @name = @config.service_name
26
+ @environment = @config.environment
27
+ @agent = Agent.new(name: 'ruby', version: VERSION)
28
+ @framework = Framework.new(
29
+ name: @config.framework_name,
30
+ version: @config.framework_version
31
+ )
32
+ @language = Language.new(name: 'ruby', version: RUBY_VERSION)
33
+ @runtime = lookup_runtime
34
+ @version = @config.service_version || Util.git_sha
37
35
  end
38
36
  # rubocop:enable Metrics/MethodLength
39
37
 
40
- def self.build(config)
41
- new(config).build
42
- end
38
+ attr_reader :name, :environment, :agent, :framework, :language, :runtime,
39
+ :version
43
40
 
44
41
  private
45
42
 
46
- def runtime
43
+ # rubocop:disable Metrics/MethodLength
44
+ def lookup_runtime
47
45
  case RUBY_ENGINE
48
46
  when 'ruby'
49
- { name: RUBY_ENGINE, version: RUBY_VERSION || RUBY_ENGINE_VERSION }
47
+ Runtime.new(
48
+ name: RUBY_ENGINE,
49
+ version: RUBY_VERSION || RUBY_ENGINE_VERSION
50
+ )
50
51
  when 'jruby'
51
- { name: RUBY_ENGINE, version: JRUBY_VERSION || RUBY_ENGINE_VERSION }
52
+ Runtime.new(
53
+ name: RUBY_ENGINE,
54
+ version: JRUBY_VERSION || RUBY_ENGINE_VERSION
55
+ )
52
56
  end
53
57
  end
58
+ # rubocop:enable Metrics/MethodLength
54
59
  end
55
60
  end
56
61
  end
@@ -1,29 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- module Metadata
4
+ class Metadata
5
5
  # @api private
6
6
  class SystemInfo
7
7
  def initialize(config)
8
8
  @config = config
9
- end
10
9
 
11
- def build
12
- {
13
- hostname: @config.hostname || `hostname`.chomp,
14
- architecture: platform.cpu,
15
- platform: platform.os
16
- }
10
+ @hostname = @config.hostname || `hostname`.chomp
11
+ @architecture = gem_platform.cpu
12
+ @platform = gem_platform.os
17
13
  end
18
14
 
19
- def self.build(config)
20
- new(config).build
21
- end
15
+ attr_reader :hostname, :architecture, :platform
22
16
 
23
17
  private
24
18
 
25
- def platform
26
- @platform ||= Gem::Platform.local
19
+ def gem_platform
20
+ @gem_platform ||= Gem::Platform.local
27
21
  end
28
22
  end
29
23
  end
@@ -26,8 +26,8 @@ module ElasticAPM
26
26
  app.middleware.insert 0, Middleware
27
27
  end
28
28
  rescue StandardError => e
29
- Rails.logger.error "#{Logging::PREFIX}Failed to start: #{e.message}"
30
- Rails.logger.debug e.backtrace.join("\n")
29
+ config.alert_logger.error format('Failed to start: %s', e.message)
30
+ config.alert_logger.debug e.backtrace.join("\n")
31
31
  end
32
32
  end
33
33
 
@@ -4,8 +4,6 @@ module ElasticAPM
4
4
  class Span
5
5
  # @api private
6
6
  class Context
7
- include NaivelyHashable
8
-
9
7
  def initialize(db: nil, http: nil)
10
8
  @db = db && Db.new(db)
11
9
  @http = http && Http.new(http)
@@ -15,8 +13,6 @@ module ElasticAPM
15
13
 
16
14
  # @api private
17
15
  class Db
18
- include NaivelyHashable
19
-
20
16
  def initialize(instance: nil, statement: nil, type: nil, user: nil)
21
17
  @instance = instance
22
18
  @statement = statement
@@ -29,8 +25,6 @@ module ElasticAPM
29
25
 
30
26
  # @api private
31
27
  class Http
32
- include NaivelyHashable
33
-
34
28
  def initialize(url: nil, status_code: nil, method: nil)
35
29
  @url = url
36
30
  @status_code = status_code
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Spies
6
+ # @api private
7
+ class FaradaySpy
8
+ # rubocop:disable Metrics/MethodLength
9
+ def install
10
+ ::Faraday::Connection.class_eval do
11
+ alias run_request_without_apm run_request
12
+
13
+ def run_request(method, url, body, headers, &block)
14
+ unless (transaction = ElasticAPM.current_transaction)
15
+ return run_request_without_apm(method, url, body, headers, &block)
16
+ end
17
+
18
+ host = URI(url).host
19
+
20
+ name = "#{method.upcase} #{host}"
21
+ type = "ext.faraday.#{method}"
22
+
23
+ ElasticAPM.with_span name, type do |span|
24
+ ElasticAPM::Spies::NetHTTPSpy.disable_in do
25
+ run_request_without_apm(method, url, body, headers) do |req|
26
+ req['Elastic-Apm-Traceparent'] =
27
+ transaction.traceparent.to_header(span_id: span.id)
28
+
29
+ yield req if block_given?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ # rubocop:enable Metrics/MethodLength
37
+ end
38
+
39
+ register 'Faraday', 'faraday', FaradaySpy.new
40
+ end
41
+ end
@@ -5,7 +5,29 @@ module ElasticAPM
5
5
  module Spies
6
6
  # @api private
7
7
  class NetHTTPSpy
8
+ KEY = :__elastic_apm_net_http_disabled
9
+
8
10
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
11
+ class << self
12
+ def disabled=(disabled)
13
+ Thread.current[KEY] = disabled
14
+ end
15
+
16
+ def disabled?
17
+ Thread.current[KEY] ||= false
18
+ end
19
+
20
+ def disable_in
21
+ self.disabled = true
22
+
23
+ begin
24
+ yield
25
+ ensure
26
+ self.disabled = false
27
+ end
28
+ end
29
+ end
30
+
9
31
  def install
10
32
  Net::HTTP.class_eval do
11
33
  alias request_without_apm request
@@ -14,6 +36,9 @@ module ElasticAPM
14
36
  unless (transaction = ElasticAPM.current_transaction)
15
37
  return request_without_apm(req, body, &block)
16
38
  end
39
+ if ElasticAPM::Spies::NetHTTPSpy.disabled?
40
+ return request_without_apm(req, body, &block)
41
+ end
17
42
 
18
43
  host, = req['host'] && req['host'].split(':')
19
44
  method = req.method
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'elastic_apm/naively_hashable'
4
+
3
5
  module ElasticAPM
4
6
  class Stacktrace
5
7
  # @api private
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'elastic_apm/metadata'
3
4
  require 'elastic_apm/transport/connection'
4
5
  require 'elastic_apm/transport/worker'
5
-
6
6
  require 'elastic_apm/transport/serializers'
7
7
  require 'elastic_apm/transport/filters'
8
8
 
@@ -25,7 +25,6 @@ module ElasticAPM
25
25
  attr_reader :config, :queue, :workers, :filters
26
26
 
27
27
  def start
28
- ensure_worker_count
29
28
  end
30
29
 
31
30
  def stop
@@ -4,8 +4,6 @@ require 'http'
4
4
  require 'concurrent'
5
5
  require 'zlib'
6
6
 
7
- require 'elastic_apm/metadata'
8
-
9
7
  module ElasticAPM
10
8
  module Transport
11
9
  # @api private
@@ -30,8 +28,9 @@ module ElasticAPM
30
28
  }.freeze
31
29
  GZIP_HEADERS = HEADERS.merge('Content-Encoding' => 'gzip').freeze
32
30
 
33
- def initialize(config)
31
+ def initialize(config, metadata)
34
32
  @config = config
33
+ @metadata = metadata.to_json
35
34
 
36
35
  @url = config.server_url + '/intake/v2/events'
37
36
 
@@ -44,12 +43,12 @@ module ElasticAPM
44
43
 
45
44
  @client = HTTP.headers(headers).persistent(@url)
46
45
 
47
- @metadata = Metadata.build(config)
48
-
49
46
  @mutex = Mutex.new
50
47
  end
51
48
 
52
49
  def write(str)
50
+ return if @config.disable_send
51
+
53
52
  connect_unless_connected
54
53
 
55
54
  @mutex.synchronize { append(str) }
@@ -15,11 +15,23 @@ module ElasticAPM
15
15
  @config = config
16
16
  end
17
17
 
18
+ attr_reader :config
19
+
18
20
  private
19
21
 
20
22
  def ms(micros)
21
23
  micros.to_f / 1_000
22
24
  end
25
+
26
+ def keyword_field(value)
27
+ Util.truncate(value)
28
+ end
29
+
30
+ def keyword_object(hash)
31
+ hash.tap do |h|
32
+ h.each { |k, v| hash[k] = keyword_field(v) }
33
+ end
34
+ end
23
35
  end
24
36
 
25
37
  # @api private
@@ -28,10 +40,12 @@ module ElasticAPM
28
40
  @transaction = Serializers::TransactionSerializer.new(config)
29
41
  @span = Serializers::SpanSerializer.new(config)
30
42
  @error = Serializers::ErrorSerializer.new(config)
43
+ @metadata = Serializers::MetadataSerializer.new(config)
31
44
  end
32
45
 
33
- attr_reader :transaction, :span, :error
46
+ attr_reader :transaction, :span, :error, :metadata
34
47
 
48
+ # rubocop:disable Metrics/MethodLength
35
49
  def serialize(resource)
36
50
  case resource
37
51
  when Transaction
@@ -40,10 +54,13 @@ module ElasticAPM
40
54
  span.build(resource)
41
55
  when Error
42
56
  error.build(resource)
57
+ when Metadata
58
+ metadata.build(resource)
43
59
  else
44
60
  raise UnrecognizedResource, resource.inspect
45
61
  end
46
62
  end
63
+ # rubocop:enable Metrics/MethodLength
47
64
  end
48
65
 
49
66
  def self.new(config)
@@ -53,6 +70,8 @@ module ElasticAPM
53
70
  end
54
71
  end
55
72
 
56
- require 'elastic_apm/transport/serializers/transaction_serializer'
57
- require 'elastic_apm/transport/serializers/span_serializer'
73
+ require 'elastic_apm/transport/serializers/context_serializer'
58
74
  require 'elastic_apm/transport/serializers/error_serializer'
75
+ require 'elastic_apm/transport/serializers/metadata_serializer'
76
+ require 'elastic_apm/transport/serializers/span_serializer'
77
+ require 'elastic_apm/transport/serializers/transaction_serializer'
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Transport
5
+ module Serializers
6
+ # @api private
7
+ class ContextSerializer < Serializer
8
+ def build(context)
9
+ {
10
+ custom: context.custom,
11
+ tags: keyword_object(context.tags),
12
+ request: build_request(context.request),
13
+ response: build_response(context.response),
14
+ user: build_user(context.user)
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ # rubocop:disable Metrics/MethodLength
21
+ def build_request(request)
22
+ return unless request
23
+
24
+ {
25
+ body: request.body,
26
+ cookies: request.cookies,
27
+ env: request.env,
28
+ headers: request.headers,
29
+ http_version: keyword_field(request.http_version),
30
+ method: keyword_field(request.method),
31
+ socket: build_socket(request.socket),
32
+ url: request.url
33
+ }
34
+ end
35
+ # rubocop:enable Metrics/MethodLength
36
+
37
+ def build_response(response)
38
+ return unless response
39
+
40
+ {
41
+ status_code: response.status_code,
42
+ headers: response.headers,
43
+ headers_sent: response.headers_sent,
44
+ finished: response.finished
45
+ }
46
+ end
47
+
48
+ def build_user(user)
49
+ return unless user
50
+
51
+ {
52
+ id: keyword_field(user.id),
53
+ email: keyword_field(user.email),
54
+ username: keyword_field(user.username)
55
+ }
56
+ end
57
+
58
+ def build_socket(socket)
59
+ return unless socket
60
+
61
+ {
62
+ remote_addr: socket.remote_addr,
63
+ encrypted: socket.encrypted
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -5,6 +5,10 @@ module ElasticAPM
5
5
  module Serializers
6
6
  # @api private
7
7
  class ErrorSerializer < Serializer
8
+ def context_serializer
9
+ @context_serializer ||= ContextSerializer.new(config)
10
+ end
11
+
8
12
  # rubocop:disable Metrics/MethodLength
9
13
  def build(error)
10
14
  base = {
@@ -15,7 +19,7 @@ module ElasticAPM
15
19
 
16
20
  culprit: error.culprit,
17
21
  timestamp: error.timestamp,
18
- context: error.context.to_h
22
+ context: context_serializer.build(error.context)
19
23
  }
20
24
 
21
25
  if (exception = error.exception)
@@ -35,9 +39,9 @@ module ElasticAPM
35
39
  def build_exception(exception)
36
40
  {
37
41
  message: exception.message,
38
- type: exception.type,
39
- module: exception.module,
40
- code: exception.code,
42
+ type: keyword_field(exception.type),
43
+ module: keyword_field(exception.module),
44
+ code: keyword_field(exception.code),
41
45
  attributes: exception.attributes,
42
46
  stacktrace: exception.stacktrace.to_a,
43
47
  handled: exception.handled
@@ -47,9 +51,9 @@ module ElasticAPM
47
51
  def build_log(log)
48
52
  {
49
53
  message: log.message,
50
- level: log.level,
51
- logger_name: log.logger_name,
52
- param_message: log.param_message,
54
+ level: keyword_field(log.level),
55
+ logger_name: keyword_field(log.logger_name),
56
+ param_message: keyword_field(log.param_message),
53
57
  stacktrace: log.stacktrace.to_a
54
58
  }
55
59
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Transport
5
+ module Serializers
6
+ # @api private
7
+ class MetadataSerializer < Serializer
8
+ def build(metadata)
9
+ {
10
+ metadata: {
11
+ service: build_service(metadata.service),
12
+ process: build_process(metadata.process),
13
+ system: build_system(metadata.system)
14
+ }
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
21
+ def build_service(service)
22
+ {
23
+ name: keyword_field(service.name),
24
+ environment: keyword_field(service.environment),
25
+ version: keyword_field(service.version),
26
+ agent: {
27
+ name: keyword_field(service.agent.name),
28
+ version: keyword_field(service.agent.version)
29
+ },
30
+ framework: {
31
+ name: keyword_field(service.framework.name),
32
+ version: keyword_field(service.framework.version)
33
+ },
34
+ language: {
35
+ name: keyword_field(service.language.name),
36
+ version: keyword_field(service.language.version)
37
+ },
38
+ runtime: {
39
+ name: keyword_field(service.runtime.name),
40
+ version: keyword_field(service.runtime.version)
41
+ }
42
+ }
43
+ end
44
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
45
+
46
+ def build_process(process)
47
+ {
48
+ pid: process.pid,
49
+ title: keyword_field(process.title),
50
+ argv: process.argv
51
+ }
52
+ end
53
+
54
+ def build_system(system)
55
+ {
56
+ hostname: keyword_field(system.hostname),
57
+ architecture: keyword_field(system.architecture),
58
+ platform: keyword_field(system.platform)
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -5,24 +5,68 @@ module ElasticAPM
5
5
  module Serializers
6
6
  # @api private
7
7
  class SpanSerializer < Serializer
8
- # rubocop:disable Metrics/MethodLength
8
+ def initialize(config)
9
+ super
10
+
11
+ @context_serializer = ContextSerializer.new(config)
12
+ end
13
+
14
+ attr_reader :context_serializer
15
+
16
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
9
17
  def build(span)
10
18
  {
11
19
  span: {
12
20
  id: span.id,
13
21
  transaction_id: span.transaction_id,
14
22
  parent_id: span.parent_id,
15
- name: span.name,
16
- type: span.type,
23
+ name: keyword_field(span.name),
24
+ type: keyword_field(span.type),
17
25
  duration: ms(span.duration),
18
- context: span.context&.to_h,
26
+ context: context_serializer.build(span.context),
19
27
  stacktrace: span.stacktrace.to_a,
20
28
  timestamp: span.timestamp,
21
29
  trace_id: span.trace_id
22
30
  }
23
31
  }
24
32
  end
25
- # rubocop:enable Metrics/MethodLength
33
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
34
+
35
+ # @api private
36
+ class ContextSerializer < Serializer
37
+ def build(context)
38
+ return unless context
39
+
40
+ {
41
+ sync: context.sync,
42
+ db: build_db(context.db),
43
+ http: build_http(context.http)
44
+ }
45
+ end
46
+
47
+ private
48
+
49
+ def build_db(db)
50
+ return unless db
51
+
52
+ {
53
+ instance: db.instance,
54
+ statement: db.statement,
55
+ type: db.type,
56
+ user: db.user
57
+ }
58
+ end
59
+
60
+ def build_http(http)
61
+ return unless http
62
+
63
+ {
64
+ url: http.url,
65
+ status_code: http.status_code,
66
+ method: keyword_field(http.method)
67
+ }
68
+ end
69
+ end
26
70
  end
27
71
  end
28
72
  end
@@ -5,20 +5,24 @@ module ElasticAPM
5
5
  module Serializers
6
6
  # @api private
7
7
  class TransactionSerializer < Serializer
8
- # rubocop:disable Metrics/MethodLength
8
+ def context_serializer
9
+ @context_serializer ||= ContextSerializer.new(config)
10
+ end
11
+
12
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
9
13
  def build(transaction)
10
14
  {
11
15
  transaction: {
12
16
  id: transaction.id,
13
17
  trace_id: transaction.trace_id,
14
18
  parent_id: transaction.parent_id,
15
- name: transaction.name,
16
- type: transaction.type,
17
- result: transaction.result.to_s,
19
+ name: keyword_field(transaction.name),
20
+ type: keyword_field(transaction.type),
21
+ result: keyword_field(transaction.result.to_s),
18
22
  duration: ms(transaction.duration),
19
23
  timestamp: transaction.timestamp,
20
24
  sampled: transaction.sampled?,
21
- context: transaction.context.to_h,
25
+ context: context_serializer.build(transaction.context),
22
26
  span_count: {
23
27
  started: transaction.started_spans,
24
28
  dropped: transaction.dropped_spans
@@ -26,7 +30,7 @@ module ElasticAPM
26
30
  }
27
31
  }
28
32
  end
29
- # rubocop:enable Metrics/MethodLength
33
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
30
34
  end
31
35
  end
32
36
  end
@@ -24,9 +24,11 @@ module ElasticAPM
24
24
 
25
25
  @stopping = false
26
26
 
27
- @connection = conn_adapter.new(config)
28
27
  @serializers = serializers
29
28
  @filters = filters
29
+
30
+ metadata = serializers.serialize(Metadata.new(config))
31
+ @connection = conn_adapter.new(config, metadata)
30
32
  end
31
33
 
32
34
  attr_reader :queue, :filters, :name, :connection, :serializers
@@ -39,7 +41,7 @@ module ElasticAPM
39
41
  @stopping
40
42
  end
41
43
 
42
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
44
+ # rubocop:disable Metrics/MethodLength
43
45
  def work_forever
44
46
  while (msg = queue.pop)
45
47
  case msg
@@ -59,7 +61,7 @@ module ElasticAPM
59
61
  warn 'Worker died with exception: %s', e.inspect
60
62
  debug e.backtrace.join("\n")
61
63
  end
62
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
64
+ # rubocop:enable Metrics/MethodLength
63
65
 
64
66
  private
65
67
 
@@ -24,5 +24,12 @@ module ElasticAPM
24
24
  def self.reverse_merge!(first, second)
25
25
  first.merge!(second) { |_, old, _| old }
26
26
  end
27
+
28
+ def self.truncate(value, max_length: 1024)
29
+ return unless value
30
+ return value if value.length <= max_length
31
+
32
+ value[0...(max_length - 1)] + '…'
33
+ end
27
34
  end
28
35
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class PrefixedLogger < Logger
6
+ def initialize(logdev, prefix: '', **args)
7
+ super(logdev, **args)
8
+
9
+ @prefix = prefix
10
+ end
11
+
12
+ attr_reader :prefix
13
+
14
+ def add(severity, message = nil, progname = nil)
15
+ super(severity, format('%s%s', prefix, message), progname)
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- VERSION = '2.0.1'
4
+ VERSION = '2.1.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic-apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-15 00:00:00.000000000 Z
11
+ date: 2018-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -66,6 +66,7 @@ files:
66
66
  - bin/console
67
67
  - bin/setup
68
68
  - bin/with_framework
69
+ - docs/advanced.asciidoc
69
70
  - docs/api.asciidoc
70
71
  - docs/configuration.asciidoc
71
72
  - docs/context.asciidoc
@@ -73,6 +74,7 @@ files:
73
74
  - docs/getting-started-rack.asciidoc
74
75
  - docs/getting-started-rails.asciidoc
75
76
  - docs/index.asciidoc
77
+ - docs/introduction.asciidoc
76
78
  - docs/supported-technologies.asciidoc
77
79
  - elastic-apm.gemspec
78
80
  - lib/elastic-apm.rb
@@ -115,6 +117,7 @@ files:
115
117
  - lib/elastic_apm/spies/action_dispatch.rb
116
118
  - lib/elastic_apm/spies/delayed_job.rb
117
119
  - lib/elastic_apm/spies/elasticsearch.rb
120
+ - lib/elastic_apm/spies/faraday.rb
118
121
  - lib/elastic_apm/spies/http.rb
119
122
  - lib/elastic_apm/spies/json.rb
120
123
  - lib/elastic_apm/spies/mongo.rb
@@ -138,13 +141,16 @@ files:
138
141
  - lib/elastic_apm/transport/filters/request_body_filter.rb
139
142
  - lib/elastic_apm/transport/filters/secrets_filter.rb
140
143
  - lib/elastic_apm/transport/serializers.rb
144
+ - lib/elastic_apm/transport/serializers/context_serializer.rb
141
145
  - lib/elastic_apm/transport/serializers/error_serializer.rb
146
+ - lib/elastic_apm/transport/serializers/metadata_serializer.rb
142
147
  - lib/elastic_apm/transport/serializers/span_serializer.rb
143
148
  - lib/elastic_apm/transport/serializers/transaction_serializer.rb
144
149
  - lib/elastic_apm/transport/worker.rb
145
150
  - lib/elastic_apm/util.rb
146
151
  - lib/elastic_apm/util/inflector.rb
147
152
  - lib/elastic_apm/util/lru_cache.rb
153
+ - lib/elastic_apm/util/prefixed_logger.rb
148
154
  - lib/elastic_apm/version.rb
149
155
  - vendor/.gitkeep
150
156
  homepage: https://github.com/elastic/apm-agent-ruby