atatus 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 (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +57 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-THIRD-PARTY +205 -0
  7. data/README.md +13 -0
  8. data/Rakefile +19 -0
  9. data/atatus.gemspec +36 -0
  10. data/atatus.yml +2 -0
  11. data/bench/.gitignore +2 -0
  12. data/bench/app.rb +53 -0
  13. data/bench/benchmark.rb +36 -0
  14. data/bench/report.rb +55 -0
  15. data/bench/rubyprof.rb +39 -0
  16. data/bench/stackprof.rb +23 -0
  17. data/bin/build_docs +5 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/bin/with_framework +7 -0
  21. data/lib/atatus.rb +325 -0
  22. data/lib/atatus/agent.rb +260 -0
  23. data/lib/atatus/central_config.rb +141 -0
  24. data/lib/atatus/central_config/cache_control.rb +34 -0
  25. data/lib/atatus/collector/base.rb +329 -0
  26. data/lib/atatus/collector/builder.rb +317 -0
  27. data/lib/atatus/collector/transport.rb +72 -0
  28. data/lib/atatus/config.rb +248 -0
  29. data/lib/atatus/config/bytes.rb +25 -0
  30. data/lib/atatus/config/duration.rb +23 -0
  31. data/lib/atatus/config/options.rb +134 -0
  32. data/lib/atatus/config/regexp_list.rb +13 -0
  33. data/lib/atatus/context.rb +33 -0
  34. data/lib/atatus/context/request.rb +11 -0
  35. data/lib/atatus/context/request/socket.rb +19 -0
  36. data/lib/atatus/context/request/url.rb +42 -0
  37. data/lib/atatus/context/response.rb +22 -0
  38. data/lib/atatus/context/user.rb +42 -0
  39. data/lib/atatus/context_builder.rb +97 -0
  40. data/lib/atatus/deprecations.rb +22 -0
  41. data/lib/atatus/error.rb +22 -0
  42. data/lib/atatus/error/exception.rb +46 -0
  43. data/lib/atatus/error/log.rb +24 -0
  44. data/lib/atatus/error_builder.rb +76 -0
  45. data/lib/atatus/instrumenter.rb +224 -0
  46. data/lib/atatus/internal_error.rb +6 -0
  47. data/lib/atatus/logging.rb +55 -0
  48. data/lib/atatus/metadata.rb +19 -0
  49. data/lib/atatus/metadata/process_info.rb +18 -0
  50. data/lib/atatus/metadata/service_info.rb +61 -0
  51. data/lib/atatus/metadata/system_info.rb +35 -0
  52. data/lib/atatus/metadata/system_info/container_info.rb +121 -0
  53. data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
  54. data/lib/atatus/metadata/system_info/os_info.rb +31 -0
  55. data/lib/atatus/metrics.rb +98 -0
  56. data/lib/atatus/metrics/cpu_mem.rb +240 -0
  57. data/lib/atatus/metrics/vm.rb +60 -0
  58. data/lib/atatus/metricset.rb +19 -0
  59. data/lib/atatus/middleware.rb +76 -0
  60. data/lib/atatus/naively_hashable.rb +21 -0
  61. data/lib/atatus/normalizers.rb +68 -0
  62. data/lib/atatus/normalizers/action_controller.rb +27 -0
  63. data/lib/atatus/normalizers/action_mailer.rb +26 -0
  64. data/lib/atatus/normalizers/action_view.rb +77 -0
  65. data/lib/atatus/normalizers/active_record.rb +45 -0
  66. data/lib/atatus/opentracing.rb +346 -0
  67. data/lib/atatus/rails.rb +61 -0
  68. data/lib/atatus/railtie.rb +30 -0
  69. data/lib/atatus/span.rb +125 -0
  70. data/lib/atatus/span/context.rb +40 -0
  71. data/lib/atatus/span_helpers.rb +44 -0
  72. data/lib/atatus/spies.rb +86 -0
  73. data/lib/atatus/spies/action_dispatch.rb +28 -0
  74. data/lib/atatus/spies/delayed_job.rb +68 -0
  75. data/lib/atatus/spies/elasticsearch.rb +36 -0
  76. data/lib/atatus/spies/faraday.rb +70 -0
  77. data/lib/atatus/spies/http.rb +44 -0
  78. data/lib/atatus/spies/json.rb +22 -0
  79. data/lib/atatus/spies/mongo.rb +87 -0
  80. data/lib/atatus/spies/net_http.rb +70 -0
  81. data/lib/atatus/spies/rake.rb +45 -0
  82. data/lib/atatus/spies/redis.rb +27 -0
  83. data/lib/atatus/spies/sequel.rb +47 -0
  84. data/lib/atatus/spies/sidekiq.rb +89 -0
  85. data/lib/atatus/spies/sinatra.rb +41 -0
  86. data/lib/atatus/spies/tilt.rb +27 -0
  87. data/lib/atatus/sql_summarizer.rb +35 -0
  88. data/lib/atatus/stacktrace.rb +16 -0
  89. data/lib/atatus/stacktrace/frame.rb +52 -0
  90. data/lib/atatus/stacktrace_builder.rb +104 -0
  91. data/lib/atatus/subscriber.rb +77 -0
  92. data/lib/atatus/trace_context.rb +85 -0
  93. data/lib/atatus/transaction.rb +100 -0
  94. data/lib/atatus/transport/base.rb +174 -0
  95. data/lib/atatus/transport/connection.rb +156 -0
  96. data/lib/atatus/transport/connection/http.rb +116 -0
  97. data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
  98. data/lib/atatus/transport/filters.rb +43 -0
  99. data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
  100. data/lib/atatus/transport/serializers.rb +93 -0
  101. data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
  102. data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
  103. data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
  104. data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
  105. data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
  106. data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
  107. data/lib/atatus/transport/worker.rb +73 -0
  108. data/lib/atatus/util.rb +42 -0
  109. data/lib/atatus/util/inflector.rb +93 -0
  110. data/lib/atatus/util/lru_cache.rb +48 -0
  111. data/lib/atatus/util/prefixed_logger.rb +18 -0
  112. data/lib/atatus/util/throttle.rb +35 -0
  113. data/lib/atatus/version.rb +5 -0
  114. data/vendor/.gitkeep +0 -0
  115. metadata +190 -0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module Deprecations
6
+ def deprecate(name, replacement = nil)
7
+ alias_name = "#{name.to_s.chomp('=')}__deprecated_"
8
+ alias_name += '=' if name.to_s.end_with?('=')
9
+
10
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
11
+ alias :"#{alias_name}" :"#{name}"
12
+
13
+ def #{name}(*args, &block)
14
+ warn "[Atatus] [DEPRECATED] `#{name}' is being removed. " \
15
+ "#{replacement && "See `#{replacement}'."}" \
16
+ "\nCalled from \#{caller.first}"
17
+ send("#{alias_name}", *args, &block)
18
+ end
19
+ RUBY
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/stacktrace'
4
+ require 'atatus/context'
5
+ require 'atatus/error/exception'
6
+ require 'atatus/error/log'
7
+
8
+ module Atatus
9
+ # @api private
10
+ class Error
11
+ def initialize(culprit: nil, context: nil)
12
+ @id = SecureRandom.hex(16)
13
+ @culprit = culprit
14
+ @timestamp = Util.micros
15
+ @context = context
16
+ end
17
+
18
+ attr_accessor :id, :culprit, :exception, :log, :transaction_id,
19
+ :transaction, :context, :parent_id, :trace_id
20
+ attr_reader :timestamp
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Error
5
+ # @api private
6
+ class Exception
7
+ MOD_SPLIT = '::'
8
+
9
+ def initialize(attrs = nil)
10
+ return unless attrs
11
+
12
+ attrs.each do |key, val|
13
+ send(:"#{key}=", val)
14
+ end
15
+ end
16
+
17
+ def self.from_exception(exception, **attrs)
18
+ new({
19
+ message: exception.message.to_s,
20
+ type: exception.class.to_s,
21
+ module: format_module(exception),
22
+ cause: exception.cause && Exception.from_exception(exception.cause)
23
+ }.merge(attrs))
24
+ end
25
+
26
+ attr_accessor(
27
+ :attributes,
28
+ :code,
29
+ :handled,
30
+ :message,
31
+ :module,
32
+ :stacktrace,
33
+ :type,
34
+ :cause
35
+ )
36
+
37
+ class << self
38
+ private
39
+
40
+ def format_module(exception)
41
+ exception.class.to_s.split(MOD_SPLIT)[0...-1].join(MOD_SPLIT)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Error
5
+ # @api private
6
+ class Log
7
+ def initialize(message, attrs = {})
8
+ @message = message
9
+
10
+ attrs.each do |key, val|
11
+ send(:"#{key}=", val)
12
+ end
13
+ end
14
+
15
+ attr_accessor(
16
+ :level,
17
+ :logger_name,
18
+ :message,
19
+ :param_message,
20
+ :stacktrace
21
+ )
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ class ErrorBuilder
6
+ def initialize(agent)
7
+ @agent = agent
8
+ end
9
+
10
+ def build_exception(exception, context: nil, handled: true)
11
+ error = Error.new context: context || Context.new
12
+ error.exception =
13
+ Error::Exception.from_exception(exception, handled: handled)
14
+
15
+ Util.reverse_merge!(error.context.labels, @agent.config.default_labels)
16
+
17
+ if exception.backtrace
18
+ add_stacktrace error, :exception, exception.backtrace
19
+ end
20
+
21
+ add_current_transaction_fields error, Atatus.current_transaction
22
+
23
+ error
24
+ end
25
+
26
+ def build_log(message, context: nil, backtrace: nil, **attrs)
27
+ error = Error.new context: context || Context.new
28
+ error.log = Error::Log.new(message, **attrs)
29
+
30
+ if backtrace
31
+ add_stacktrace error, :log, backtrace
32
+ end
33
+
34
+ add_current_transaction_fields error, Atatus.current_transaction
35
+
36
+ error
37
+ end
38
+
39
+ private
40
+
41
+ def add_stacktrace(error, kind, backtrace)
42
+ stacktrace =
43
+ @agent.stacktrace_builder.build(backtrace, type: :error)
44
+ return unless stacktrace
45
+
46
+ case kind
47
+ when :exception
48
+ error.exception.stacktrace = stacktrace
49
+ when :log
50
+ error.log.stacktrace = stacktrace
51
+ end
52
+
53
+ error.culprit = stacktrace.frames.first&.function
54
+ end
55
+
56
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
57
+ def add_current_transaction_fields(error, transaction)
58
+ return unless transaction
59
+
60
+ error.transaction_id = transaction.id
61
+ error.transaction = {
62
+ sampled: transaction.sampled?,
63
+ type: transaction.type
64
+ }
65
+ error.transaction[:name] = transaction.name unless transaction.name.nil?
66
+ error.trace_id = transaction.trace_id
67
+ error.parent_id = Atatus.current_span&.id || transaction.id
68
+
69
+ return unless transaction.context
70
+
71
+ Util.reverse_merge!(error.context.labels, transaction.context.labels)
72
+ Util.reverse_merge!(error.context.custom, transaction.context.custom)
73
+ end
74
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
75
+ end
76
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/trace_context'
4
+ require 'atatus/span'
5
+ require 'atatus/transaction'
6
+ require 'atatus/span_helpers'
7
+
8
+ module Atatus
9
+ # rubocop:disable Metrics/ClassLength
10
+ # @api private
11
+ class Instrumenter
12
+ TRANSACTION_KEY = :__atatus_instrumenter_transaction_key
13
+ SPAN_KEY = :__atatus_instrumenter_spans_key
14
+
15
+ include Logging
16
+
17
+ # @api private
18
+ class Current
19
+ def initialize
20
+ self.transaction = nil
21
+ self.spans = []
22
+ end
23
+
24
+ def transaction
25
+ Thread.current[TRANSACTION_KEY]
26
+ end
27
+
28
+ def transaction=(transaction)
29
+ Thread.current[TRANSACTION_KEY] = transaction
30
+ end
31
+
32
+ def spans
33
+ Thread.current[SPAN_KEY] ||= []
34
+ end
35
+
36
+ def spans=(spans)
37
+ Thread.current[SPAN_KEY] ||= []
38
+ Thread.current[SPAN_KEY] = spans
39
+ end
40
+ end
41
+
42
+ def initialize(config, stacktrace_builder:, &enqueue)
43
+ @config = config
44
+ @stacktrace_builder = stacktrace_builder
45
+ @enqueue = enqueue
46
+
47
+ @current = Current.new
48
+ end
49
+
50
+ attr_reader :config, :stacktrace_builder, :enqueue
51
+
52
+ def start
53
+ debug 'Starting instrumenter'
54
+ end
55
+
56
+ def stop
57
+ debug 'Stopping instrumenter'
58
+
59
+ self.current_transaction = nil
60
+ current_spans.pop until current_spans.empty?
61
+
62
+ @subscriber.unregister! if @subscriber
63
+ end
64
+
65
+ def subscriber=(subscriber)
66
+ debug 'Registering subscriber'
67
+ @subscriber = subscriber
68
+ @subscriber.register!
69
+ end
70
+
71
+ # transactions
72
+
73
+ def current_transaction
74
+ @current.transaction
75
+ end
76
+
77
+ def current_transaction=(transaction)
78
+ @current.transaction = transaction
79
+ end
80
+
81
+ # rubocop:disable Metrics/MethodLength
82
+ def start_transaction(
83
+ name = nil,
84
+ type = nil,
85
+ context: nil,
86
+ trace_context: nil
87
+ )
88
+ return nil unless config.instrument?
89
+
90
+ if (transaction = current_transaction)
91
+ raise ExistingTransactionError,
92
+ "Transactions may not be nested.\nAlready inside #{transaction}"
93
+ end
94
+
95
+ sampled = trace_context ? trace_context.recorded? : random_sample?
96
+
97
+ transaction =
98
+ Transaction.new(
99
+ name,
100
+ type,
101
+ context: context,
102
+ trace_context: trace_context,
103
+ sampled: sampled,
104
+ labels: config.default_labels
105
+ )
106
+
107
+ transaction.start
108
+
109
+ self.current_transaction = transaction
110
+ end
111
+ # rubocop:enable Metrics/MethodLength
112
+
113
+ def end_transaction(result = nil)
114
+ return nil unless (transaction = current_transaction)
115
+
116
+ self.current_transaction = nil
117
+
118
+ transaction.done result
119
+
120
+ enqueue.call transaction
121
+
122
+ transaction
123
+ end
124
+
125
+ # spans
126
+
127
+ def current_spans
128
+ @current.spans
129
+ end
130
+
131
+ def current_span
132
+ current_spans.last
133
+ end
134
+
135
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
136
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
137
+ # rubocop:disable Metrics/ParameterLists
138
+ def start_span(
139
+ name,
140
+ type = nil,
141
+ subtype: nil,
142
+ action: nil,
143
+ backtrace: nil,
144
+ context: nil,
145
+ trace_context: nil
146
+ )
147
+ return unless (transaction = current_transaction)
148
+ return unless transaction.sampled?
149
+
150
+ transaction.inc_started_spans!
151
+
152
+ if transaction.max_spans_reached?(config)
153
+ transaction.inc_dropped_spans!
154
+ return
155
+ end
156
+
157
+ parent = current_span || transaction
158
+
159
+ span = Span.new(
160
+ name: name,
161
+ subtype: subtype,
162
+ action: action,
163
+ transaction_id: transaction.id,
164
+ trace_context: trace_context || parent.trace_context.child,
165
+ type: type,
166
+ context: context,
167
+ stacktrace_builder: stacktrace_builder
168
+ )
169
+
170
+ if backtrace && config.span_frames_min_duration?
171
+ span.original_backtrace = backtrace
172
+ end
173
+
174
+ current_spans.push span
175
+
176
+ span.start
177
+ end
178
+ # rubocop:enable Metrics/ParameterLists
179
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
180
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
181
+
182
+ def end_span
183
+ return unless (span = current_spans.pop)
184
+
185
+ span.done
186
+
187
+ enqueue.call span
188
+
189
+ span
190
+ end
191
+
192
+ # metadata
193
+
194
+ def set_label(key, value)
195
+ return unless current_transaction
196
+
197
+ key = key.to_s.gsub(/[\."\*]/, '_').to_sym
198
+ current_transaction.context.labels[key] = value
199
+ end
200
+
201
+ def set_custom_context(context)
202
+ return unless current_transaction
203
+ current_transaction.context.custom.merge!(context)
204
+ end
205
+
206
+ def set_user(user)
207
+ return unless current_transaction
208
+ current_transaction.context.user = Context::User.infer(config, user)
209
+ end
210
+
211
+ def inspect
212
+ '<Atatus::Instrumenter ' \
213
+ "current_transaction=#{current_transaction.inspect}" \
214
+ '>'
215
+ end
216
+
217
+ private
218
+
219
+ def random_sample?
220
+ rand <= config.transaction_sample_rate
221
+ end
222
+ end
223
+ # rubocop:enable Metrics/ClassLength
224
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class InternalError < StandardError; end
5
+ class ExistingTransactionError < InternalError; end
6
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Atatus
6
+ # @api private
7
+ module Logging
8
+ PREFIX = '[Atatus] '
9
+
10
+ LEVELS = {
11
+ debug: Logger::DEBUG,
12
+ info: Logger::INFO,
13
+ warn: Logger::WARN,
14
+ error: Logger::ERROR,
15
+ fatal: Logger::FATAL
16
+ }.freeze
17
+
18
+ def debug(msg, *args, &block)
19
+ log(:debug, msg, *args, &block)
20
+ end
21
+
22
+ def info(msg, *args, &block)
23
+ log(:info, msg, *args, &block)
24
+ end
25
+
26
+ def warn(msg, *args, &block)
27
+ log(:warn, msg, *args, &block)
28
+ end
29
+
30
+ def error(msg, *args, &block)
31
+ log(:error, msg, *args, &block)
32
+ end
33
+
34
+ def fatal(msg, *args, &block)
35
+ log(:fatal, msg, *args, &block)
36
+ end
37
+
38
+ private
39
+
40
+ def log(lvl, msg, *args)
41
+ return unless (logger = @config&.logger)
42
+ return unless LEVELS[lvl] >= (@config&.log_level || 0)
43
+
44
+ formatted_msg = prepend_prefix(format(msg.to_s, *args))
45
+
46
+ return logger.send(lvl, formatted_msg) unless block_given?
47
+
48
+ logger.send(lvl, "#{formatted_msg}\n#{yield}")
49
+ end
50
+
51
+ def prepend_prefix(str)
52
+ "#{PREFIX}#{str}"
53
+ end
54
+ end
55
+ end