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.

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