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
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Normalizers
|
5
|
+
module ActionController
|
6
|
+
# @api private
|
7
|
+
class ProcessActionNormalizer < Normalizer
|
8
|
+
register 'process_action.action_controller'
|
9
|
+
TYPE = 'app.controller.action'.freeze
|
10
|
+
|
11
|
+
def normalize(transaction, _name, payload)
|
12
|
+
transaction.name = endpoint(payload)
|
13
|
+
[transaction.name, TYPE, nil]
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def endpoint(payload)
|
19
|
+
"#{payload[:controller]}##{payload[:action]}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Normalizers
|
5
|
+
module ActionView
|
6
|
+
# @api private
|
7
|
+
class RenderNormalizer < Normalizer
|
8
|
+
private
|
9
|
+
|
10
|
+
def normalize_render(payload, type)
|
11
|
+
[path_for(payload[:identifier]), type, nil]
|
12
|
+
end
|
13
|
+
|
14
|
+
def path_for(path)
|
15
|
+
return 'Unknown template' unless path
|
16
|
+
return path unless path.start_with?('/')
|
17
|
+
|
18
|
+
view_path(path) || gem_path(path) || 'Absolute path'
|
19
|
+
end
|
20
|
+
|
21
|
+
def view_path(path)
|
22
|
+
root = @config.view_paths.find { |vp| path.start_with?(vp) }
|
23
|
+
return unless root
|
24
|
+
|
25
|
+
strip_root(root, path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def gem_path(path)
|
29
|
+
root = Gem.path.find { |gp| path.start_with? gp }
|
30
|
+
return unless root
|
31
|
+
|
32
|
+
format '$GEM_PATH/%s', strip_root(root, path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def strip_root(root, path)
|
36
|
+
start = root.length + 1
|
37
|
+
path[start, path.length]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
class RenderTemplateNormalizer < RenderNormalizer
|
43
|
+
register 'render_template.action_view'
|
44
|
+
TYPE = 'template.view'.freeze
|
45
|
+
|
46
|
+
def normalize(_transaction, _name, payload)
|
47
|
+
normalize_render(payload, TYPE)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
class RenderPartialNormalizer < RenderNormalizer
|
53
|
+
register 'render_partial.action_view'
|
54
|
+
TYPE = 'template.view.partial'.freeze
|
55
|
+
|
56
|
+
def normalize(_transaction, _name, payload)
|
57
|
+
normalize_render(payload, TYPE)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
class RenderCollectionNormalizer < RenderNormalizer
|
63
|
+
register 'render_collection.action_view'
|
64
|
+
TYPE = 'template.view.collection'.freeze
|
65
|
+
|
66
|
+
def normalize(_transaction, _name, payload)
|
67
|
+
normalize_render(payload, TYPE)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elastic_apm/sql_summarizer'
|
4
|
+
|
5
|
+
module ElasticAPM
|
6
|
+
module Normalizers
|
7
|
+
module ActiveRecord
|
8
|
+
# @api private
|
9
|
+
class SqlNormalizer < Normalizer
|
10
|
+
register 'sql.active_record'
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
super(*args)
|
14
|
+
|
15
|
+
@type = format('db.%s.sql', lookup_adapter || 'unknown').freeze
|
16
|
+
@summarizer = SqlSummarizer.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def normalize(_transaction, _name, payload)
|
20
|
+
return :skip if %w[SCHEMA CACHE].include?(payload[:name])
|
21
|
+
|
22
|
+
name = summarize(payload[:sql]) || payload[:name] || 'SQL'
|
23
|
+
context = Span::Context.new(statement: payload[:sql])
|
24
|
+
[name, @type, context]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def summarize(sql)
|
30
|
+
@summarizer.summarize(sql)
|
31
|
+
end
|
32
|
+
|
33
|
+
def lookup_adapter
|
34
|
+
::ActiveRecord::Base.connection.adapter_name.downcase
|
35
|
+
rescue StandardError
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
config.elastic_apm = ActiveSupport::OrderedOptions.new
|
7
|
+
Config::DEFAULTS.each { |option, value| config.elastic_apm[option] = value }
|
8
|
+
|
9
|
+
initializer 'elastic_apm.initialize' do |app|
|
10
|
+
config = Config.new app.config.elastic_apm do |c|
|
11
|
+
c.app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
file_config = load_config(app)
|
15
|
+
file_config.each do |option, value|
|
16
|
+
config.send(:"#{option}=", value)
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
ElasticAPM.start config
|
21
|
+
Rails.logger.info "#{Log::PREFIX}Running"
|
22
|
+
|
23
|
+
app.middleware.insert 0, Middleware
|
24
|
+
rescue StandardError => e
|
25
|
+
Rails.logger.error "#{Log::PREFIX}Failed to start: #{e.message}"
|
26
|
+
Rails.logger.debug e.backtrace.join("\n")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
config.after_initialize do
|
31
|
+
require 'elastic_apm/injectors/action_dispatch'
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def load_config(app)
|
37
|
+
config_path = app.root.join('config', 'elastic_apm.yml')
|
38
|
+
return {} unless File.exist?(config_path)
|
39
|
+
|
40
|
+
YAML.load_file(config_path) || {}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Serializers
|
6
|
+
# @api private
|
7
|
+
class Serializer
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def micros_to_time(micros)
|
15
|
+
Time.at(ms(micros) / 1_000)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ms(micros)
|
19
|
+
micros.to_f / 1_000
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'elastic_apm/serializers/transactions'
|
26
|
+
require 'elastic_apm/serializers/errors'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Serializers
|
5
|
+
# @api private
|
6
|
+
class Errors < Serializer
|
7
|
+
def build(error)
|
8
|
+
base = {
|
9
|
+
id: SecureRandom.uuid,
|
10
|
+
culprit: error.culprit,
|
11
|
+
timestamp: micros_to_time(error.timestamp).utc.iso8601
|
12
|
+
}
|
13
|
+
|
14
|
+
if (exception = error.exception)
|
15
|
+
base[:exception] = build_exception exception
|
16
|
+
end
|
17
|
+
|
18
|
+
base
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_all(errors)
|
22
|
+
{ errors: Array(errors).map(&method(:build)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def build_exception(exception)
|
28
|
+
{
|
29
|
+
message: exception.message,
|
30
|
+
type: exception.type,
|
31
|
+
module: exception.module,
|
32
|
+
code: exception.code,
|
33
|
+
attributes: exception.attributes,
|
34
|
+
stacktrace: exception.stacktrace.to_a,
|
35
|
+
unhandled: !exception.handled
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Serializers
|
5
|
+
# @api private
|
6
|
+
class Transactions < Serializer
|
7
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
8
|
+
def build(transaction)
|
9
|
+
{
|
10
|
+
id: SecureRandom.uuid,
|
11
|
+
name: transaction.name,
|
12
|
+
type: transaction.type,
|
13
|
+
result: transaction.result.to_s,
|
14
|
+
duration: ms(transaction.duration),
|
15
|
+
timestamp: micros_to_time(transaction.timestamp).utc.iso8601,
|
16
|
+
spans: transaction.spans.map do |span|
|
17
|
+
{
|
18
|
+
id: span.id,
|
19
|
+
parent: span.parent && span.parent.id,
|
20
|
+
name: span.name,
|
21
|
+
type: span.type,
|
22
|
+
start: ms(span.relative_start),
|
23
|
+
duration: ms(span.duration),
|
24
|
+
context: span.context && { db: span.context.to_h }
|
25
|
+
}
|
26
|
+
end
|
27
|
+
}
|
28
|
+
end
|
29
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
30
|
+
|
31
|
+
def build_all(transactions)
|
32
|
+
{ transactions: Array(transactions).map(&method(:build)) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
class ServiceInfo
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
# rubocop:disable Metrics/MethodLength
|
13
|
+
def build
|
14
|
+
base = {
|
15
|
+
name: config.app_name,
|
16
|
+
environment: config.environment,
|
17
|
+
agent: {
|
18
|
+
name: 'ruby',
|
19
|
+
version: VERSION
|
20
|
+
},
|
21
|
+
framework: nil,
|
22
|
+
argv: ARGV,
|
23
|
+
language: {
|
24
|
+
name: 'ruby',
|
25
|
+
version: RUBY_VERSION
|
26
|
+
},
|
27
|
+
pid: $PID,
|
28
|
+
process_title: $PROGRAM_NAME,
|
29
|
+
runtime: runtime,
|
30
|
+
version: git_sha
|
31
|
+
}
|
32
|
+
|
33
|
+
if config.framework_name
|
34
|
+
base[:framework] = {
|
35
|
+
name: config.framework_name,
|
36
|
+
version: config.framework_version
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
base
|
41
|
+
end
|
42
|
+
# rubocop:enable Metrics/MethodLength
|
43
|
+
|
44
|
+
def self.build(config)
|
45
|
+
new(config).build
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def git_sha
|
51
|
+
sha = `git rev-parse --verify HEAD 2>&1`.chomp
|
52
|
+
return sha if $?.success? # rubocop:disable Style/SpecialGlobalVars
|
53
|
+
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def runtime
|
58
|
+
case RUBY_ENGINE
|
59
|
+
when 'ruby'
|
60
|
+
{ name: RUBY_ENGINE, version: RUBY_VERSION }
|
61
|
+
when 'jruby'
|
62
|
+
{ name: RUBY_ENGINE, version: ENV['JRUBY_VERSION'] }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elastic_apm/span/context'
|
4
|
+
|
5
|
+
module ElasticAPM
|
6
|
+
# @api private
|
7
|
+
class Span
|
8
|
+
DEFAULT_KIND = 'custom'.freeze
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/ParameterLists
|
11
|
+
def initialize(
|
12
|
+
transaction,
|
13
|
+
id,
|
14
|
+
name,
|
15
|
+
type = DEFAULT_KIND,
|
16
|
+
parent: nil,
|
17
|
+
context: nil
|
18
|
+
)
|
19
|
+
@transaction = transaction
|
20
|
+
@id = id
|
21
|
+
@name = name
|
22
|
+
@type = type
|
23
|
+
@parent = parent
|
24
|
+
@context = context
|
25
|
+
end
|
26
|
+
# rubocop:enable Metrics/ParameterLists
|
27
|
+
|
28
|
+
attr_accessor :name, :context, :type
|
29
|
+
attr_reader :id, :duration, :parent, :relative_start
|
30
|
+
|
31
|
+
def start
|
32
|
+
@relative_start = Util.micros - @transaction.timestamp
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def done
|
38
|
+
@duration = Util.micros - @transaction.timestamp - relative_start
|
39
|
+
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def done?
|
44
|
+
!!duration
|
45
|
+
end
|
46
|
+
|
47
|
+
def running?
|
48
|
+
relative_start && !done?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
class Span
|
5
|
+
# @api private
|
6
|
+
class Context
|
7
|
+
def initialize(**args)
|
8
|
+
args.each do |key, val|
|
9
|
+
send(:"#{key}=", val)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :instance, :statement, :type, :user
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{ instance: instance, statement: statement, type: type, user: user }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module SpanHelpers
|
6
|
+
# @api private
|
7
|
+
module ClassMethods
|
8
|
+
def span_class_method(method, name, type)
|
9
|
+
__span_method_on(singleton_class, method, name, type)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def __span_method_on(klass, method, name, type)
|
15
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
16
|
+
alias :"__without_apm_#{method}" :"#{method}"
|
17
|
+
|
18
|
+
def #{method}(*args, &block)
|
19
|
+
unless ElasticAPM.current_transaction
|
20
|
+
return __without_apm_#{method}(*args, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
ElasticAPM.span "#{name}", "#{type}" do
|
24
|
+
__without_apm_#{method}(*args, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RUBY
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.included(kls)
|
32
|
+
kls.class_eval do
|
33
|
+
extend ClassMethods
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|