atatus 1.7.0 → 2.0.0

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