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.

Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +47 -0
  5. data/.travis.yml +5 -0
  6. data/CODE_OF_CONDUCT.md +47 -0
  7. data/Gemfile +38 -0
  8. data/LICENSE +201 -0
  9. data/README.md +55 -0
  10. data/Rakefile +12 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/bin/with_framework +7 -0
  14. data/elastic-apm.gemspec +22 -0
  15. data/lib/elastic-apm.rb +4 -0
  16. data/lib/elastic_apm.rb +92 -0
  17. data/lib/elastic_apm/agent.rb +164 -0
  18. data/lib/elastic_apm/config.rb +124 -0
  19. data/lib/elastic_apm/error.rb +21 -0
  20. data/lib/elastic_apm/error/context.rb +119 -0
  21. data/lib/elastic_apm/error/exception.rb +37 -0
  22. data/lib/elastic_apm/error/log.rb +24 -0
  23. data/lib/elastic_apm/error_builder.rb +40 -0
  24. data/lib/elastic_apm/http.rb +103 -0
  25. data/lib/elastic_apm/injectors.rb +71 -0
  26. data/lib/elastic_apm/injectors/action_dispatch.rb +26 -0
  27. data/lib/elastic_apm/injectors/json.rb +22 -0
  28. data/lib/elastic_apm/injectors/net_http.rb +50 -0
  29. data/lib/elastic_apm/injectors/redis.rb +33 -0
  30. data/lib/elastic_apm/injectors/sequel.rb +45 -0
  31. data/lib/elastic_apm/injectors/sinatra.rb +41 -0
  32. data/lib/elastic_apm/injectors/tilt.rb +27 -0
  33. data/lib/elastic_apm/instrumenter.rb +112 -0
  34. data/lib/elastic_apm/internal_error.rb +5 -0
  35. data/lib/elastic_apm/log.rb +47 -0
  36. data/lib/elastic_apm/middleware.rb +30 -0
  37. data/lib/elastic_apm/normalizers.rb +63 -0
  38. data/lib/elastic_apm/normalizers/action_controller.rb +24 -0
  39. data/lib/elastic_apm/normalizers/action_view.rb +72 -0
  40. data/lib/elastic_apm/normalizers/active_record.rb +41 -0
  41. data/lib/elastic_apm/railtie.rb +43 -0
  42. data/lib/elastic_apm/serializers.rb +26 -0
  43. data/lib/elastic_apm/serializers/errors.rb +40 -0
  44. data/lib/elastic_apm/serializers/transactions.rb +36 -0
  45. data/lib/elastic_apm/service_info.rb +66 -0
  46. data/lib/elastic_apm/span.rb +51 -0
  47. data/lib/elastic_apm/span/context.rb +20 -0
  48. data/lib/elastic_apm/span_helpers.rb +37 -0
  49. data/lib/elastic_apm/sql_summarizer.rb +26 -0
  50. data/lib/elastic_apm/stacktrace.rb +84 -0
  51. data/lib/elastic_apm/stacktrace/frame.rb +62 -0
  52. data/lib/elastic_apm/subscriber.rb +72 -0
  53. data/lib/elastic_apm/system_info.rb +30 -0
  54. data/lib/elastic_apm/transaction.rb +92 -0
  55. data/lib/elastic_apm/util.rb +20 -0
  56. data/lib/elastic_apm/util/inspector.rb +61 -0
  57. data/lib/elastic_apm/version.rb +5 -0
  58. data/lib/elastic_apm/worker.rb +48 -0
  59. data/vendor/.gitkeep +0 -0
  60. 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