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.

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