elastic-apm 0.1.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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +47 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +47 -0
- data/Gemfile +38 -0
- data/LICENSE +201 -0
- data/README.md +55 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/with_framework +7 -0
- data/elastic-apm.gemspec +22 -0
- data/lib/elastic-apm.rb +4 -0
- data/lib/elastic_apm.rb +92 -0
- data/lib/elastic_apm/agent.rb +164 -0
- data/lib/elastic_apm/config.rb +124 -0
- data/lib/elastic_apm/error.rb +21 -0
- data/lib/elastic_apm/error/context.rb +119 -0
- data/lib/elastic_apm/error/exception.rb +37 -0
- data/lib/elastic_apm/error/log.rb +24 -0
- data/lib/elastic_apm/error_builder.rb +40 -0
- data/lib/elastic_apm/http.rb +103 -0
- data/lib/elastic_apm/injectors.rb +71 -0
- data/lib/elastic_apm/injectors/action_dispatch.rb +26 -0
- data/lib/elastic_apm/injectors/json.rb +22 -0
- data/lib/elastic_apm/injectors/net_http.rb +50 -0
- data/lib/elastic_apm/injectors/redis.rb +33 -0
- data/lib/elastic_apm/injectors/sequel.rb +45 -0
- data/lib/elastic_apm/injectors/sinatra.rb +41 -0
- data/lib/elastic_apm/injectors/tilt.rb +27 -0
- data/lib/elastic_apm/instrumenter.rb +112 -0
- data/lib/elastic_apm/internal_error.rb +5 -0
- data/lib/elastic_apm/log.rb +47 -0
- data/lib/elastic_apm/middleware.rb +30 -0
- data/lib/elastic_apm/normalizers.rb +63 -0
- data/lib/elastic_apm/normalizers/action_controller.rb +24 -0
- data/lib/elastic_apm/normalizers/action_view.rb +72 -0
- data/lib/elastic_apm/normalizers/active_record.rb +41 -0
- data/lib/elastic_apm/railtie.rb +43 -0
- data/lib/elastic_apm/serializers.rb +26 -0
- data/lib/elastic_apm/serializers/errors.rb +40 -0
- data/lib/elastic_apm/serializers/transactions.rb +36 -0
- data/lib/elastic_apm/service_info.rb +66 -0
- data/lib/elastic_apm/span.rb +51 -0
- data/lib/elastic_apm/span/context.rb +20 -0
- data/lib/elastic_apm/span_helpers.rb +37 -0
- data/lib/elastic_apm/sql_summarizer.rb +26 -0
- data/lib/elastic_apm/stacktrace.rb +84 -0
- data/lib/elastic_apm/stacktrace/frame.rb +62 -0
- data/lib/elastic_apm/subscriber.rb +72 -0
- data/lib/elastic_apm/system_info.rb +30 -0
- data/lib/elastic_apm/transaction.rb +92 -0
- data/lib/elastic_apm/util.rb +20 -0
- data/lib/elastic_apm/util/inspector.rb +61 -0
- data/lib/elastic_apm/version.rb +5 -0
- data/lib/elastic_apm/worker.rb +48 -0
- data/vendor/.gitkeep +0 -0
- metadata +116 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
class Error
|
5
|
+
# @api private
|
6
|
+
class Context
|
7
|
+
# @api private
|
8
|
+
class Request
|
9
|
+
def initialize
|
10
|
+
@socket = {}
|
11
|
+
@headers = {}
|
12
|
+
@cookies = {}
|
13
|
+
@env = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor(
|
17
|
+
:socket,
|
18
|
+
:http_version,
|
19
|
+
:method,
|
20
|
+
:url,
|
21
|
+
:headers,
|
22
|
+
:cookies,
|
23
|
+
:env,
|
24
|
+
:body
|
25
|
+
)
|
26
|
+
|
27
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
28
|
+
def add_rack_env(rack_env)
|
29
|
+
req = rails_req?(rack_env) ? rack_env : Rack::Request.new(rack_env)
|
30
|
+
|
31
|
+
self.socket = {
|
32
|
+
remote_address: req.ip,
|
33
|
+
encrypted: req.scheme == 'https'
|
34
|
+
}
|
35
|
+
http_version = rack_env['HTTP_VERSION']
|
36
|
+
self.http_version =
|
37
|
+
http_version && http_version.gsub(%r{HTTP/}, '')
|
38
|
+
self.method = req.request_method
|
39
|
+
self.url = {
|
40
|
+
protocol: req.scheme,
|
41
|
+
hostname: req.host,
|
42
|
+
port: req.port,
|
43
|
+
pathname: req.path,
|
44
|
+
search: req.query_string,
|
45
|
+
hash: nil,
|
46
|
+
raw: req.fullpath
|
47
|
+
}
|
48
|
+
|
49
|
+
add_headers(rack_env)
|
50
|
+
add_body(req)
|
51
|
+
|
52
|
+
self
|
53
|
+
end
|
54
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
55
|
+
|
56
|
+
def self.from_rack_env(rack_env)
|
57
|
+
request = new
|
58
|
+
request.add_rack_env rack_env
|
59
|
+
request
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def add_body(req)
|
65
|
+
if req.form_data?
|
66
|
+
self.body = req.POST
|
67
|
+
else
|
68
|
+
self.body = req.body.read
|
69
|
+
req.body.rewind
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def rails_req?(env)
|
74
|
+
defined?(ActionDispatch::Request) &&
|
75
|
+
env.is_a?(ActionDispatch::Request)
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_headers(rack_env)
|
79
|
+
get_headers(rack_env).each do |key, value|
|
80
|
+
next unless key.upcase == key
|
81
|
+
|
82
|
+
if key.start_with?('HTTP_')
|
83
|
+
headers[camel_key(key)] = value
|
84
|
+
else
|
85
|
+
env[key] = value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def camel_key(key)
|
91
|
+
key.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_headers(rack_env)
|
95
|
+
# In Rails < 5 ActionDispatch::Request inherits from Hash
|
96
|
+
rack_env.respond_to?(:headers) ? rack_env.headers : rack_env
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# @api private
|
101
|
+
class Response
|
102
|
+
attr_accessor(
|
103
|
+
:status_code,
|
104
|
+
:headers,
|
105
|
+
:headers_sent,
|
106
|
+
:finished
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_accessor(
|
111
|
+
:request,
|
112
|
+
:response,
|
113
|
+
:user,
|
114
|
+
:tags,
|
115
|
+
:custom
|
116
|
+
)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
class Error
|
5
|
+
# @api private
|
6
|
+
class Exception
|
7
|
+
MOD_SPLIT = '::'.freeze
|
8
|
+
|
9
|
+
def initialize(exception, **attrs)
|
10
|
+
@message =
|
11
|
+
"#{exception.class}: #{exception.message}"
|
12
|
+
@type = exception.class.to_s
|
13
|
+
@module = format_module exception
|
14
|
+
|
15
|
+
attrs.each do |key, val|
|
16
|
+
send(:"#{key}=", val)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor(
|
21
|
+
:attributes,
|
22
|
+
:code,
|
23
|
+
:handled,
|
24
|
+
:message,
|
25
|
+
:module,
|
26
|
+
:stacktrace,
|
27
|
+
:type
|
28
|
+
)
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def format_module(exception)
|
33
|
+
exception.class.to_s.split(MOD_SPLIT)[0...-1].join(MOD_SPLIT)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
class Error
|
5
|
+
# @api private
|
6
|
+
class Log
|
7
|
+
def initialize(message, attrs = {})
|
8
|
+
@message = message
|
9
|
+
|
10
|
+
attrs.each do |key, val|
|
11
|
+
send(:"#{key}=", val)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor(
|
16
|
+
:level,
|
17
|
+
:logger_name,
|
18
|
+
:message,
|
19
|
+
:param_message,
|
20
|
+
:stacktrace
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
class ErrorBuilder
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def build_exception(exception, rack_env: nil, handled: true)
|
13
|
+
error = Error.new
|
14
|
+
error.exception = Error::Exception.new(exception, handled: handled)
|
15
|
+
|
16
|
+
if (stacktrace = Stacktrace.build(config, exception.backtrace))
|
17
|
+
error.exception.stacktrace = stacktrace
|
18
|
+
error.culprit = stacktrace.frames.last.function
|
19
|
+
end
|
20
|
+
|
21
|
+
if rack_env
|
22
|
+
error.context.request = Error::Context::Request.from_rack_env rack_env
|
23
|
+
end
|
24
|
+
|
25
|
+
error
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_log(message, backtrace: nil, **attrs)
|
29
|
+
error = Error.new
|
30
|
+
error.log = Error::Log.new(message, **attrs)
|
31
|
+
|
32
|
+
if (stacktrace = Stacktrace.build(config, backtrace))
|
33
|
+
error.log.stacktrace = stacktrace
|
34
|
+
error.culprit = stacktrace.frames.last.function
|
35
|
+
end
|
36
|
+
|
37
|
+
error
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
require 'elastic_apm/service_info'
|
6
|
+
require 'elastic_apm/system_info'
|
7
|
+
|
8
|
+
module ElasticAPM
|
9
|
+
# @api private
|
10
|
+
class Http
|
11
|
+
include Log
|
12
|
+
|
13
|
+
USER_AGENT = "elastic-apm/ruby #{VERSION}".freeze
|
14
|
+
ACCEPT = 'application/json'.freeze
|
15
|
+
CONTENT_TYPE = 'application/json'.freeze
|
16
|
+
|
17
|
+
def initialize(config, adapter: HttpAdapter)
|
18
|
+
@config = config
|
19
|
+
@adapter = adapter.new(config)
|
20
|
+
@base_payload = {
|
21
|
+
service: ServiceInfo.build(config),
|
22
|
+
system: SystemInfo.build(config)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :config
|
27
|
+
|
28
|
+
def post(path, payload = {})
|
29
|
+
payload.merge! @base_payload
|
30
|
+
request = prepare_request path, payload.to_json
|
31
|
+
response = @adapter.perform request
|
32
|
+
|
33
|
+
status = response.code.to_i
|
34
|
+
return response if status >= 200 && status <= 299
|
35
|
+
|
36
|
+
error "POST returned an unsuccessful status code (#{response.code})"
|
37
|
+
debug response.body
|
38
|
+
|
39
|
+
response
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def prepare_request(path, data)
|
45
|
+
@adapter.post url_for(path) do |req|
|
46
|
+
req['Accept'] = ACCEPT
|
47
|
+
req['Content-Type'] = CONTENT_TYPE
|
48
|
+
req['User-Agent'] = USER_AGENT
|
49
|
+
req['Content-Length'] = data.bytesize.to_s
|
50
|
+
|
51
|
+
if (token = config.secret_token)
|
52
|
+
req['Authorization'] = "Bearer #{token}"
|
53
|
+
end
|
54
|
+
|
55
|
+
req.body = data
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def url_for(path)
|
60
|
+
"#{@config.server}#{path}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
class HttpAdapter
|
66
|
+
def initialize(conf)
|
67
|
+
@config = conf
|
68
|
+
end
|
69
|
+
|
70
|
+
def post(path)
|
71
|
+
req = Net::HTTP::Post.new path
|
72
|
+
yield req if block_given?
|
73
|
+
req
|
74
|
+
end
|
75
|
+
|
76
|
+
def perform(req)
|
77
|
+
http.start do |http|
|
78
|
+
http.request req
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def http
|
85
|
+
return @http if @http
|
86
|
+
|
87
|
+
http = Net::HTTP.new server_uri.host, server_uri.port
|
88
|
+
http.use_ssl = @config.use_ssl?
|
89
|
+
http.read_timeout = @config.timeout
|
90
|
+
http.open_timeout = @config.open_timeout
|
91
|
+
|
92
|
+
if @config.debug_http
|
93
|
+
http.set_debug_output(@config.logger)
|
94
|
+
end
|
95
|
+
|
96
|
+
@http = http
|
97
|
+
end
|
98
|
+
|
99
|
+
def server_uri
|
100
|
+
@uri ||= URI(@config.server)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/inflector'
|
4
|
+
|
5
|
+
module ElasticAPM
|
6
|
+
# @api private
|
7
|
+
module Injectors
|
8
|
+
# @api private
|
9
|
+
class Registration
|
10
|
+
def initialize(const_name, require_paths, injector)
|
11
|
+
@const_name = const_name
|
12
|
+
@require_paths = Array(require_paths)
|
13
|
+
@injector = injector
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :const_name, :require_paths, :injector
|
17
|
+
|
18
|
+
def install
|
19
|
+
injector.install
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.require_hooks
|
24
|
+
@require_hooks ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.installed
|
28
|
+
@installed ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.register(*args)
|
32
|
+
registration = Registration.new(*args)
|
33
|
+
|
34
|
+
if const_defined?(registration.const_name)
|
35
|
+
installed[registration.const_name] = registration
|
36
|
+
registration.install
|
37
|
+
else
|
38
|
+
register_require_hook registration
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.register_require_hook(registration)
|
43
|
+
registration.require_paths.each do |path|
|
44
|
+
require_hooks[path] = registration
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.const_defined?(const_name)
|
49
|
+
const = ActiveSupport::Inflector.constantize(const_name)
|
50
|
+
!!const
|
51
|
+
rescue NameError
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
module Kernel
|
59
|
+
alias require_without_apm require
|
60
|
+
|
61
|
+
def require(path)
|
62
|
+
res = require_without_apm(path)
|
63
|
+
|
64
|
+
begin
|
65
|
+
ElasticAPM::Injectors.hook_into(path)
|
66
|
+
rescue ::Exception # rubocop:disable Lint/HandleExceptions
|
67
|
+
end
|
68
|
+
|
69
|
+
res
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Injectors
|
6
|
+
# @api private
|
7
|
+
class ActionDispatchInjector
|
8
|
+
def install
|
9
|
+
::ActionDispatch::ShowExceptions.class_eval do
|
10
|
+
alias render_exception_without_apm render_exception
|
11
|
+
|
12
|
+
def render_exception(env, exception)
|
13
|
+
ElasticAPM.report(exception, rack_env: env)
|
14
|
+
render_exception_without_apm env, exception
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
register(
|
21
|
+
'ActionDispatch::ShowExceptions',
|
22
|
+
'action_dispatch/show_exception',
|
23
|
+
ActionDispatchInjector.new
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elastic_apm/span_helpers'
|
4
|
+
|
5
|
+
module ElasticAPM
|
6
|
+
# @api private
|
7
|
+
module Injectors
|
8
|
+
# @api private
|
9
|
+
class JSONInjector
|
10
|
+
def install
|
11
|
+
::JSON.class_eval do
|
12
|
+
include SpanHelpers
|
13
|
+
span_class_method :parse, 'JSON#parse', 'json.parse'
|
14
|
+
span_class_method :parse!, 'JSON#parse!', 'json.parse'
|
15
|
+
span_class_method :generate, 'JSON#generate', 'json.generate'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
register 'JSON', 'json', JSONInjector.new
|
21
|
+
end
|
22
|
+
end
|