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
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