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