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
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,7 @@
1
+ #!/bin/bash -x
2
+ set -e
3
+
4
+ framework=$1
5
+
6
+ FRAMEWORK=$framework bundle update
7
+ FRAMEWORK=$framework ${@:2}
@@ -0,0 +1,22 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'elastic_apm/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'elastic-apm'
7
+ spec.version = ElasticAPM::VERSION
8
+ spec.authors = ['Mikkel Malmberg']
9
+ spec.email = ['mikkel@elastic.co']
10
+
11
+ spec.summary = 'The official Elastic APM agent for Ruby'
12
+ spec.homepage = 'https://github.com/elastic/apm-agent-ruby'
13
+ spec.license = 'Apache-2.0'
14
+ spec.required_ruby_version = ">= 2.0.0"
15
+
16
+ spec.add_dependency('activesupport', '>= 3.0.0')
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.require_paths = ['lib']
22
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Make bundler auto-require work
4
+ require 'elastic_apm'
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/version'
4
+ require 'elastic_apm/log'
5
+
6
+ # Core
7
+ require 'elastic_apm/agent'
8
+ require 'elastic_apm/config'
9
+ require 'elastic_apm/instrumenter'
10
+ require 'elastic_apm/internal_error'
11
+ require 'elastic_apm/util'
12
+
13
+ # Metrics
14
+ require 'elastic_apm/middleware'
15
+
16
+ require 'elastic_apm/railtie' if defined?(::Rails::Railtie)
17
+
18
+ # ElasticAPM
19
+ module ElasticAPM
20
+ ### Life cycle
21
+
22
+ # Starts the ElasticAPM Agent
23
+ #
24
+ # @param config [Config] An instance of Config
25
+ # @return [Agent] The resulting [Agent]
26
+ def self.start(config = {})
27
+ Agent.start config
28
+ end
29
+
30
+ # Stops the ElasticAPM Agent
31
+ def self.stop
32
+ Agent.stop
33
+ end
34
+
35
+ # @return [Boolean] Whether there's an [Agent] running
36
+ def self.running?
37
+ Agent.running?
38
+ end
39
+
40
+ # @return [Agent] Currently running [Agent] if any
41
+ def self.agent
42
+ Agent.instance
43
+ end
44
+
45
+ ### Metrics
46
+
47
+ # Returns the currently active transaction (if any)
48
+ #
49
+ # @return [Transaction] if any
50
+ def self.current_transaction
51
+ agent && agent.current_transaction
52
+ end
53
+
54
+ # Start a new transaction or return the currently running
55
+ #
56
+ # @param name [String] A description of the transaction, eg
57
+ # `ExamplesController#index`
58
+ # @param type [String] The kind of the transaction, eg `app.request.get` or
59
+ # `db.mysql2.query`
60
+ # @yield [Transaction] Optional block encapsulating transaction
61
+ # @return [Transaction] Unless block given
62
+ def self.transaction(name, type = nil, &block)
63
+ agent && agent.transaction(name, type, &block)
64
+ end
65
+
66
+ # Starts a new span under the current Transaction
67
+ #
68
+ # @param name [String] A description of the span, eq `SELECT FROM "users"`
69
+ # @param type [String] The kind of span, eq `db.mysql2.query`
70
+ # @param context [Span::Context] Context information about the span
71
+ # @yield [Span] Optional block encapsulating span
72
+ # @return [Span] Unless block given
73
+ def self.span(name, type = nil, context: nil, &block)
74
+ agent && agent.span(name, type, context: context, &block)
75
+ end
76
+
77
+ ### Errors
78
+
79
+ # Report and exception to APM
80
+ #
81
+ # @param exception [Exception] The exception
82
+ # @param rack_env [Rack::Env] An optional Rack env
83
+ # @param handled [Boolean] Whether the exception was rescued
84
+ # @return [Error] An [Error] instance
85
+ def self.report(exception, rack_env: nil, handled: true)
86
+ agent && agent.report(exception, rack_env: rack_env, handled: handled)
87
+ end
88
+
89
+ def self.report_message(message, **attrs)
90
+ agent && agent.report_message(message, backtrace: caller, **attrs)
91
+ end
92
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/error_builder'
4
+ require 'elastic_apm/error'
5
+ require 'elastic_apm/http'
6
+ require 'elastic_apm/injectors'
7
+ require 'elastic_apm/serializers'
8
+ require 'elastic_apm/worker'
9
+
10
+ module ElasticAPM
11
+ # rubocop:disable Metrics/ClassLength
12
+ # @api private
13
+ class Agent
14
+ include Log
15
+
16
+ LOCK = Mutex.new
17
+
18
+ # life cycle
19
+
20
+ def self.instance # rubocop:disable Style/TrivialAccessors
21
+ @instance
22
+ end
23
+
24
+ def self.start(config)
25
+ return @instance if @instance
26
+
27
+ LOCK.synchronize do
28
+ return @instance if @instance
29
+ @instance = new(config).start
30
+ end
31
+ end
32
+
33
+ def self.stop
34
+ LOCK.synchronize do
35
+ return unless @instance
36
+
37
+ @instance.stop
38
+ @instance = nil
39
+ end
40
+ end
41
+
42
+ def self.running?
43
+ !!@instance
44
+ end
45
+
46
+ def initialize(config)
47
+ config = Config.new(config) if config.is_a?(Hash)
48
+
49
+ @config = config
50
+
51
+ @queue = Queue.new
52
+
53
+ @instrumenter = Instrumenter.new(config, self)
54
+ @error_builder = ErrorBuilder.new(config)
55
+
56
+ @serializers = Struct.new(:transactions, :errors).new(
57
+ Serializers::Transactions.new(config),
58
+ Serializers::Errors.new(config)
59
+ )
60
+ end
61
+
62
+ attr_reader :config, :queue, :instrumenter
63
+
64
+ def start
65
+ debug 'Starting agent reporting to %s', config.server
66
+
67
+ @instrumenter.start
68
+
69
+ boot_worker
70
+
71
+ config.enabled_injectors.each do |lib|
72
+ require "elastic_apm/injectors/#{lib}"
73
+ end
74
+
75
+ self
76
+ end
77
+
78
+ def stop
79
+ debug 'Stopping agent'
80
+
81
+ @instrumenter.stop
82
+
83
+ kill_worker
84
+
85
+ self
86
+ end
87
+
88
+ at_exit do
89
+ stop
90
+ end
91
+
92
+ def enqueue_transactions(transactions)
93
+ data = @serializers.transactions.build_all(transactions)
94
+ @queue << Worker::Request.new('/v1/transactions', data)
95
+ end
96
+
97
+ def enqueue_errors(errors)
98
+ data = @serializers.errors.build_all(errors)
99
+ @queue << Worker::Request.new('/v1/errors', data)
100
+ errors
101
+ end
102
+
103
+ # instrumentation
104
+
105
+ def current_transaction
106
+ instrumenter.current_transaction
107
+ end
108
+
109
+ def transaction(*args, &block)
110
+ instrumenter.transaction(*args, &block)
111
+ end
112
+
113
+ def span(*args, &block)
114
+ instrumenter.span(*args, &block)
115
+ end
116
+
117
+ # errors
118
+
119
+ def report(exception, rack_env: nil, handled: true)
120
+ error = @error_builder.build_exception(
121
+ exception,
122
+ rack_env: rack_env,
123
+ handled: handled
124
+ )
125
+ enqueue_errors error
126
+ end
127
+
128
+ def report_message(message, backtrace: nil, **attrs)
129
+ error = @error_builder.build_log(
130
+ message,
131
+ backtrace: backtrace,
132
+ **attrs
133
+ )
134
+ enqueue_errors error
135
+ end
136
+
137
+ def inspect
138
+ '<ElasticAPM::Agent>'
139
+ end
140
+
141
+ private
142
+
143
+ def boot_worker
144
+ debug 'Booting worker in thread'
145
+
146
+ @worker_thread = Thread.new do
147
+ Worker.new(@config, @queue).run_forever
148
+ end
149
+ end
150
+
151
+ def kill_worker
152
+ @queue << Worker::StopMessage.new
153
+
154
+ unless @worker_thread.join(5) # 5 secs
155
+ raise 'Failed to wait for worker, not all messages sent'
156
+ end
157
+
158
+ @worker_thread = nil
159
+
160
+ debug 'Killed worker'
161
+ end
162
+ end
163
+ # rubocop:enable Metrics/ClassLength
164
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module ElasticAPM
6
+ # @api private
7
+ class Config
8
+ DEFAULTS = {
9
+ server: 'http://localhost:8200',
10
+ secret_token: nil,
11
+
12
+ app_name: nil,
13
+ environment: nil,
14
+ framework_name: nil,
15
+ framework_version: nil,
16
+
17
+ log_path: '-',
18
+ log_level: Logger::INFO,
19
+
20
+ timeout: 10,
21
+ open_timeout: 10,
22
+ transaction_send_interval: 10,
23
+ debug_transactions: false,
24
+ debug_http: false,
25
+
26
+ enabled_injectors: %w[net_http],
27
+
28
+ view_paths: []
29
+ }.freeze
30
+
31
+ LOCK = Mutex.new
32
+
33
+ def initialize(options = nil)
34
+ options = {} if options.nil?
35
+
36
+ DEFAULTS.merge(options).each do |key, value|
37
+ send("#{key}=", value)
38
+ end
39
+
40
+ return unless block_given?
41
+
42
+ yield self
43
+ end
44
+
45
+ attr_accessor :server
46
+ attr_accessor :secret_token
47
+
48
+ attr_accessor :app_name
49
+ attr_writer :environment
50
+ attr_accessor :framework_name
51
+ attr_accessor :framework_version
52
+
53
+ attr_accessor :log_path
54
+ attr_accessor :log_level
55
+
56
+ attr_accessor :timeout
57
+ attr_accessor :open_timeout
58
+ attr_accessor :transaction_send_interval
59
+ attr_accessor :debug_transactions
60
+ attr_accessor :debug_http
61
+
62
+ attr_accessor :enabled_injectors
63
+
64
+ attr_accessor :view_paths
65
+
66
+ attr_writer :logger
67
+
68
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
69
+ def app=(app)
70
+ case app_type?(app)
71
+ when :sinatra
72
+ self.app_name = format_name(app_name || app.to_s)
73
+ self.framework_name = 'Sinatra'
74
+ self.framework_version = Sinatra::VERSION
75
+ when :rails
76
+ self.app_name = format_name(app_name || app.class.parent_name)
77
+ self.framework_name = 'Ruby on Rails'
78
+ self.framework_version = Rails::VERSION::STRING
79
+ self.logger = Rails.logger
80
+ self.view_paths = app.config.paths['app/views'].existent
81
+ end
82
+ end
83
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
84
+
85
+ def app_type?(app)
86
+ if defined?(::Rails) && app.is_a?(Rails::Application)
87
+ return :rails
88
+ end
89
+
90
+ if app.is_a?(Class) && app.superclass.to_s == 'Sinatra::Base'
91
+ return :sinatra
92
+ end
93
+
94
+ nil
95
+ end
96
+
97
+ def environment
98
+ @environment ||= ENV['RAILS_ENV'] || ENV['RACK_ENV']
99
+ end
100
+
101
+ def logger
102
+ @logger ||=
103
+ LOCK.synchronize do
104
+ build_logger(log_path, log_level)
105
+ end
106
+ end
107
+
108
+ def use_ssl?
109
+ server.start_with?('https')
110
+ end
111
+
112
+ private
113
+
114
+ def build_logger(path, level)
115
+ logger = Logger.new(path == '-' ? STDOUT : path)
116
+ logger.level = level
117
+ logger
118
+ end
119
+
120
+ def format_name(str)
121
+ str.gsub('::', '_')
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/stacktrace'
4
+ require 'elastic_apm/error/exception'
5
+ require 'elastic_apm/error/log'
6
+ require 'elastic_apm/error/context'
7
+
8
+ module ElasticAPM
9
+ # @api private
10
+ class Error
11
+ def initialize(culprit: nil)
12
+ @culprit = culprit
13
+
14
+ @timestamp = Util.micros
15
+ @context = Context.new
16
+ end
17
+
18
+ attr_accessor :culprit, :exception, :log
19
+ attr_reader :timestamp, :context
20
+ end
21
+ end