atatus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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