elastic-apm 0.1.0 → 0.2.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.
- 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
|