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
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
module ElasticAPM
|
6
|
+
# @api private
|
7
|
+
module Injectors
|
8
|
+
# @api private
|
9
|
+
class NetHTTPInjector
|
10
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
11
|
+
def install
|
12
|
+
Net::HTTP.class_eval do
|
13
|
+
alias request_without_apm request
|
14
|
+
|
15
|
+
def request(req, body = nil, &block)
|
16
|
+
unless ElasticAPM.current_transaction
|
17
|
+
return request_without_apm(req, body, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
host, port = req['host'] && req['host'].split(':')
|
21
|
+
method = req.method
|
22
|
+
path = req.path
|
23
|
+
scheme = use_ssl? ? 'https' : 'http'
|
24
|
+
|
25
|
+
# inside a session
|
26
|
+
host ||= address
|
27
|
+
port ||= 80
|
28
|
+
|
29
|
+
# TODO: investigate
|
30
|
+
_extra = {
|
31
|
+
scheme: scheme,
|
32
|
+
port: port,
|
33
|
+
path: path
|
34
|
+
}
|
35
|
+
|
36
|
+
name = "#{method} #{host}"
|
37
|
+
type = "ext.net_http.#{method}"
|
38
|
+
|
39
|
+
ElasticAPM.span name, type do
|
40
|
+
request_without_apm(req, body, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
46
|
+
end
|
47
|
+
|
48
|
+
register 'Net::HTTP', 'net/http', NetHTTPInjector.new
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Injectors
|
6
|
+
# @api private
|
7
|
+
class RedisInjector
|
8
|
+
# rubocop:disable Metrics/MethodLength
|
9
|
+
def install
|
10
|
+
::Redis::Client.class_eval do
|
11
|
+
alias call_without_apm call
|
12
|
+
|
13
|
+
def call(command, &block)
|
14
|
+
name = command[0].upcase
|
15
|
+
statement =
|
16
|
+
format('%s %s', name, command[1..command.length].join(' '))
|
17
|
+
context = Span::Context.new(
|
18
|
+
statement: statement,
|
19
|
+
type: 'redis'
|
20
|
+
)
|
21
|
+
|
22
|
+
ElasticAPM.span(name.to_s, 'db.redis', context: context) do
|
23
|
+
call_without_apm(command, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# rubocop:enable Metrics/MethodLength
|
29
|
+
end
|
30
|
+
|
31
|
+
register 'Redis', 'redis', RedisInjector.new
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elastic_apm/sql_summarizer'
|
4
|
+
|
5
|
+
module ElasticAPM
|
6
|
+
# @api private
|
7
|
+
module Injectors
|
8
|
+
# @api private
|
9
|
+
class SequelInjector
|
10
|
+
TYPE = 'db.sequel.sql'.freeze
|
11
|
+
|
12
|
+
def self.summarizer
|
13
|
+
@summarizer ||= SqlSummarizer.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
17
|
+
def install
|
18
|
+
require 'sequel/database/logging'
|
19
|
+
|
20
|
+
::Sequel::Database.class_eval do
|
21
|
+
alias log_connection_yield_without_apm log_connection_yield
|
22
|
+
|
23
|
+
def log_connection_yield(sql, *args, &block)
|
24
|
+
unless ElasticAPM.current_transaction
|
25
|
+
return log_connection_yield_without_apm(sql, *args, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
summarizer = ElasticAPM::Injectors::SequelInjector.summarizer
|
29
|
+
name = summarizer.summarize sql
|
30
|
+
context = Span::Context.new(
|
31
|
+
statement: sql,
|
32
|
+
type: 'sql',
|
33
|
+
user: opts[:user]
|
34
|
+
)
|
35
|
+
|
36
|
+
ElasticAPM.span(name, TYPE, context: context, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# rubocop:enable Metrics/MethodLength
|
41
|
+
end
|
42
|
+
|
43
|
+
register 'Sequel', 'sequel', SequelInjector.new
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Injectors
|
6
|
+
# @api private
|
7
|
+
class SinatraInjector
|
8
|
+
# rubocop:disable Metrics/MethodLength
|
9
|
+
def install
|
10
|
+
::Sinatra::Base.class_eval do
|
11
|
+
alias dispatch_without_apm! dispatch!
|
12
|
+
alias compile_template_without_apm compile_template
|
13
|
+
|
14
|
+
def dispatch!(*args, &block)
|
15
|
+
dispatch_without_apm!(*args, &block).tap do
|
16
|
+
next unless (transaction = ElasticAPM.current_transaction)
|
17
|
+
next unless (route = env['sinatra.route'])
|
18
|
+
|
19
|
+
transaction.name = route
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def compile_template(engine, data, opts, *args, &block)
|
24
|
+
opts[:__elastic_apm_template_name] =
|
25
|
+
case data
|
26
|
+
when Symbol then data.to_s
|
27
|
+
else format('Inline %s', engine)
|
28
|
+
end
|
29
|
+
|
30
|
+
compile_template_without_apm(engine, data, opts, *args, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# rubocop:enable Metrics/MethodLength
|
35
|
+
end
|
36
|
+
|
37
|
+
register 'Sinatra::Base', 'sinatra/base', SinatraInjector.new
|
38
|
+
|
39
|
+
require 'elastic_apm/injectors/tilt'
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Injectors
|
6
|
+
# @api private
|
7
|
+
class TiltInjector
|
8
|
+
TYPE = 'template.tilt'.freeze
|
9
|
+
|
10
|
+
def install
|
11
|
+
::Tilt::Template.class_eval do
|
12
|
+
alias render_without_apm render
|
13
|
+
|
14
|
+
def render(*args, &block)
|
15
|
+
name = options[:__elastic_apm_template_name] || 'Unknown template'
|
16
|
+
|
17
|
+
ElasticAPM.span name, TYPE do
|
18
|
+
render_without_apm(*args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
register 'Tilt::Template', 'tilt/template', TiltInjector.new
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elastic_apm/subscriber'
|
4
|
+
require 'elastic_apm/span'
|
5
|
+
require 'elastic_apm/transaction'
|
6
|
+
|
7
|
+
module ElasticAPM
|
8
|
+
# @api private
|
9
|
+
class Instrumenter
|
10
|
+
include Log
|
11
|
+
|
12
|
+
KEY = :__elastic_transaction_key
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
class TransactionInfo
|
16
|
+
def current
|
17
|
+
Thread.current[KEY]
|
18
|
+
end
|
19
|
+
|
20
|
+
def current=(transaction)
|
21
|
+
Thread.current[KEY] = transaction
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(config, agent, subscriber_class: Subscriber)
|
26
|
+
@config = config
|
27
|
+
@agent = agent
|
28
|
+
|
29
|
+
@transaction_info = TransactionInfo.new
|
30
|
+
|
31
|
+
@subscriber = subscriber_class.new(self)
|
32
|
+
|
33
|
+
@pending_transactions = []
|
34
|
+
@last_sent_transactions = Time.now.utc
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :config, :pending_transactions
|
38
|
+
|
39
|
+
def start
|
40
|
+
@subscriber.register!
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop
|
44
|
+
current_transaction.release if current_transaction
|
45
|
+
@subscriber.unregister!
|
46
|
+
end
|
47
|
+
|
48
|
+
def current_transaction
|
49
|
+
@transaction_info.current
|
50
|
+
end
|
51
|
+
|
52
|
+
def current_transaction=(transaction)
|
53
|
+
@transaction_info.current = transaction
|
54
|
+
end
|
55
|
+
|
56
|
+
# rubocop:disable Metrics/MethodLength
|
57
|
+
def transaction(*args)
|
58
|
+
if (transaction = current_transaction)
|
59
|
+
yield transaction if block_given?
|
60
|
+
return transaction
|
61
|
+
end
|
62
|
+
|
63
|
+
transaction = Transaction.new self, *args
|
64
|
+
|
65
|
+
self.current_transaction = transaction
|
66
|
+
return transaction unless block_given?
|
67
|
+
|
68
|
+
begin
|
69
|
+
yield transaction
|
70
|
+
ensure
|
71
|
+
self.current_transaction = nil
|
72
|
+
transaction.done
|
73
|
+
end
|
74
|
+
|
75
|
+
transaction
|
76
|
+
end
|
77
|
+
# rubocop:enable Metrics/MethodLength
|
78
|
+
|
79
|
+
def span(*args, &block)
|
80
|
+
transaction.span(*args, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def submit_transaction(transaction)
|
84
|
+
@pending_transactions << transaction
|
85
|
+
|
86
|
+
if config.debug_transactions
|
87
|
+
debug('Submitted transaction:') { Util.inspect_transaction transaction }
|
88
|
+
end
|
89
|
+
|
90
|
+
return unless should_flush_transactions?
|
91
|
+
flush_transactions
|
92
|
+
end
|
93
|
+
|
94
|
+
def should_flush_transactions?
|
95
|
+
return true unless (interval = config.transaction_send_interval)
|
96
|
+
Time.now.utc - @last_sent_transactions >= interval
|
97
|
+
end
|
98
|
+
|
99
|
+
def flush_transactions
|
100
|
+
return if @pending_transactions.empty?
|
101
|
+
|
102
|
+
debug 'Flushing transactions'
|
103
|
+
|
104
|
+
@agent.enqueue_transactions @pending_transactions
|
105
|
+
|
106
|
+
@last_sent_transactions = Time.now.utc
|
107
|
+
@pending_transactions = []
|
108
|
+
|
109
|
+
true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Log
|
6
|
+
PREFIX = '** [ElasticAPM] '.freeze
|
7
|
+
|
8
|
+
def debug(msg, *args, &block)
|
9
|
+
log(:debug, msg, *args, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(msg, *args, &block)
|
13
|
+
log(:info, msg, *args, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def warn(msg, *args, &block)
|
17
|
+
log(:warn, msg, *args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(msg, *args, &block)
|
21
|
+
log(:error, msg, *args, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fatal(msg, *args, &block)
|
25
|
+
log(:fatal, msg, *args, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def log(lvl, msg, *args)
|
29
|
+
formatted_msg = prepend_prefix(format(msg.to_s, *args))
|
30
|
+
|
31
|
+
return config.logger.send(lvl, formatted_msg) unless block_given?
|
32
|
+
|
33
|
+
# TODO: dont evaluate block if level is higher
|
34
|
+
config.logger.send(lvl, "#{formatted_msg}\n#{yield}")
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def prepend_prefix(str)
|
40
|
+
"#{PREFIX}#{str}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_logger?
|
44
|
+
respond_to?(:config) && config.logger
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
class Middleware
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/MethodLength
|
11
|
+
def call(env)
|
12
|
+
begin
|
13
|
+
transaction = ElasticAPM.transaction 'Rack', 'request'
|
14
|
+
resp = @app.call env
|
15
|
+
transaction.submit(resp[0]) if transaction
|
16
|
+
rescue InternalError
|
17
|
+
raise # Don't report ElasticAPM errors
|
18
|
+
rescue ::Exception => e
|
19
|
+
ElasticAPM.report(e, rack_env: env, handled: false)
|
20
|
+
transaction.submit(500) if transaction
|
21
|
+
raise
|
22
|
+
ensure
|
23
|
+
transaction.release if transaction
|
24
|
+
end
|
25
|
+
|
26
|
+
resp
|
27
|
+
end
|
28
|
+
# rubocop:enable Metrics/MethodLength
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM # :nodoc:
|
4
|
+
# @api private
|
5
|
+
module Normalizers
|
6
|
+
# @api privagte
|
7
|
+
class Normalizer
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.register(name)
|
13
|
+
Normalizers.register(name, self)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.register(name, klass)
|
18
|
+
@registered ||= {}
|
19
|
+
@registered[name] = klass
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.build(config)
|
23
|
+
normalizers = @registered.each_with_object({}) do |(name, klass), built|
|
24
|
+
built[name] = klass.new(config)
|
25
|
+
end
|
26
|
+
|
27
|
+
Collection.new(normalizers)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
class Collection
|
32
|
+
# @api private
|
33
|
+
class SkipNormalizer
|
34
|
+
def initialize; end
|
35
|
+
|
36
|
+
def normalize(*_args)
|
37
|
+
:skip
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(normalizers)
|
42
|
+
@normalizers = normalizers
|
43
|
+
@default = SkipNormalizer.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def for(name)
|
47
|
+
@normalizers.fetch(name, @default)
|
48
|
+
end
|
49
|
+
|
50
|
+
def keys
|
51
|
+
@normalizers.keys
|
52
|
+
end
|
53
|
+
|
54
|
+
def normalize(transaction, name, payload)
|
55
|
+
self.for(name).normalize(transaction, name, payload)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
%w[action_controller action_view active_record].each do |lib|
|
61
|
+
require "elastic_apm/normalizers/#{lib}"
|
62
|
+
end
|
63
|
+
end
|