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.
- 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
data/bin/setup
ADDED
data/bin/with_framework
ADDED
data/elastic-apm.gemspec
ADDED
@@ -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
|
data/lib/elastic-apm.rb
ADDED
data/lib/elastic_apm.rb
ADDED
@@ -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
|