influx_reporter 1.0.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.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +51 -0
  5. data/.travis.yml +42 -0
  6. data/.yardopts +3 -0
  7. data/Dockerfile +9 -0
  8. data/Gemfile +7 -0
  9. data/HISTORY.md +1 -0
  10. data/LICENSE +14 -0
  11. data/README.md +189 -0
  12. data/Rakefile +27 -0
  13. data/gemfiles/Gemfile.base +29 -0
  14. data/gemfiles/Gemfile.rails-3.2.x +4 -0
  15. data/gemfiles/Gemfile.rails-4.0.x +4 -0
  16. data/gemfiles/Gemfile.rails-4.1.x +4 -0
  17. data/gemfiles/Gemfile.rails-4.2.x +5 -0
  18. data/gemfiles/Gemfile.rails-5.0.x +5 -0
  19. data/gemfiles/Gemfile.rails-HEAD +7 -0
  20. data/influx_reporter.gemspec +26 -0
  21. data/lib/influx_reporter.rb +142 -0
  22. data/lib/influx_reporter/client.rb +306 -0
  23. data/lib/influx_reporter/configuration.rb +88 -0
  24. data/lib/influx_reporter/data_builders.rb +18 -0
  25. data/lib/influx_reporter/data_builders/error.rb +52 -0
  26. data/lib/influx_reporter/data_builders/transactions.rb +120 -0
  27. data/lib/influx_reporter/error.rb +7 -0
  28. data/lib/influx_reporter/error_message.rb +85 -0
  29. data/lib/influx_reporter/error_message/exception.rb +14 -0
  30. data/lib/influx_reporter/error_message/http.rb +73 -0
  31. data/lib/influx_reporter/error_message/stacktrace.rb +76 -0
  32. data/lib/influx_reporter/error_message/user.rb +25 -0
  33. data/lib/influx_reporter/filter.rb +66 -0
  34. data/lib/influx_reporter/http_client.rb +140 -0
  35. data/lib/influx_reporter/influx_db_client.rb +74 -0
  36. data/lib/influx_reporter/injections.rb +89 -0
  37. data/lib/influx_reporter/injections/json.rb +21 -0
  38. data/lib/influx_reporter/injections/net_http.rb +50 -0
  39. data/lib/influx_reporter/injections/redis.rb +25 -0
  40. data/lib/influx_reporter/injections/sequel.rb +37 -0
  41. data/lib/influx_reporter/injections/sinatra.rb +59 -0
  42. data/lib/influx_reporter/integration/delayed_job.rb +30 -0
  43. data/lib/influx_reporter/integration/rails/inject_exceptions_catcher.rb +25 -0
  44. data/lib/influx_reporter/integration/railtie.rb +56 -0
  45. data/lib/influx_reporter/integration/resque.rb +18 -0
  46. data/lib/influx_reporter/integration/sidekiq.rb +130 -0
  47. data/lib/influx_reporter/line_cache.rb +21 -0
  48. data/lib/influx_reporter/logging.rb +39 -0
  49. data/lib/influx_reporter/middleware.rb +63 -0
  50. data/lib/influx_reporter/normalizers.rb +65 -0
  51. data/lib/influx_reporter/normalizers/action_controller.rb +34 -0
  52. data/lib/influx_reporter/normalizers/action_view.rb +72 -0
  53. data/lib/influx_reporter/normalizers/active_record.rb +45 -0
  54. data/lib/influx_reporter/sql_summarizer.rb +31 -0
  55. data/lib/influx_reporter/subscriber.rb +80 -0
  56. data/lib/influx_reporter/tasks.rb +28 -0
  57. data/lib/influx_reporter/trace.rb +48 -0
  58. data/lib/influx_reporter/trace_helpers.rb +31 -0
  59. data/lib/influx_reporter/transaction.rb +114 -0
  60. data/lib/influx_reporter/util.rb +18 -0
  61. data/lib/influx_reporter/util/constantize.rb +56 -0
  62. data/lib/influx_reporter/util/inspector.rb +72 -0
  63. data/lib/influx_reporter/util/timestamp.rb +11 -0
  64. data/lib/influx_reporter/version.rb +5 -0
  65. data/lib/influx_reporter/worker.rb +56 -0
  66. data/spec/influx_reporter/client_spec.rb +264 -0
  67. data/spec/influx_reporter/configuration_spec.rb +42 -0
  68. data/spec/influx_reporter/data_builders/error_spec.rb +40 -0
  69. data/spec/influx_reporter/data_builders/transactions_spec.rb +48 -0
  70. data/spec/influx_reporter/error_message/exception_spec.rb +22 -0
  71. data/spec/influx_reporter/error_message/http_spec.rb +55 -0
  72. data/spec/influx_reporter/error_message/stacktrace_spec.rb +56 -0
  73. data/spec/influx_reporter/error_message/user_spec.rb +26 -0
  74. data/spec/influx_reporter/error_message_spec.rb +102 -0
  75. data/spec/influx_reporter/filter_spec.rb +33 -0
  76. data/spec/influx_reporter/injections/net_http_spec.rb +35 -0
  77. data/spec/influx_reporter/injections/sequel_spec.rb +33 -0
  78. data/spec/influx_reporter/injections/sinatra_spec.rb +13 -0
  79. data/spec/influx_reporter/injections_spec.rb +50 -0
  80. data/spec/influx_reporter/integration/delayed_job_spec.rb +37 -0
  81. data/spec/influx_reporter/integration/json_spec.rb +41 -0
  82. data/spec/influx_reporter/integration/rails_spec.rb +92 -0
  83. data/spec/influx_reporter/integration/redis_spec.rb +20 -0
  84. data/spec/influx_reporter/integration/resque_spec.rb +42 -0
  85. data/spec/influx_reporter/integration/sidekiq_spec.rb +40 -0
  86. data/spec/influx_reporter/integration/sinatra_spec.rb +99 -0
  87. data/spec/influx_reporter/line_cache_spec.rb +38 -0
  88. data/spec/influx_reporter/logging_spec.rb +49 -0
  89. data/spec/influx_reporter/middleware_spec.rb +32 -0
  90. data/spec/influx_reporter/normalizers/action_controller_spec.rb +37 -0
  91. data/spec/influx_reporter/normalizers/action_view_spec.rb +78 -0
  92. data/spec/influx_reporter/normalizers/active_record_spec.rb +70 -0
  93. data/spec/influx_reporter/normalizers_spec.rb +16 -0
  94. data/spec/influx_reporter/sql_summarizer_spec.rb +35 -0
  95. data/spec/influx_reporter/subscriber_spec.rb +83 -0
  96. data/spec/influx_reporter/trace_spec.rb +43 -0
  97. data/spec/influx_reporter/transaction_spec.rb +98 -0
  98. data/spec/influx_reporter/util/inspector_spec.rb +41 -0
  99. data/spec/influx_reporter/util_spec.rb +20 -0
  100. data/spec/influx_reporter/worker_spec.rb +54 -0
  101. data/spec/influx_reporter_spec.rb +50 -0
  102. data/spec/spec_helper.rb +86 -0
  103. metadata +188 -0
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module InfluxReporter
6
+ class Configuration
7
+ DEFAULTS = {
8
+ influx_db: {},
9
+ database: 'endpoints',
10
+ logger: Logger.new(nil),
11
+ context_lines: 3,
12
+ enabled_environments: %w[production],
13
+ excluded_exceptions: [],
14
+ filter_parameters: [/(authorization|password|passwd|secret)/i],
15
+ timeout: 100,
16
+ open_timeout: 100,
17
+ backoff_multiplier: 2,
18
+ current_user_method: :current_user,
19
+ environment: ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'default',
20
+ transaction_post_interval: 60,
21
+ worker_quit_timeout: 5,
22
+
23
+ payload_tags: [],
24
+ payload_values: [],
25
+
26
+ disable_performance: false,
27
+ disable_errors: false,
28
+
29
+ debug_traces: false,
30
+
31
+ view_paths: [],
32
+
33
+ tags: {},
34
+
35
+ # for tests
36
+ disable_worker: false
37
+ }.freeze
38
+
39
+ attr_accessor :influx_db
40
+ attr_accessor :database
41
+ attr_accessor :logger
42
+ attr_accessor :context_lines
43
+ attr_accessor :enabled_environments
44
+ attr_accessor :excluded_exceptions
45
+ attr_accessor :filter_parameters
46
+ attr_accessor :timeout
47
+ attr_accessor :open_timeout
48
+ attr_accessor :backoff_multiplier
49
+ attr_accessor :use_ssl
50
+ attr_accessor :current_user_method
51
+ attr_accessor :environment
52
+ attr_accessor :transaction_post_interval
53
+ attr_accessor :worker_quit_timeout
54
+
55
+ attr_accessor :payload_tags
56
+ attr_accessor :payload_values
57
+
58
+ attr_accessor :disable_performance
59
+ attr_accessor :disable_errors
60
+
61
+ attr_accessor :debug_traces
62
+
63
+ attr_accessor :disable_worker
64
+
65
+ attr_accessor :view_paths
66
+
67
+ attr_accessor :tags
68
+
69
+ def initialize(opts = {})
70
+ DEFAULTS.merge(opts).each do |k, v|
71
+ send("#{k}=", v)
72
+ end
73
+
74
+ yield self if block_given?
75
+ end
76
+
77
+ def validate!
78
+ %w[database influx_db tags].each do |key|
79
+ raise Error, "InfluxReporter Configuration missing `#{key}'" unless send(key)
80
+ end
81
+
82
+ true
83
+ rescue Error => e
84
+ logger.error e.message
85
+ false
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ # @api private
5
+ module DataBuilders
6
+ class DataBuilder
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ attr_reader :config
12
+ end
13
+
14
+ %w[transactions error].each do |f|
15
+ require "influx_reporter/data_builders/#{f}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'influx_reporter/filter'
4
+
5
+ module InfluxReporter
6
+ module DataBuilders
7
+ class Error < DataBuilder
8
+ # @param error_message [InfluxReporter::ErrorMessage]
9
+ def build(error_message)
10
+ {
11
+ series: 'errors',
12
+ values: build_values(error_message),
13
+ tags: build_tags(error_message),
14
+ timestamp: error_message.timestamp
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ # @param error_message [InfluxReporter::ErrorMessage]
21
+ def build_tags(error_message)
22
+ tags = {
23
+ level: error_message.level,
24
+ excpetion: error_message.exception&.type,
25
+ module: error_message.exception&.module,
26
+ user_id: error_message.user&.id,
27
+ method: error_message.http&.method
28
+ }
29
+ tags = error_message.extra[:tags].merge(tags) if error_message.extra && error_message.extra[:tags].is_a?(Hash)
30
+ tags = error_message.config.tags.merge(tags)
31
+ tags.reject { |_, value| value.nil? || value == '' }
32
+ end
33
+
34
+ # @param error_message [InfluxReporter::ErrorMessage]
35
+ def build_values(error_message)
36
+ values = {
37
+ message: error_message.message,
38
+ culprit: error_message.culprit
39
+ }
40
+ if error_message.http
41
+ values[:url] = error_message.http.url
42
+ values[:user_agent] = error_message.http.user_agent
43
+ values[:user_agent] = error_message.http.user_agent
44
+ values[:uuid] = error_message.http.uuid
45
+ end
46
+ values = error_message.extra[:values].merge(values) if error_message.extra && error_message.extra[:values].is_a?(Hash)
47
+
48
+ values.reject { |_, value| value.nil? }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+ require 'influx_reporter/transaction'
3
+
4
+ module InfluxReporter
5
+ module DataBuilders
6
+ class Transactions < DataBuilder
7
+ def build(transactions)
8
+ data_points = []
9
+ transactions.each do |transaction|
10
+ data_points << build_transaction(transaction)
11
+ transaction.traces.each do |trace|
12
+ next if trace.kind == InfluxReporter::Transaction::ROOT_TRACE_NAME
13
+ data_points << build_trace(trace)
14
+ end
15
+ end
16
+ data_points
17
+ end
18
+
19
+ private
20
+
21
+ # @param transaction [InfluxReporter::Transaction]
22
+ def build_transaction(transaction)
23
+ {
24
+ series: series(transaction),
25
+ tags: tags(transaction),
26
+ values: values(transaction),
27
+ timestamp: transaction.timestamp
28
+ }
29
+ end
30
+
31
+ # @param transaction [InfluxReporter::Transaction, InfluxReporter::Trace]
32
+ # @return [String]
33
+ def series(transaction)
34
+ transaction.kind.split('.').first(2).join('.')
35
+ end
36
+
37
+ # @param transaction [InfluxReporter::Transaction]
38
+ # @return [Hash]
39
+ def tags(transaction)
40
+ tags = {
41
+ endpoint: transaction.endpoint,
42
+ result: transaction.result.to_i,
43
+ kind: transaction.kind.split('.')[2..-1].join('.')
44
+ }
45
+ tags = (transaction.root_trace.extra[:tags] || {}).merge(tags)
46
+ tags = transaction.config.tags.merge(tags) if transaction.config
47
+ clean tags
48
+ end
49
+
50
+ # @param transaction [InfluxReporter::Transaction]
51
+ # @return [Hash]
52
+ def values(transaction)
53
+ values = {
54
+ duration: ms(transaction.duration)
55
+ }.merge(values_from_traces(transaction))
56
+ values = (transaction.root_trace.extra[:values] || {}).merge(values)
57
+ clean values
58
+ end
59
+
60
+ # @param transaction [InfluxReporter::Transaction]
61
+ # @return [Hash]
62
+ def values_from_traces(transaction)
63
+ values = {}
64
+ transaction.traces.each do |trace|
65
+ next if trace.signature == InfluxReporter::Transaction::ROOT_TRACE_NAME || trace.transaction.root_trace == trace
66
+ values["#{trace.kind}.count"] ||= 0
67
+ values["#{trace.kind}.duration"] ||= 0
68
+ values["#{trace.kind}.count"] += 1
69
+ values["#{trace.kind}.duration"] += ms(trace.duration)
70
+ end
71
+ values
72
+ end
73
+
74
+ # @param trace [InfluxReporter::Trace]
75
+ # @return [Hash]
76
+ def build_trace(trace)
77
+ {
78
+ series: "trace.#{trace.kind.split('.').first}",
79
+ tags: trace_tags(trace),
80
+ values: trace_values(trace),
81
+ timestamp: trace.timestamp
82
+ }
83
+ end
84
+
85
+ # @param trace [InfluxReporter::Trace]
86
+ def trace_tags(trace)
87
+ tags = {
88
+ endpoint: trace.transaction.endpoint,
89
+ signature: trace.signature,
90
+ kind: trace.kind.split('.')[1..-1].join('.')
91
+ }
92
+ tags = (trace.transaction.root_trace.extra[:tags] || {}).merge(trace.extra[:tags] || {}).merge(tags)
93
+ tags = trace.transaction.config.tags.merge(tags) if trace.transaction.config
94
+ clean tags
95
+ end
96
+
97
+ # @param trace [InfluxReporter::Trace]
98
+
99
+ def trace_values(trace)
100
+ values = {
101
+ duration: ms(trace.duration),
102
+ start_time: ms(trace.relative_start)
103
+ }
104
+ values = (trace.extra[:values] || {}).merge(values)
105
+ values[:uuid] ||= trace.transaction.root_trace.extra[:values][:uuid] if trace.transaction.root_trace.extra[:values]
106
+ clean values
107
+ end
108
+
109
+ # @param hash [Hash]
110
+ # @return [Hash]
111
+ def clean(hash)
112
+ hash.reject { |_, value| value.nil? || value == '' }
113
+ end
114
+
115
+ def ms(nanos)
116
+ nanos.to_f / 1_000_000
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ # @api private
5
+ class Error < StandardError
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'influx_reporter/line_cache'
4
+ require 'influx_reporter/error_message/exception'
5
+ require 'influx_reporter/error_message/stacktrace'
6
+ require 'influx_reporter/error_message/http'
7
+ require 'influx_reporter/error_message/user'
8
+ require 'influx_reporter/util/timestamp'
9
+
10
+ module InfluxReporter
11
+ class ErrorMessage
12
+ extend Logging
13
+
14
+ DEFAULTS = {
15
+ level: :error,
16
+ logger: 'root'
17
+ }.freeze
18
+
19
+ def initialize(config, message, attrs = {})
20
+ @config = config
21
+
22
+ @message = message
23
+ @timestamp = Util.nanos
24
+
25
+ DEFAULTS.merge(attrs).each do |k, v|
26
+ send(:"#{k}=", v)
27
+ end
28
+ @filter = Filter.new config
29
+
30
+ yield self if block_given?
31
+ end
32
+
33
+ attr_reader :config
34
+ attr_accessor :message
35
+ attr_reader :timestamp
36
+ attr_reader :filter
37
+ attr_accessor :level
38
+ attr_accessor :logger
39
+ attr_accessor :culprit
40
+ attr_accessor :machine
41
+ attr_accessor :extra
42
+ attr_accessor :param_message
43
+ attr_accessor :exception
44
+ attr_accessor :stacktrace
45
+ attr_accessor :http
46
+ attr_accessor :user
47
+
48
+ def self.from_exception(config, exception, opts = {})
49
+ message = "#{exception.class}: #{exception.message}"
50
+
51
+ if config.excluded_exceptions.include? exception.class.to_s
52
+ info "Skipping excluded exception #{exception.class}"
53
+ return nil
54
+ end
55
+
56
+ error_message = new(config, message) do |msg|
57
+ msg.level = :error
58
+ msg.exception = Exception.from(exception)
59
+ msg.stacktrace = Stacktrace.from(config, exception)
60
+ end
61
+
62
+ if frames = error_message.stacktrace&.frames
63
+ if first_frame = frames.last
64
+ error_message.culprit = "#{first_frame.filename}:#{first_frame.lineno}:in `#{first_frame.function}'"
65
+ end
66
+ end
67
+
68
+ if env = opts[:rack_env]
69
+ error_message.http = HTTP.from_rack_env env, filter: error_message.filter
70
+ error_message.user = User.from_rack_env config, env
71
+ end
72
+
73
+ if extra = opts[:extra]
74
+ error_message.extra = extra
75
+ end
76
+
77
+ error_message
78
+ end
79
+
80
+ def add_extra(info)
81
+ @extra ||= {}
82
+ @extra.merge! info
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ class ErrorMessage
5
+ class Exception < Struct.new(:type, :value, :module)
6
+ SPLIT = '::'
7
+
8
+ def self.from(exception)
9
+ new exception.class.to_s, exception.message,
10
+ exception.class.to_s.split(SPLIT)[0...-1].join(SPLIT)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ class ErrorMessage
5
+ class HTTP < Struct.new(:url, :method, :data, :query_string, :cookies,
6
+ :headers, :remote_host, :http_host, :user_agent,
7
+ :secure, :env, :uuid)
8
+
9
+ HTTP_ENV_KEY = /^HTTP_/
10
+ UNDERSCORE = '_'
11
+
12
+ DASH = '-'
13
+ QUESTION = '?'
14
+
15
+ def self.from_rack_env(env, opts = {})
16
+ req = if defined?(ActionDispatch::Request) && env.is_a?(ActionDispatch::Request)
17
+ env
18
+ else
19
+ Rack::Request.new env
20
+ end
21
+
22
+ http = new(
23
+ req.url.split(QUESTION).first, # url
24
+ req.request_method, # method
25
+ nil, # data
26
+ req.query_string, # query string
27
+ env['HTTP_COOKIE'], # cookies
28
+ {}, # headers
29
+ req.ip, # remote host
30
+ req.host_with_port, # http host
31
+ req.user_agent, # user agent
32
+ req.scheme == 'https', # secure
33
+ {}, # env
34
+ req.respond_to?(:uuid) ? req.uuid : nil
35
+ )
36
+
37
+ # In Rails < 5 ActionDispatch::Request inherits from Hash
38
+ headers = env.respond_to?(:headers) ? env.headers : env
39
+
40
+ headers.each do |k, v|
41
+ next unless k.upcase == k # lower case stuff isn't relevant
42
+
43
+ if k.match(HTTP_ENV_KEY)
44
+ header = k.gsub(HTTP_ENV_KEY, '')
45
+ .split(UNDERSCORE).map(&:capitalize).join(DASH)
46
+ http.headers[header] = v.to_s
47
+ else
48
+ http.env[k] = v.to_s
49
+ end
50
+ end
51
+
52
+ if req.form_data?
53
+ http.data = req.POST
54
+ elsif req.body
55
+ http.data = req.body.read
56
+ req.body.rewind
57
+ end
58
+
59
+ if filter = opts[:filter]
60
+ http.apply_filter filter
61
+ end
62
+
63
+ http
64
+ end
65
+
66
+ def apply_filter(filter)
67
+ self.data = filter.apply data
68
+ self.query_string = filter.apply query_string
69
+ self.cookies = filter.apply cookies
70
+ end
71
+ end
72
+ end
73
+ end