elastic-apm 0.1.0 → 0.2.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +3 -0
  4. data/README.md +7 -1
  5. data/docs/api.asciidoc +210 -0
  6. data/docs/configuration.asciidoc +32 -0
  7. data/docs/context.asciidoc +27 -0
  8. data/docs/custom-instrumentation.asciidoc +14 -0
  9. data/docs/getting-started-rack.asciidoc +26 -0
  10. data/docs/getting-started-rails.asciidoc +13 -0
  11. data/docs/index.asciidoc +33 -0
  12. data/elastic-apm.gemspec +1 -0
  13. data/lib/elastic_apm.rb +54 -7
  14. data/lib/elastic_apm/agent.rb +20 -4
  15. data/lib/elastic_apm/config.rb +16 -3
  16. data/lib/elastic_apm/context.rb +22 -0
  17. data/lib/elastic_apm/context/request.rb +13 -0
  18. data/lib/elastic_apm/context/request/socket.rb +21 -0
  19. data/lib/elastic_apm/context/request/url.rb +44 -0
  20. data/lib/elastic_apm/context/response.rb +24 -0
  21. data/lib/elastic_apm/context/user.rb +26 -0
  22. data/lib/elastic_apm/context_builder.rb +93 -0
  23. data/lib/elastic_apm/error.rb +6 -3
  24. data/lib/elastic_apm/error_builder.rb +26 -10
  25. data/lib/elastic_apm/http.rb +4 -2
  26. data/lib/elastic_apm/injectors/action_dispatch.rb +1 -1
  27. data/lib/elastic_apm/instrumenter.rb +18 -0
  28. data/lib/elastic_apm/middleware.rb +15 -3
  29. data/lib/elastic_apm/naively_hashable.rb +21 -0
  30. data/lib/elastic_apm/process_info.rb +22 -0
  31. data/lib/elastic_apm/serializers/errors.rb +10 -3
  32. data/lib/elastic_apm/serializers/transactions.rb +4 -2
  33. data/lib/elastic_apm/service_info.rb +0 -3
  34. data/lib/elastic_apm/span/context.rb +2 -4
  35. data/lib/elastic_apm/stacktrace/frame.rb +2 -18
  36. data/lib/elastic_apm/transaction.rb +20 -7
  37. data/lib/elastic_apm/version.rb +1 -1
  38. metadata +20 -4
  39. data/lib/elastic_apm/error/context.rb +0 -119
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'elastic_apm/naively_hashable'
4
+ require 'elastic_apm/context_builder'
3
5
  require 'elastic_apm/error_builder'
4
6
  require 'elastic_apm/error'
5
7
  require 'elastic_apm/http'
@@ -51,6 +53,7 @@ module ElasticAPM
51
53
  @queue = Queue.new
52
54
 
53
55
  @instrumenter = Instrumenter.new(config, self)
56
+ @context_builder = ContextBuilder.new(config)
54
57
  @error_builder = ErrorBuilder.new(config)
55
58
 
56
59
  @serializers = Struct.new(:transactions, :errors).new(
@@ -59,10 +62,10 @@ module ElasticAPM
59
62
  )
60
63
  end
61
64
 
62
- attr_reader :config, :queue, :instrumenter
65
+ attr_reader :config, :queue, :instrumenter, :context_builder
63
66
 
64
67
  def start
65
- debug 'Starting agent reporting to %s', config.server
68
+ debug 'Starting agent reporting to %s', config.server_url
66
69
 
67
70
  @instrumenter.start
68
71
 
@@ -114,12 +117,15 @@ module ElasticAPM
114
117
  instrumenter.span(*args, &block)
115
118
  end
116
119
 
120
+ def build_context(rack_env)
121
+ @context_builder.build(rack_env)
122
+ end
123
+
117
124
  # errors
118
125
 
119
- def report(exception, rack_env: nil, handled: true)
126
+ def report(exception, handled: true)
120
127
  error = @error_builder.build_exception(
121
128
  exception,
122
- rack_env: rack_env,
123
129
  handled: handled
124
130
  )
125
131
  enqueue_errors error
@@ -134,6 +140,16 @@ module ElasticAPM
134
140
  enqueue_errors error
135
141
  end
136
142
 
143
+ # context
144
+
145
+ def set_tag(*args)
146
+ instrumenter.set_tag(*args)
147
+ end
148
+
149
+ def set_custom_context(*args)
150
+ instrumenter.set_custom_context(*args)
151
+ end
152
+
137
153
  def inspect
138
154
  '<ElasticAPM::Agent>'
139
155
  end
@@ -6,7 +6,7 @@ module ElasticAPM
6
6
  # @api private
7
7
  class Config
8
8
  DEFAULTS = {
9
- server: 'http://localhost:8200',
9
+ server_url: 'http://localhost:8200',
10
10
  secret_token: nil,
11
11
 
12
12
  app_name: nil,
@@ -25,6 +25,11 @@ module ElasticAPM
25
25
 
26
26
  enabled_injectors: %w[net_http],
27
27
 
28
+ current_user_method: :current_user,
29
+ current_user_id_method: :id,
30
+ current_user_email_method: :email,
31
+ current_user_username_method: :username,
32
+
28
33
  view_paths: []
29
34
  }.freeze
30
35
 
@@ -42,7 +47,7 @@ module ElasticAPM
42
47
  yield self
43
48
  end
44
49
 
45
- attr_accessor :server
50
+ attr_accessor :server_url
46
51
  attr_accessor :secret_token
47
52
 
48
53
  attr_accessor :app_name
@@ -63,6 +68,11 @@ module ElasticAPM
63
68
 
64
69
  attr_accessor :view_paths
65
70
 
71
+ attr_accessor :current_user_method
72
+ attr_accessor :current_user_id_method
73
+ attr_accessor :current_user_email_method
74
+ attr_accessor :current_user_username_method
75
+
66
76
  attr_writer :logger
67
77
 
68
78
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
@@ -78,6 +88,9 @@ module ElasticAPM
78
88
  self.framework_version = Rails::VERSION::STRING
79
89
  self.logger = Rails.logger
80
90
  self.view_paths = app.config.paths['app/views'].existent
91
+ else
92
+ # TODO: define custom?
93
+ self.app_name = 'ruby'
81
94
  end
82
95
  end
83
96
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
@@ -106,7 +119,7 @@ module ElasticAPM
106
119
  end
107
120
 
108
121
  def use_ssl?
109
- server.start_with?('https')
122
+ server_url.start_with?('https')
110
123
  end
111
124
 
112
125
  private
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/context/request'
4
+ require 'elastic_apm/context/request/socket'
5
+ require 'elastic_apm/context/request/url'
6
+ require 'elastic_apm/context/response'
7
+ require 'elastic_apm/context/user'
8
+
9
+ module ElasticAPM
10
+ # @api private
11
+ class Context
12
+ include NaivelyHashable
13
+
14
+ attr_accessor :request, :response, :user
15
+ attr_reader :custom, :tags
16
+
17
+ def initialize
18
+ @custom = {}
19
+ @tags = {}
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Context
5
+ # @api private
6
+ class Request
7
+ include NaivelyHashable
8
+
9
+ attr_accessor :body, :cookies, :env, :headers, :http_version, :method,
10
+ :socket, :url
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class Context
6
+ # @api private
7
+ class Request
8
+ # @api private
9
+ class Socket
10
+ include NaivelyHashable
11
+
12
+ def initialize(req)
13
+ @remote_addr = req.ip
14
+ @encrypted = req.scheme == 'https'
15
+ end
16
+
17
+ attr_reader :remote_addr, :encrypted
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class Context
6
+ # @api private
7
+ class Request
8
+ # @api private
9
+ class Url
10
+ include NaivelyHashable
11
+
12
+ SKIPPED_PORTS = {
13
+ 'http' => 80,
14
+ 'https' => 443
15
+ }.freeze
16
+
17
+ def initialize(req)
18
+ @protocol = req.scheme
19
+ @hostname = req.host
20
+ @port = req.port.to_s
21
+ @pathname = req.path
22
+ @search = req.query_string
23
+ @hash = nil
24
+ @full = build_full_url req
25
+ end
26
+
27
+ attr_reader :protocol, :hostname, :port, :pathname, :search, :hash,
28
+ :full
29
+
30
+ private
31
+
32
+ def build_full_url(req)
33
+ url = "#{req.scheme}://#{req.host}"
34
+
35
+ if req.port != SKIPPED_PORTS[req.scheme]
36
+ url += ":#{req.port}"
37
+ end
38
+
39
+ url + req.fullpath
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Context
5
+ # @api private
6
+ class Response
7
+ include NaivelyHashable
8
+
9
+ def initialize(
10
+ status_code,
11
+ headers: {},
12
+ headers_sent: true,
13
+ finished: true
14
+ )
15
+ @status_code = status_code
16
+ @headers = headers
17
+ @headers_sent = headers_sent
18
+ @finished = finished
19
+ end
20
+
21
+ attr_accessor :status_code, :headers, :headers_sent, :finished
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Context
5
+ # @api private
6
+ class User
7
+ include NaivelyHashable
8
+
9
+ def initialize(config, record)
10
+ return unless record
11
+
12
+ @id = safe_get(record, config.current_user_id_method)
13
+ @email = safe_get(record, config.current_user_email_method)
14
+ @username = safe_get(record, config.current_user_username_method)
15
+ end
16
+
17
+ attr_accessor :id, :email, :username
18
+
19
+ private
20
+
21
+ def safe_get(record, method_name)
22
+ record.respond_to?(method_name) ? record.send(method_name) : nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class ContextBuilder
6
+ CONTROLLER_KEY = 'action_controller.instance'.freeze
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ attr_reader :config
13
+
14
+ def build(rack_env)
15
+ context = Context.new
16
+
17
+ apply_to_request(context, rack_env)
18
+ apply_to_user(context, rack_env)
19
+
20
+ context
21
+ end
22
+
23
+ private
24
+
25
+ # rubocop:disable Metrics/AbcSize
26
+ def apply_to_request(context, rack_env)
27
+ req = rails_req?(rack_env) ? rack_env : Rack::Request.new(rack_env)
28
+
29
+ context.request = Context::Request.new unless context.request
30
+ request = context.request
31
+
32
+ request.socket = Context::Request::Socket.new(req).to_h
33
+ request.http_version = build_http_version rack_env
34
+ request.method = req.request_method
35
+ request.url = Context::Request::Url.new(req).to_h
36
+ request.headers, request.env = get_headers_and_env(rack_env)
37
+ request.body = get_body(req)
38
+
39
+ context
40
+ end
41
+ # rubocop:enable Metrics/AbcSize
42
+
43
+ def apply_to_user(context, rack_env)
44
+ return unless (controller = rack_env[CONTROLLER_KEY])
45
+
46
+ method = config.current_user_method.to_sym
47
+ return unless controller.respond_to?(method)
48
+
49
+ return unless (record = controller.send method)
50
+
51
+ context.user = Context::User.new(config, record)
52
+ context
53
+ end
54
+
55
+ def get_body(req)
56
+ return req.POST if req.form_data?
57
+
58
+ body = req.body.read
59
+ req.body.rewind
60
+ body
61
+ end
62
+
63
+ def rails_req?(env)
64
+ defined?(ActionDispatch::Request) &&
65
+ env.is_a?(ActionDispatch::Request)
66
+ end
67
+
68
+ def get_headers_and_env(rack_env)
69
+ # In Rails < 5 ActionDispatch::Request inherits from Hash
70
+ headers =
71
+ rack_env.respond_to?(:headers) ? rack_env.headers : rack_env
72
+
73
+ headers.each_with_object([{}, {}]) do |(key, value), (http, env)|
74
+ next unless key == key.upcase
75
+
76
+ if key.start_with?('HTTP_')
77
+ http[camel_key(key)] = value
78
+ else
79
+ env[key] = value
80
+ end
81
+ end
82
+ end
83
+
84
+ def camel_key(key)
85
+ key.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
86
+ end
87
+
88
+ def build_http_version(rack_env)
89
+ return unless (http_version = rack_env['HTTP_VERSION'])
90
+ http_version.gsub(%r{HTTP/}, '')
91
+ end
92
+ end
93
+ end
@@ -1,21 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'elastic_apm/stacktrace'
4
+ require 'elastic_apm/context'
4
5
  require 'elastic_apm/error/exception'
5
6
  require 'elastic_apm/error/log'
6
- require 'elastic_apm/error/context'
7
7
 
8
8
  module ElasticAPM
9
9
  # @api private
10
10
  class Error
11
11
  def initialize(culprit: nil)
12
+ @id = SecureRandom.uuid
12
13
  @culprit = culprit
13
14
 
14
15
  @timestamp = Util.micros
15
16
  @context = Context.new
17
+
18
+ @transaction_id = nil
16
19
  end
17
20
 
18
- attr_accessor :culprit, :exception, :log
19
- attr_reader :timestamp, :context
21
+ attr_accessor :id, :culprit, :exception, :log, :transaction_id, :context
22
+ attr_reader :timestamp
20
23
  end
21
24
  end
@@ -9,17 +9,15 @@ module ElasticAPM
9
9
 
10
10
  attr_reader :config
11
11
 
12
- def build_exception(exception, rack_env: nil, handled: true)
12
+ def build_exception(exception, handled: true)
13
13
  error = Error.new
14
14
  error.exception = Error::Exception.new(exception, handled: handled)
15
15
 
16
- if (stacktrace = Stacktrace.build(config, exception.backtrace))
17
- error.exception.stacktrace = stacktrace
18
- error.culprit = stacktrace.frames.last.function
19
- end
16
+ add_stacktrace error, :exception, exception.backtrace
17
+ add_transaction_id error
20
18
 
21
- if rack_env
22
- error.context.request = Error::Context::Request.from_rack_env rack_env
19
+ if (transaction = ElasticAPM.current_transaction)
20
+ error.context = transaction.context.dup
23
21
  end
24
22
 
25
23
  error
@@ -29,12 +27,30 @@ module ElasticAPM
29
27
  error = Error.new
30
28
  error.log = Error::Log.new(message, **attrs)
31
29
 
32
- if (stacktrace = Stacktrace.build(config, backtrace))
30
+ add_stacktrace error, :log, backtrace
31
+ add_transaction_id error
32
+
33
+ error
34
+ end
35
+
36
+ private
37
+
38
+ def add_stacktrace(error, kind, backtrace)
39
+ return unless (stacktrace = Stacktrace.build(config, backtrace))
40
+
41
+ case kind
42
+ when :exception
43
+ error.exception.stacktrace = stacktrace
44
+ when :log
33
45
  error.log.stacktrace = stacktrace
34
- error.culprit = stacktrace.frames.last.function
35
46
  end
36
47
 
37
- error
48
+ error.culprit = stacktrace.frames.last.function
49
+ end
50
+
51
+ def add_transaction_id(error)
52
+ return unless (transaction = ElasticAPM.current_transaction)
53
+ error.transaction_id = transaction.id
38
54
  end
39
55
  end
40
56
  end