atatus 1.7.0 → 2.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 +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +49 -13
  4. data/LICENSE +1 -1
  5. data/atatus.gemspec +3 -3
  6. data/lib/atatus/agent.rb +10 -7
  7. data/lib/atatus/central_config.rb +19 -8
  8. data/lib/atatus/collector/layer.rb +1 -1
  9. data/lib/atatus/{sql_summarizer.rb → config/log_level_map.rb} +22 -28
  10. data/lib/atatus/config/options.rb +2 -1
  11. data/lib/atatus/config/regexp_list.rb +1 -1
  12. data/lib/atatus/config/round_float.rb +31 -0
  13. data/lib/atatus/config/server_info.rb +50 -0
  14. data/lib/atatus/config/wildcard_pattern_list.rb +3 -1
  15. data/lib/atatus/config.rb +91 -70
  16. data/lib/atatus/context/request/socket.rb +1 -2
  17. data/lib/atatus/context/response.rb +1 -3
  18. data/lib/atatus/context.rb +3 -10
  19. data/lib/atatus/context_builder.rb +3 -3
  20. data/lib/atatus/error.rb +2 -1
  21. data/lib/atatus/error_builder.rb +1 -1
  22. data/lib/atatus/fields.rb +98 -0
  23. data/lib/atatus/graphql.rb +2 -0
  24. data/lib/atatus/grpc.rb +5 -7
  25. data/lib/atatus/instrumenter.rb +29 -25
  26. data/lib/atatus/metadata/cloud_info.rb +156 -0
  27. data/lib/atatus/metadata/service_info.rb +3 -3
  28. data/lib/atatus/metadata/system_info/container_info.rb +20 -8
  29. data/lib/atatus/metadata/system_info.rb +20 -5
  30. data/lib/atatus/metadata.rb +3 -1
  31. data/lib/atatus/metrics/cpu_mem_set.rb +10 -38
  32. data/lib/atatus/metrics/jvm_set.rb +88 -0
  33. data/lib/atatus/metrics/metric.rb +2 -0
  34. data/lib/atatus/metrics.rb +33 -16
  35. data/lib/atatus/middleware.rb +8 -3
  36. data/lib/atatus/naively_hashable.rb +1 -0
  37. data/lib/atatus/normalizers/rails/active_record.rb +25 -7
  38. data/lib/atatus/normalizers.rb +2 -2
  39. data/lib/atatus/opentracing.rb +5 -3
  40. data/lib/atatus/rails.rb +1 -1
  41. data/lib/atatus/span/context/db.rb +1 -1
  42. data/lib/atatus/span/context/destination.rb +58 -32
  43. data/lib/atatus/span/context/http.rb +2 -0
  44. data/lib/atatus/span/context/links.rb +32 -0
  45. data/lib/atatus/{sql.rb → span/context/message.rb} +16 -12
  46. data/lib/atatus/span/context/service.rb +55 -0
  47. data/lib/atatus/span/context.rb +28 -3
  48. data/lib/atatus/span.rb +35 -5
  49. data/lib/atatus/span_helpers.rb +12 -23
  50. data/lib/atatus/spies/action_dispatch.rb +10 -13
  51. data/lib/atatus/spies/azure_storage_table.rb +148 -0
  52. data/lib/atatus/spies/delayed_job.rb +19 -13
  53. data/lib/atatus/spies/dynamo_db.rb +56 -15
  54. data/lib/atatus/spies/elasticsearch.rb +54 -39
  55. data/lib/atatus/spies/faraday.rb +92 -58
  56. data/lib/atatus/spies/http.rb +33 -37
  57. data/lib/atatus/spies/json.rb +5 -9
  58. data/lib/atatus/spies/mongo.rb +26 -19
  59. data/lib/atatus/spies/net_http.rb +53 -51
  60. data/lib/atatus/spies/racecar.rb +77 -0
  61. data/lib/atatus/spies/rake.rb +27 -27
  62. data/lib/atatus/spies/redis.rb +11 -12
  63. data/lib/atatus/spies/resque.rb +18 -23
  64. data/lib/atatus/spies/s3.rb +132 -0
  65. data/lib/atatus/spies/sequel.rb +11 -2
  66. data/lib/atatus/spies/shoryuken.rb +4 -6
  67. data/lib/atatus/spies/sidekiq.rb +23 -31
  68. data/lib/atatus/spies/sinatra.rb +20 -28
  69. data/lib/atatus/spies/sneakers.rb +2 -0
  70. data/lib/atatus/spies/sns.rb +126 -0
  71. data/lib/atatus/spies/sqs.rb +231 -0
  72. data/lib/atatus/spies/sucker_punch.rb +20 -22
  73. data/lib/atatus/spies/tilt.rb +10 -13
  74. data/lib/atatus/spies.rb +20 -0
  75. data/lib/atatus/sql/signature.rb +4 -2
  76. data/lib/atatus/sql/tokenizer.rb +23 -7
  77. data/lib/atatus/stacktrace/frame.rb +1 -0
  78. data/lib/atatus/stacktrace_builder.rb +12 -16
  79. data/lib/atatus/subscriber.rb +1 -0
  80. data/lib/atatus/trace_context/traceparent.rb +5 -8
  81. data/lib/atatus/trace_context/tracestate.rb +16 -14
  82. data/lib/atatus/trace_context.rb +6 -16
  83. data/lib/atatus/transaction.rb +17 -4
  84. data/lib/atatus/transport/base.rb +1 -3
  85. data/lib/atatus/transport/connection/http.rb +11 -3
  86. data/lib/atatus/transport/connection/proxy_pipe.rb +1 -2
  87. data/lib/atatus/transport/connection.rb +3 -2
  88. data/lib/atatus/transport/filters/hash_sanitizer.rb +16 -34
  89. data/lib/atatus/transport/filters/secrets_filter.rb +35 -12
  90. data/lib/atatus/transport/serializers/context_serializer.rb +1 -2
  91. data/lib/atatus/transport/serializers/metadata_serializer.rb +54 -8
  92. data/lib/atatus/transport/serializers/metricset_serializer.rb +2 -2
  93. data/lib/atatus/transport/serializers/span_serializer.rb +55 -9
  94. data/lib/atatus/transport/serializers/transaction_serializer.rb +1 -0
  95. data/lib/atatus/transport/serializers.rb +9 -6
  96. data/lib/atatus/transport/user_agent.rb +16 -9
  97. data/lib/atatus/transport/worker.rb +2 -1
  98. data/lib/atatus/util/deep_dup.rb +65 -0
  99. data/lib/atatus/util/precision_validator.rb +46 -0
  100. data/lib/atatus/util.rb +2 -0
  101. data/lib/atatus/version.rb +1 -1
  102. data/lib/atatus.rb +32 -5
  103. metadata +40 -11
data/lib/atatus/config.rb CHANGED
@@ -17,20 +17,26 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- require 'atatus/config/options'
21
- require 'atatus/config/duration'
22
20
  require 'atatus/config/bytes'
21
+ require 'atatus/config/duration'
22
+ require 'atatus/config/log_level_map'
23
+ require 'atatus/config/options'
24
+ require 'atatus/config/round_float'
23
25
  require 'atatus/config/regexp_list'
24
26
  require 'atatus/config/wildcard_pattern_list'
27
+ require 'atatus/deprecations'
28
+ require 'atatus/config/server_info'
25
29
 
26
30
  module Atatus
27
31
  # @api private
28
32
  class Config
29
33
  extend Options
34
+ extend Deprecations
30
35
 
31
- DEPRECATED_OPTIONS = %i[].freeze
36
+ SANITIZE_FIELD_NAMES_DEFAULT =
37
+ %w[password passwd pwd secret *key *token* *session* *credit* *card* *auth* set-cookie].freeze
32
38
 
33
- # rubocop:disable Metrics/LineLength, Layout/ExtraSpacing
39
+ # rubocop:disable Layout/LineLength, Layout/ExtraSpacing
34
40
  option :app_name, type: :string
35
41
  option :license_key, type: :string
36
42
  option :notify_host, type: :string, default: 'https://apm-rx.atatus.com'
@@ -51,6 +57,7 @@ module Atatus
51
57
  option :capture_elasticsearch_queries, type: :bool, default: false
52
58
  option :capture_env, type: :bool, default: true
53
59
  option :central_config, type: :bool, default: true
60
+ option :cloud_provider, type: :string, default: 'auto'
54
61
  option :current_user_email_method, type: :string, default: 'email'
55
62
  option :current_user_id_method, type: :string, default: 'id'
56
63
  option :current_user_username_method, type: :string, default: 'username'
@@ -73,7 +80,8 @@ module Atatus
73
80
  option :ignore_url_patterns, type: :list, default: [], converter: RegexpList.new
74
81
  option :instrument, type: :bool, default: true
75
82
  option :instrumented_rake_tasks, type: :list, default: []
76
- option :log_level, type: :int, default: Logger::INFO
83
+ option :log_ecs_reformatting, type: :string, default: 'off'
84
+ option :log_level, type: :int, default: Logger::INFO, converter: LogLevelMap.new
77
85
  option :log_path, type: :string, default: '-'
78
86
  option :metrics_interval, type: :int, default: '30s', converter: Duration.new
79
87
  option :pool_size, type: :int, default: 1
@@ -83,8 +91,8 @@ module Atatus
83
91
  option :proxy_port, type: :int
84
92
  option :proxy_username, type: :string
85
93
  option :recording, type: :bool, default: true
86
- option :sanitize_field_names, type: :list, default: [], converter: WildcardPatternList.new
87
- option :server_ca_cert, type: :string
94
+ option :sanitize_field_names, type: :list, default: SANITIZE_FIELD_NAMES_DEFAULT, converter: WildcardPatternList.new
95
+ option :server_ca_cert_file, type: :string
88
96
  option :service_name, type: :string
89
97
  option :service_node_name, type: :string
90
98
  option :service_version, type: :string
@@ -96,12 +104,21 @@ module Atatus
96
104
  option :stack_trace_limit, type: :int, default: 999_999
97
105
  option :transaction_ignore_urls, type: :list, default: [], converter: WildcardPatternList.new
98
106
  option :transaction_max_spans, type: :int, default: 500
99
- option :transaction_sample_rate, type: :float, default: 1.0
107
+ option :transaction_sample_rate, type: :float, default: 1.0, converter: RoundFloat.new
100
108
  option :use_atatus_traceparent_header, type: :bool, default: true
101
- option :use_legacy_sql_parser, type: :bool, default: false
102
109
  option :verify_server_cert, type: :bool, default: true
103
110
 
104
- # rubocop:enable Metrics/LineLength, Layout/ExtraSpacing
111
+ def log_ecs_formatting
112
+ log_ecs_reformatting
113
+ end
114
+
115
+ def log_ecs_formatting=(value)
116
+ @options[:log_ecs_reformatting].set(value)
117
+ end
118
+
119
+ deprecate :log_ecs_formatting, :log_ecs_reformatting
120
+
121
+ # rubocop:enable Layout/LineLength, Layout/ExtraSpacing
105
122
  def initialize(options = {})
106
123
  @options = load_schema
107
124
 
@@ -114,19 +131,20 @@ module Atatus
114
131
  self.config_file = env_config_file
115
132
  end
116
133
 
117
- assign(load_config_file)
134
+ assign(load_config_file(self.environment))
118
135
  assign(env)
119
136
 
120
137
  yield self if block_given?
121
138
 
122
- self.logger ||= build_logger
139
+ if self.logger.nil? || self.log_path
140
+ self.logger = build_logger
141
+ end
123
142
 
124
143
  @__view_paths ||= []
125
144
  @__root_path ||= Dir.pwd
126
145
  end
127
146
 
128
- attr_accessor :__view_paths, :__root_path
129
- attr_accessor :logger
147
+ attr_accessor :__view_paths, :__root_path, :logger
130
148
 
131
149
  attr_reader :options
132
150
 
@@ -138,6 +156,7 @@ module Atatus
138
156
  def available_instrumentations
139
157
  %w[
140
158
  action_dispatch
159
+ azure_storage_table
141
160
  delayed_job
142
161
  dynamo_db
143
162
  elasticsearch
@@ -147,13 +166,17 @@ module Atatus
147
166
  mongo
148
167
  net_http
149
168
  rake
169
+ racecar
150
170
  redis
151
171
  resque
172
+ s3
152
173
  sequel
153
174
  shoryuken
154
175
  sidekiq
155
176
  sinatra
156
177
  sneakers
178
+ sns
179
+ sqs
157
180
  sucker_punch
158
181
  tilt
159
182
  ]
@@ -163,11 +186,6 @@ module Atatus
163
186
  available_instrumentations - disable_instrumentations
164
187
  end
165
188
 
166
- def method_missing(name, *args)
167
- return super unless DEPRECATED_OPTIONS.include?(name)
168
- warn "The option `#{name}' has been removed."
169
- end
170
-
171
189
  def replace_options(new_options)
172
190
  return if new_options.nil? || new_options.empty?
173
191
  options_copy = @options.dup
@@ -214,8 +232,8 @@ module Atatus
214
232
 
215
233
  @ssl_context ||=
216
234
  OpenSSL::SSL::SSLContext.new.tap do |context|
217
- if server_ca_cert
218
- context.ca_file = server_ca_cert
235
+ if server_ca_cert_file
236
+ context.ca_file = server_ca_cert_file
219
237
  else
220
238
  context.cert_store =
221
239
  OpenSSL::X509::Store.new.tap(&:set_default_paths)
@@ -234,66 +252,49 @@ module Atatus
234
252
  super.split.first + '>'
235
253
  end
236
254
 
237
- # Deprecations
238
-
239
- def default_tags=(value)
240
- warn '[DEPRECATED] The option default_tags has been renamed to ' \
241
- 'default_labels.'
242
- self.default_labels = value
255
+ def version
256
+ @version ||= ServerInfo.new(self).version
243
257
  end
244
258
 
245
- def ignore_url_patterns=(value)
246
- unless value == self.class.schema[:ignore_url_patterns][:default]
247
- warn '[DEPRECATED] The option ignore_url_patterns is being removed. ' \
248
- 'Consider using transaction_ignore_urls instead.'
249
- end
259
+ private
250
260
 
251
- set(:ignore_url_patterns, value)
252
- end
253
261
 
254
- def custom_key_filters=(value)
255
- unless value == self.class.schema[:custom_key_filters][:default]
256
- warn '[DEPRECATED] The option custom_key_filters is being removed. ' \
257
- 'See sanitize_field_names for an alternative.'
262
+ def parse_erb(file)
263
+ begin
264
+ file.gsub!(/^\s*#.*$/, '#')
265
+ ERB.new(file).result(binding)
266
+ rescue ScriptError, StandardError => e
267
+ warn "[Atatus] FATAL : Failed ERB processing of atatus.yml file. Please check contents of the file. #{e}"
268
+ nil
258
269
  end
259
-
260
- set(:custom_key_filters, value)
261
- end
262
-
263
- def disabled_instrumentations
264
- disable_instrumentations
265
270
  end
266
271
 
267
- def active
268
- enabled
269
- end
270
- alias active? active
272
+ def load_config_file(env)
273
+ return unless File.exist?(config_file)
271
274
 
272
- def disabled_instrumentations=(value)
273
- warn '[DEPRECATED] The option disabled_instrumentations has been ' \
274
- 'renamed to disable_instrumentations to align with other agents.'
275
- self.disable_instrumentations = value
276
- end
275
+ config = {}
277
276
 
278
- def use_experimental_sql_parser=(value)
279
- warn '[DEPRECATED] The new SQL parser is now the default. To use the old one, '
280
- 'use use_legacy_sql_parser and please report why you wish to do so.'
281
- end
277
+ begin
278
+ read = File.read(config_file)
279
+ evaled = parse_erb(read)
280
+
281
+ # YAML.safe_load(evaled)
282
282
 
283
- def active=(value)
284
- warn '[DEPRECATED] The option active has been renamed to enabled ' \
285
- 'to align with other agents and with the remote config.'
286
- self.enabled = value
287
- end
283
+ loaded_config = YAML.load(evaled)
288
284
 
289
- private
285
+ if loaded_config.key?("default")
286
+ unless loaded_config.key?(env)
287
+ warn "[Atatus] ERROR : atatus.yml does not include '#{env}' section!"
288
+ loaded_config = loaded_config["default"]
289
+ end
290
+ end
290
291
 
291
- def load_config_file
292
- return unless File.exist?(config_file)
292
+ config = loaded_config[env] || loaded_config
293
+ rescue ScriptError, StandardError => e
294
+ warn "[Atatus] FATAL : Error parsing atatus.yml configuration '#{e}'"
295
+ end
293
296
 
294
- read = File.read(config_file)
295
- evaled = ERB.new(read).result
296
- YAML.safe_load(evaled)
297
+ config
297
298
  end
298
299
 
299
300
  def load_env
@@ -304,7 +305,25 @@ module Atatus
304
305
  end
305
306
 
306
307
  def build_logger
307
- Logger.new(log_path == '-' ? STDOUT : log_path).tap do |logger|
308
+ if self.log_ecs_reformatting == 'override'
309
+ begin
310
+ return build_ecs_logger
311
+ rescue LoadError
312
+ logger.info "Attempted to use EcsLogging::Logger but the gem couldn't be " \
313
+ "loaded so a ::Logger was created instead. Check if you have the `ecs-logging` " \
314
+ "gem installed and attempt to start the agent again."
315
+ end
316
+ end
317
+
318
+ Logger.new(log_path == '-' ? $stdout : log_path).tap do |logger|
319
+ logger.level = log_level
320
+ end
321
+ end
322
+
323
+ def build_ecs_logger
324
+ require 'ecs_logging/logger'
325
+
326
+ ::EcsLogging::Logger.new(log_path == '-' ? $stdout : log_path).tap do |logger|
308
327
  logger.level = log_level
309
328
  end
310
329
  end
@@ -333,9 +352,11 @@ module Atatus
333
352
  self.framework_name ||= 'Rails'
334
353
  self.framework_version ||= ::Rails::VERSION::STRING
335
354
  self.logger ||= ::Rails.logger
355
+ self.log_level ||= ::Rails.logger.log_level
336
356
 
337
357
  self.__root_path = ::Rails.root.to_s
338
- self.__view_paths = app.config.paths['app/views'].existent + [::Rails.root.to_s]
358
+ self.__view_paths = app.config.paths['app/views'].existent +
359
+ [::Rails.root.to_s]
339
360
  end
340
361
 
341
362
  def rails_app_name(app)
@@ -26,10 +26,9 @@ module Atatus
26
26
  class Socket
27
27
  def initialize(req)
28
28
  @remote_addr = req.env['REMOTE_ADDR']
29
- @encrypted = req.scheme == 'https'
30
29
  end
31
30
 
32
- attr_reader :remote_addr, :encrypted
31
+ attr_reader :remote_addr
33
32
  end
34
33
  end
35
34
  end
@@ -38,9 +38,7 @@ module Atatus
38
38
  attr_reader :headers
39
39
 
40
40
  def headers=(headers)
41
- @headers = headers&.each_with_object({}) do |(k, v), hsh|
42
- hsh[k] = v.to_s
43
- end
41
+ @headers = headers&.transform_values { |v| v.to_s }
44
42
  end
45
43
  end
46
44
  end
@@ -37,16 +37,10 @@ module Atatus
37
37
  Service = Struct.new(:framework)
38
38
  Framework = Struct.new(:name, :version)
39
39
 
40
- attr_accessor :request
41
- attr_accessor :response
42
- attr_accessor :user
43
- attr_reader :custom
44
- attr_reader :labels
45
- attr_reader :service
46
- attr_accessor :company
47
- attr_accessor :response_body
40
+ attr_accessor :request, :response, :user
41
+ attr_reader :custom, :labels, :service
42
+ attr_accessor :company, :response_body
48
43
 
49
- # rubocop:disable Metrics/CyclomaticComplexity
50
44
  def empty?
51
45
  return false if labels.any?
52
46
  return false if custom.any?
@@ -56,7 +50,6 @@ module Atatus
56
50
 
57
51
  true
58
52
  end
59
- # rubocop:enable Metrics/CyclomaticComplexity
60
53
 
61
54
  def set_service(framework_name: nil, framework_version: nil)
62
55
  @service = Service.new(
@@ -44,7 +44,7 @@ module Atatus
44
44
  request = context.request
45
45
 
46
46
  request.socket = Context::Request::Socket.new(req)
47
- request.http_version = build_http_version rack_env
47
+ request.http_version = build_http_version req
48
48
  request.method = req.request_method
49
49
  request.url = Context::Request::Url.new(req)
50
50
 
@@ -104,8 +104,8 @@ module Atatus
104
104
  key.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
105
105
  end
106
106
 
107
- def build_http_version(rack_env)
108
- return unless (http_version = rack_env['HTTP_VERSION'])
107
+ def build_http_version(req)
108
+ return unless (http_version = req.env['HTTP_VERSION'])
109
109
  http_version.gsub(%r{HTTP/}, '')
110
110
  end
111
111
  end
data/lib/atatus/error.rb CHANGED
@@ -33,7 +33,7 @@ module Atatus
33
33
  end
34
34
 
35
35
  attr_accessor :id, :culprit, :exception, :log, :transaction_id,
36
- :transaction, :context, :parent_id, :trace_id
36
+ :transaction_name, :transaction, :context, :parent_id, :trace_id
37
37
  attr_reader :timestamp
38
38
 
39
39
  def inspect
@@ -41,6 +41,7 @@ module Atatus
41
41
  " culprit:#{culprit}" \
42
42
  " timestamp:#{timestamp}" \
43
43
  " transaction_id:#{transaction_id}" \
44
+ " transaction_name:#{transaction_name}" \
44
45
  " trace_id:#{trace_id}" \
45
46
  " exception:#{exception.inspect}" \
46
47
  '>'
@@ -74,11 +74,11 @@ module Atatus
74
74
  return unless transaction
75
75
 
76
76
  error.transaction_id = transaction.id
77
+ error.transaction_name = transaction.name
77
78
  error.transaction = {
78
79
  sampled: transaction.sampled?,
79
80
  type: transaction.type
80
81
  }
81
- error.transaction[:name] = transaction.name unless transaction.name.nil?
82
82
  error.trace_id = transaction.trace_id
83
83
  error.parent_id = Atatus.current_span&.id || transaction.id
84
84
 
@@ -0,0 +1,98 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ #
18
+ # frozen_string_literal: true
19
+
20
+ module Atatus
21
+ # An interface for creating simple, value holding objects that correspond to
22
+ # object fields in the API.
23
+ #
24
+ # Example:
25
+ # class MyThing
26
+ # include Fields
27
+ # field :name
28
+ # field :address, default: 'There'
29
+ # end
30
+ #
31
+ # MyThing.new(name: 'AJ').to_h
32
+ # # => { name: 'AJ' }
33
+ # MyThing.new().empty?
34
+ # # => true
35
+ module Fields
36
+ class Field
37
+ def initialize(key, default: nil)
38
+ @key = key
39
+ @default = default
40
+ end
41
+
42
+ attr_reader :key, :default
43
+ end
44
+
45
+ module InstanceMethods
46
+ def initialize(**attrs)
47
+ schema.each do |key, field|
48
+ send(:"#{key}=", field.default)
49
+ end
50
+
51
+ attrs.each do |key, value|
52
+ send(:"#{key}=", value)
53
+ end
54
+
55
+ super()
56
+ end
57
+
58
+ def empty?
59
+ self.class.schema.each do |key, field|
60
+ next if send(key).nil?
61
+ return false
62
+ end
63
+
64
+ true
65
+ end
66
+
67
+ def to_h
68
+ schema.each_with_object({}) do |(key, field), hsh|
69
+ hsh[key] = send(key)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def schema
76
+ self.class.schema
77
+ end
78
+ end
79
+
80
+ module ClassMethods
81
+ def field(key, default: nil)
82
+ field = Field.new(key, default: default)
83
+ schema[key] = field
84
+
85
+ attr_accessor(key)
86
+ end
87
+
88
+ attr_reader :schema
89
+ end
90
+
91
+ def self.included(cls)
92
+ cls.extend(ClassMethods)
93
+ cls.include(InstanceMethods)
94
+
95
+ cls.instance_variable_set(:@schema, {})
96
+ end
97
+ end
98
+ end
@@ -48,6 +48,7 @@ module Atatus
48
48
  # "authorized" => "graphql.authorized",
49
49
  }.freeze
50
50
 
51
+ # rubocop:disable Style/ExplicitBlockArgument
51
52
  def self.trace(key, data)
52
53
  return yield unless KEYS_TO_NAME.key?(key)
53
54
  return yield unless (transaction = Atatus.current_transaction)
@@ -69,6 +70,7 @@ module Atatus
69
70
 
70
71
  results
71
72
  end
73
+ # rubocop:enable Style/ExplicitBlockArgument
72
74
 
73
75
  class << self
74
76
  private
data/lib/atatus/grpc.rb CHANGED
@@ -25,7 +25,7 @@ module Atatus
25
25
  TYPE = 'external'
26
26
  SUBTYPE = 'grpc'
27
27
 
28
- # rubocop:disable Lint/UnusedMethodArgument
28
+ # rubocop:disable Lint/UnusedMethodArgument, Style/ExplicitBlockArgument
29
29
  def request_response(request:, call:, method:, metadata:)
30
30
  return yield unless (transaction = Atatus.current_transaction)
31
31
  if (trace_context = transaction.trace_context)
@@ -40,7 +40,7 @@ module Atatus
40
40
  yield
41
41
  end
42
42
  end
43
- # rubocop:enable Lint/UnusedMethodArgument
43
+ # rubocop:enable Lint/UnusedMethodArgument, Style/ExplicitBlockArgument
44
44
 
45
45
  private
46
46
 
@@ -50,11 +50,9 @@ module Atatus
50
50
 
51
51
  split_peer = URI.split(peer)
52
52
  destination = Atatus::Span::Context::Destination.new(
53
- type: TYPE,
54
- name: SUBTYPE,
55
- resource: peer,
56
53
  address: split_peer[0],
57
- port: split_peer[6]
54
+ port: split_peer[6],
55
+ service: { type: TYPE, name: SUBTYPE, resource: peer }
58
56
  )
59
57
  Atatus::Span::Context.new(destination: destination)
60
58
  end
@@ -71,7 +69,7 @@ module Atatus
71
69
  transaction.done 'success'
72
70
  rescue ::Exception => e
73
71
  Atatus.report(e, handled: false)
74
- transaction.done 'error' if transaction
72
+ transaction&.done 'error'
75
73
  raise
76
74
  ensure
77
75
  Atatus.end_transaction
@@ -89,7 +89,7 @@ module Atatus
89
89
  end
90
90
 
91
91
  def subscriber=(subscriber)
92
- debug 'Registering subscriber'
92
+ debug 'Registering ActiveSupport::Notifications subscriber'
93
93
  @subscriber = subscriber
94
94
  @subscriber.register!
95
95
  end
@@ -120,11 +120,11 @@ module Atatus
120
120
  end
121
121
 
122
122
  if trace_context
123
- samled = trace_context.recorded?
123
+ sampled = trace_context.recorded?
124
124
  sample_rate = trace_context.tracestate.sample_rate
125
125
  else
126
126
  sampled = random_sample?(config)
127
- sample_rate = config.transaction_sample_rate
127
+ sample_rate = sampled ? config.transaction_sample_rate : 0
128
128
  end
129
129
 
130
130
  transaction =
@@ -150,7 +150,9 @@ module Atatus
150
150
 
151
151
  transaction.done result
152
152
 
153
- enqueue.call transaction
153
+ if transaction.sampled? || @config.version < Config::ServerInfo::VERSION_8_0
154
+ enqueue.call transaction
155
+ end
154
156
 
155
157
  update_transaction_metrics(transaction)
156
158
 
@@ -179,7 +181,8 @@ module Atatus
179
181
  context: nil,
180
182
  trace_context: nil,
181
183
  parent: nil,
182
- sync: nil
184
+ sync: nil,
185
+ exit_span: nil
183
186
  )
184
187
 
185
188
  transaction =
@@ -197,6 +200,15 @@ module Atatus
197
200
 
198
201
  parent ||= (current_span || current_transaction)
199
202
 
203
+ # To not mess with breakdown metric stats, exit spans MUST not add
204
+ # sub-spans unless they share the same type and subtype.
205
+ if parent && parent.is_a?(Span) && parent.exit_span?
206
+ if parent.type != type || parent.subtype != subtype
207
+ debug "Skipping new span '#{name}' as its parent is an exit_span"
208
+ return
209
+ end
210
+ end
211
+
200
212
  span = Span.new(
201
213
  name: name,
202
214
  subtype: subtype,
@@ -207,7 +219,8 @@ module Atatus
207
219
  type: type,
208
220
  context: context,
209
221
  stacktrace_builder: stacktrace_builder,
210
- sync: sync
222
+ sync: sync,
223
+ exit_span: exit_span
211
224
  )
212
225
 
213
226
  if backtrace && transaction.span_frames_min_duration
@@ -222,8 +235,14 @@ module Atatus
222
235
  # rubocop:enable Metrics/CyclomaticComplexity
223
236
  # rubocop:enable Metrics/PerceivedComplexity
224
237
 
225
- def end_span
226
- return unless (span = current_spans.pop)
238
+ def end_span(span = nil)
239
+ if span
240
+ current_spans.delete(span)
241
+ else
242
+ span = current_spans.pop
243
+ end
244
+
245
+ return unless span
227
246
 
228
247
  span.done
229
248
 
@@ -239,7 +258,7 @@ module Atatus
239
258
  def set_label(key, value)
240
259
  return unless current_transaction
241
260
 
242
- key = key.to_s.gsub(/[\."\*]/, '_').to_sym
261
+ key = key.to_s.gsub(/[."*]/, '_').to_sym
243
262
  current_transaction.context.labels[key] = value
244
263
  end
245
264
 
@@ -261,7 +280,7 @@ module Atatus
261
280
  def set_response_body(response_body)
262
281
  return unless current_transaction
263
282
  current_transaction.set_response_body(response_body)
264
- end
283
+ end
265
284
 
266
285
  def inspect
267
286
  '<Atatus::Instrumenter ' \
@@ -283,24 +302,9 @@ module Atatus
283
302
  'transaction.type': transaction.type
284
303
  }
285
304
 
286
- @metrics.get(:transaction).timer(
287
- :'transaction.duration.sum.us',
288
- tags: tags, reset_on_collect: true
289
- ).update(transaction.duration)
290
-
291
- @metrics.get(:transaction).counter(
292
- :'transaction.duration.count',
293
- tags: tags, reset_on_collect: true
294
- ).inc!
295
-
296
305
  return unless transaction.sampled?
297
306
  return unless transaction.breakdown_metrics
298
307
 
299
- @metrics.get(:breakdown).counter(
300
- :'transaction.breakdown.count',
301
- tags: tags, reset_on_collect: true
302
- ).inc!
303
-
304
308
  span_tags = tags.merge('span.type': 'app')
305
309
 
306
310
  @metrics.get(:breakdown).timer(