elastic-apm 0.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.
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
|