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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +3 -0
- data/README.md +7 -1
- data/docs/api.asciidoc +210 -0
- data/docs/configuration.asciidoc +32 -0
- data/docs/context.asciidoc +27 -0
- data/docs/custom-instrumentation.asciidoc +14 -0
- data/docs/getting-started-rack.asciidoc +26 -0
- data/docs/getting-started-rails.asciidoc +13 -0
- data/docs/index.asciidoc +33 -0
- data/elastic-apm.gemspec +1 -0
- data/lib/elastic_apm.rb +54 -7
- data/lib/elastic_apm/agent.rb +20 -4
- data/lib/elastic_apm/config.rb +16 -3
- data/lib/elastic_apm/context.rb +22 -0
- data/lib/elastic_apm/context/request.rb +13 -0
- data/lib/elastic_apm/context/request/socket.rb +21 -0
- data/lib/elastic_apm/context/request/url.rb +44 -0
- data/lib/elastic_apm/context/response.rb +24 -0
- data/lib/elastic_apm/context/user.rb +26 -0
- data/lib/elastic_apm/context_builder.rb +93 -0
- data/lib/elastic_apm/error.rb +6 -3
- data/lib/elastic_apm/error_builder.rb +26 -10
- data/lib/elastic_apm/http.rb +4 -2
- data/lib/elastic_apm/injectors/action_dispatch.rb +1 -1
- data/lib/elastic_apm/instrumenter.rb +18 -0
- data/lib/elastic_apm/middleware.rb +15 -3
- data/lib/elastic_apm/naively_hashable.rb +21 -0
- data/lib/elastic_apm/process_info.rb +22 -0
- data/lib/elastic_apm/serializers/errors.rb +10 -3
- data/lib/elastic_apm/serializers/transactions.rb +4 -2
- data/lib/elastic_apm/service_info.rb +0 -3
- data/lib/elastic_apm/span/context.rb +2 -4
- data/lib/elastic_apm/stacktrace/frame.rb +2 -18
- data/lib/elastic_apm/transaction.rb +20 -7
- data/lib/elastic_apm/version.rb +1 -1
- metadata +20 -4
- data/lib/elastic_apm/error/context.rb +0 -119
data/lib/elastic_apm/agent.rb
CHANGED
@@ -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.
|
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,
|
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
|
data/lib/elastic_apm/config.rb
CHANGED
@@ -6,7 +6,7 @@ module ElasticAPM
|
|
6
6
|
# @api private
|
7
7
|
class Config
|
8
8
|
DEFAULTS = {
|
9
|
-
|
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 :
|
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
|
-
|
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,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
|
data/lib/elastic_apm/error.rb
CHANGED
@@ -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
|
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,
|
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
|
-
|
17
|
-
|
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
|
22
|
-
error.context
|
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
|
-
|
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
|