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.

Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +47 -0
  5. data/.travis.yml +5 -0
  6. data/CODE_OF_CONDUCT.md +47 -0
  7. data/Gemfile +38 -0
  8. data/LICENSE +201 -0
  9. data/README.md +55 -0
  10. data/Rakefile +12 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/bin/with_framework +7 -0
  14. data/elastic-apm.gemspec +22 -0
  15. data/lib/elastic-apm.rb +4 -0
  16. data/lib/elastic_apm.rb +92 -0
  17. data/lib/elastic_apm/agent.rb +164 -0
  18. data/lib/elastic_apm/config.rb +124 -0
  19. data/lib/elastic_apm/error.rb +21 -0
  20. data/lib/elastic_apm/error/context.rb +119 -0
  21. data/lib/elastic_apm/error/exception.rb +37 -0
  22. data/lib/elastic_apm/error/log.rb +24 -0
  23. data/lib/elastic_apm/error_builder.rb +40 -0
  24. data/lib/elastic_apm/http.rb +103 -0
  25. data/lib/elastic_apm/injectors.rb +71 -0
  26. data/lib/elastic_apm/injectors/action_dispatch.rb +26 -0
  27. data/lib/elastic_apm/injectors/json.rb +22 -0
  28. data/lib/elastic_apm/injectors/net_http.rb +50 -0
  29. data/lib/elastic_apm/injectors/redis.rb +33 -0
  30. data/lib/elastic_apm/injectors/sequel.rb +45 -0
  31. data/lib/elastic_apm/injectors/sinatra.rb +41 -0
  32. data/lib/elastic_apm/injectors/tilt.rb +27 -0
  33. data/lib/elastic_apm/instrumenter.rb +112 -0
  34. data/lib/elastic_apm/internal_error.rb +5 -0
  35. data/lib/elastic_apm/log.rb +47 -0
  36. data/lib/elastic_apm/middleware.rb +30 -0
  37. data/lib/elastic_apm/normalizers.rb +63 -0
  38. data/lib/elastic_apm/normalizers/action_controller.rb +24 -0
  39. data/lib/elastic_apm/normalizers/action_view.rb +72 -0
  40. data/lib/elastic_apm/normalizers/active_record.rb +41 -0
  41. data/lib/elastic_apm/railtie.rb +43 -0
  42. data/lib/elastic_apm/serializers.rb +26 -0
  43. data/lib/elastic_apm/serializers/errors.rb +40 -0
  44. data/lib/elastic_apm/serializers/transactions.rb +36 -0
  45. data/lib/elastic_apm/service_info.rb +66 -0
  46. data/lib/elastic_apm/span.rb +51 -0
  47. data/lib/elastic_apm/span/context.rb +20 -0
  48. data/lib/elastic_apm/span_helpers.rb +37 -0
  49. data/lib/elastic_apm/sql_summarizer.rb +26 -0
  50. data/lib/elastic_apm/stacktrace.rb +84 -0
  51. data/lib/elastic_apm/stacktrace/frame.rb +62 -0
  52. data/lib/elastic_apm/subscriber.rb +72 -0
  53. data/lib/elastic_apm/system_info.rb +30 -0
  54. data/lib/elastic_apm/transaction.rb +92 -0
  55. data/lib/elastic_apm/util.rb +20 -0
  56. data/lib/elastic_apm/util/inspector.rb +61 -0
  57. data/lib/elastic_apm/version.rb +5 -0
  58. data/lib/elastic_apm/worker.rb +48 -0
  59. data/vendor/.gitkeep +0 -0
  60. 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