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