atatus 2.0.3 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc239b97c79b5f08087cb18a01202d02a040380a57ab7cb4f324f788693459b6
4
- data.tar.gz: 30cb19013300718357c28e41e95306ced7fa80633904d4213216e54485e0618f
3
+ metadata.gz: e583b9f10ae2db24b5ffedfe79959daf38c6b1b947a0cb71ddcf2fb1005e6495
4
+ data.tar.gz: 5db30ee5dd57494608263eeb2b45943a81dbd5eb8bce84a8e19fecad49ba2650
5
5
  SHA512:
6
- metadata.gz: 39c3db15d4ee7f8f3a7d055977638c3c4a88b3073c3503a7194dd749574197166e65df68c525c45631e3514a59dbbba1cfa156b224cb4220226c691961a086b0
7
- data.tar.gz: 50f8cd73f38d8729d786c407a3be835b92fe44b7a721e6adf2a6053153ae00cbb48ec5650fb6313f7d1590e1fd6f0db12d52b8c58e535be117d99e8fc91101bb
6
+ metadata.gz: 2e88330ad4178a8b991792e2c518e2b4b4d9144508365953b8e2528fad479b01fe6d445487adf3d00c31315119ecab8a146b673145fd6cc76dda9b57ed145f03
7
+ data.tar.gz: f522a2e7f232b8892b7f8a4a07a36c39fa982a4cad992c3e4edd7ef7100b7a0df2309876670f4e5f17e391703170f4a9ddd9184447c8354de05bc5e1ebf5dca1
data/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 2.1.0 (Wed, 7 Jan 2026)
8
+
9
+ - Fixed host details fetching issue in docker.
10
+ - Added support for distributed tracing.
11
+ - Added support for server url.
12
+
13
+
7
14
  ## 2.0.3 (Wed, 16 Oct 2024)
8
15
 
9
16
  - Fixed Action Dispatch exception handler failure in Rails 7.1
@@ -6,6 +6,7 @@ require 'atatus/transaction'
6
6
  require 'atatus/collector/hist'
7
7
  require 'atatus/collector/layer'
8
8
  require 'atatus/collector/transport'
9
+ require 'atatus/transport/base'
9
10
 
10
11
  SpanTiming = Struct.new(:name, :type, :subtype, :start, :end, :duration, :id, :transaction_id)
11
12
 
@@ -27,6 +28,8 @@ module Atatus
27
28
  def initialize(config)
28
29
  # info 'Initializing Collector'
29
30
  @config = config
31
+ initialize_server_urls
32
+
30
33
  @spans = Hash.new {|h,k| h[k]=[]}
31
34
 
32
35
  @txns_lock = Mutex.new
@@ -38,6 +41,7 @@ module Atatus
38
41
 
39
42
  @errors_lock = Mutex.new
40
43
  @errors_aggs = []
44
+ @error_limit = 20
41
45
 
42
46
  @metrics_lock = Mutex.new
43
47
  @metrics_agg = []
@@ -48,9 +52,39 @@ module Atatus
48
52
 
49
53
  @hostinfo_response = {}
50
54
  @hostinfo_response["analytics"] = true
51
- @transport = Atatus::BaseTransport.new(config)
55
+ @transport = Atatus::BaseTransport.new(@config)
56
+ @dt_transport = Atatus::Transport::Base.new(@config)
52
57
  @collect_counter = 0
53
58
  @running = false
59
+
60
+ @transaction_ignore_urls = @config.transaction_ignore_urls.dup
61
+ end
62
+
63
+ def initialize_server_urls
64
+ url = nil
65
+
66
+ if @config.notify_host != "https://apm-rx.atatus.com"
67
+ url = @config.notify_host
68
+ end
69
+
70
+ if @config.server_url != ''
71
+ url = @config.server_url
72
+ end
73
+
74
+ if url
75
+ if @config.apm_server_url == "https://apm-rx.atatus.com"
76
+ @config.apm_server_url = url
77
+ end
78
+ if @config.analytics_server_url == "https://an-rx.atatus.com"
79
+ @config.analytics_server_url = url
80
+ end
81
+ if @config.traces_server_url == "https://dt-rx.atatus.com"
82
+ @config.traces_server_url = url
83
+ end
84
+ if @config.logs_server_url == "https://log-rx.atatus.com"
85
+ @config.logs_server_url = url
86
+ end
87
+ end
54
88
  end
55
89
 
56
90
  attr_reader :config
@@ -60,6 +94,7 @@ module Atatus
60
94
  end
61
95
 
62
96
  def start
97
+ @dt_transport.start
63
98
  debug '%s: Starting collector', pid_str
64
99
 
65
100
  ensure_worker_running
@@ -67,6 +102,7 @@ module Atatus
67
102
 
68
103
  def stop
69
104
  return unless @running
105
+ @dt_transport.stop
70
106
  @running = false
71
107
  if worker_active?
72
108
  debug '%s: Waiting for collector worker to exit', pid_str
@@ -81,6 +117,7 @@ module Atatus
81
117
  def handle_forking!
82
118
  stop
83
119
  start
120
+ @dt_transport.handle_forking!
84
121
  end
85
122
 
86
123
  def add_error(error)
@@ -93,7 +130,7 @@ module Atatus
93
130
  then
94
131
  if @hostinfo_response.key?("ignoreExceptionPatterns")
95
132
  @hostinfo_response['ignoreExceptionPatterns'].each do |k, v|
96
- if error.exception.type.match(k)
133
+ if k.match(error.exception.type)
97
134
  exception_values = v
98
135
  if exception_values.length == 0
99
136
  ignore_error = true
@@ -111,10 +148,10 @@ module Atatus
111
148
  end
112
149
  if ignore_error == false
113
150
  @errors_lock.synchronize do
114
- if @errors_aggs.length < 20
151
+ if @errors_aggs.length < @error_limit
115
152
  @errors_aggs.push(error)
116
153
  else
117
- i = rand(20)
154
+ i = rand(@error_limit)
118
155
  @errors_aggs[i] = error
119
156
  end
120
157
  end
@@ -249,6 +286,15 @@ module Atatus
249
286
  end
250
287
 
251
288
  def add_span(span)
289
+
290
+ if @hostinfo_response.key?("tracing")
291
+ if @hostinfo_response["tracing"] == true && @config.tracing == true
292
+ if @hostinfo_response["performance"] && @config.performance
293
+ @dt_transport.submit(span)
294
+ end
295
+ end
296
+ end
297
+
252
298
  ensure_worker_running
253
299
 
254
300
  if
@@ -265,6 +311,15 @@ module Atatus
265
311
  end
266
312
 
267
313
  def add_txn(txn)
314
+
315
+ if @hostinfo_response.key?("tracing")
316
+ if @hostinfo_response["tracing"] == true && @config.tracing == true
317
+ if @hostinfo_response["performance"] && @config.performance
318
+ @dt_transport.submit(txn)
319
+ end
320
+ end
321
+ end
322
+
268
323
  ensure_worker_running
269
324
 
270
325
  if
@@ -279,7 +334,7 @@ module Atatus
279
334
  ignore_txn = false
280
335
  if @hostinfo_response.key?("ignoreTxnNamePatterns")
281
336
  @hostinfo_response['ignoreTxnNamePatterns'].each do |k|
282
- if txn.name.match(k)
337
+ if k.match(txn.name)
283
338
  ignore_txn = true
284
339
  break
285
340
  end
@@ -433,7 +488,7 @@ module Atatus
433
488
  if status_code >= 400 && status_code != 404
434
489
  if @hostinfo_response.key?("ignoreHTTPFailurePatterns")
435
490
  @hostinfo_response['ignoreHTTPFailurePatterns'].each do |k, v|
436
- if txn.name.match(k)
491
+ if k.match(txn.name)
437
492
  status_code_array_s = v
438
493
  if status_code_array_s.length == 0
439
494
  ignore_status_code = true
@@ -490,8 +545,8 @@ module Atatus
490
545
 
491
546
  while @running
492
547
  start_time = (Time.now.to_f * 1000).to_i
493
- sleep(60)
494
548
  collect start_time
549
+ sleep(60)
495
550
  end
496
551
  end
497
552
  end
@@ -516,6 +571,10 @@ module Atatus
516
571
 
517
572
  if @collect_counter % 30 == 0
518
573
  @hostinfo_response = @transport.hostinfo(start_time)
574
+ @config.transaction_ignore_urls = @transaction_ignore_urls | @hostinfo_response["ignoreTxnUrlPatterns"]
575
+ if @hostinfo_response.key?("errorLimit")
576
+ @error_limit = @hostinfo_response["errorLimit"]
577
+ end
519
578
  @collect_counter = 0
520
579
  end
521
580
  @collect_counter += 1
@@ -131,14 +131,24 @@ module Atatus
131
131
  end
132
132
 
133
133
  def build_hostinfo_env_settings()
134
- {
134
+ settings = {
135
135
  agentVersion: VERSION,
136
- appName: @config.app_name,
137
- framework: @config.framework_name,
138
- frameworkVersion: @config.framework_version,
139
- logLevel: @config.log_level,
140
136
  ruby: RUBY_VERSION
141
137
  }
138
+
139
+ @config.options.each do |key, opt|
140
+ value = opt.value
141
+
142
+ next if value.nil? ||
143
+ value == "" ||
144
+ (value.respond_to?(:empty?) && value.empty?)
145
+
146
+ settings[key] = value
147
+ end
148
+
149
+ settings.delete(:license_key)
150
+ settings.delete(:sanitize_field_names)
151
+ settings
142
152
  end
143
153
 
144
154
  def build_hostinfo_gems()
@@ -0,0 +1,14 @@
1
+ require 'zlib'
2
+ require 'stringio'
3
+
4
+ module Atatus
5
+ class Compress
6
+ def compress(body)
7
+ sio = StringIO.new
8
+ gz = Zlib::GzipWriter.new(sio)
9
+ gz.write(body)
10
+ gz.close
11
+ sio.string
12
+ end
13
+ end
14
+ end
@@ -2,6 +2,8 @@ require 'json'
2
2
  require 'thread'
3
3
  require 'net/http'
4
4
  require 'atatus/collector/builder'
5
+ require 'atatus/config/wildcard_pattern_list'
6
+ require 'atatus/collector/compress_body'
5
7
 
6
8
  module Atatus
7
9
  class BaseTransport
@@ -19,25 +21,21 @@ module Atatus
19
21
  def initialize(config)
20
22
  @config = config
21
23
 
22
- @notify_host = @config.notify_host
23
- uri = URI(@notify_host)
24
- if not uri.kind_of?(URI::HTTPS) and not uri.kind_of?(URI::HTTP)
25
- @notify_host = "https://apm-rx.atatus.com"
26
- end
27
-
28
- @analytics_notify_host = @config.analytics_notify_host
29
- uri = URI(@analytics_notify_host)
30
- if not uri.kind_of?(URI::HTTPS) and not uri.kind_of?(URI::HTTP)
31
- @analytics_notify_host = "https://an-rx.atatus.com"
32
- end
24
+ @apm_server_url = @config.apm_server_url
25
+ @analytics_server_url = @config.analytics_server_url
33
26
 
34
27
  @builder = Atatus::Builder.new(config)
35
28
  @headers = {}
36
29
  @headers['Content-Type'] = "application/json"
30
+ @headers['Content-Encoding'] = "gzip"
37
31
 
38
32
  @blocked = false
39
33
  @capture_percentiles = false
40
34
  @hostinfo_response = {}
35
+ @wildcard_pattern_factory = Config::WildcardPatternList.new
36
+
37
+ @http_transport = Transport::Connection::Http.new(@config, headers: @headers)
38
+ @compressor = Compress.new
41
39
  end
42
40
 
43
41
  def hostinfo(start_time)
@@ -47,11 +45,15 @@ module Atatus
47
45
  end
48
46
 
49
47
  def txns(start_time, end_time, data)
48
+ return unless @hostinfo_response["performance"] && @config.performance
49
+
50
50
  payload = @builder.txns(start_time, end_time, data)
51
51
  post(TXN_ENDPOINT, payload)
52
52
  end
53
53
 
54
54
  def txn_hist(start_time, end_time, data)
55
+ return unless @hostinfo_response["performance"] && @config.performance
56
+
55
57
  if @capture_percentiles == true
56
58
  payload = @builder.txn_hist(start_time, end_time, data)
57
59
  post(TXN_HIST_ENDPOINT, payload)
@@ -59,11 +61,15 @@ module Atatus
59
61
  end
60
62
 
61
63
  def traces(start_time, end_time, data)
64
+ return unless @hostinfo_response["performance"] && @config.performance
65
+
62
66
  payload = @builder.traces(start_time, end_time, data)
63
67
  post(TRACE_ENDPOINT, payload)
64
68
  end
65
69
 
66
70
  def error_metrics(start_time, end_time, metrics_data, requests_data)
71
+ return unless @hostinfo_response["performance"] && @config.performance
72
+
67
73
  payload = @builder.error_metrics(start_time, end_time, metrics_data, requests_data)
68
74
  post(ERROR_METRIC_ENDPOINT, payload)
69
75
  end
@@ -74,6 +80,8 @@ module Atatus
74
80
  end
75
81
 
76
82
  def metrics(start_time, end_time, metric_data)
83
+ return unless @hostinfo_response["performance"] && @config.performance
84
+
77
85
  payload = @builder.metrics(start_time, end_time, metric_data)
78
86
  post(METRIC_ENDPOINT, payload)
79
87
  end
@@ -86,28 +94,30 @@ module Atatus
86
94
  private
87
95
 
88
96
  def post(endpoint, data)
89
- # puts ::JSON.pretty_generate(data, :max_nesting => false)
97
+
90
98
  if @blocked == true and endpoint != HOSTINFO_ENDPOINT
99
+ debug "Client blocked"
91
100
  return
92
101
  end
93
102
 
94
103
  begin
95
- notify_host = @notify_host
104
+ server_url = @apm_server_url
96
105
  if endpoint == ANALYTICS_ENDPOINT
97
- notify_host = @analytics_notify_host
106
+ server_url = @analytics_server_url
98
107
  end
99
108
 
100
- uri = URI(notify_host + endpoint)
109
+ uri = URI(server_url + endpoint)
101
110
  uri.query = URI.encode_www_form({"license_key": @config.license_key, "agent_name": AGENT_NAME, "agent_version": VERSION})
102
111
 
103
- request = Net::HTTP::Post.new(uri.request_uri, @headers)
104
- request.body = ::JSON.dump(data)
105
- http = Net::HTTP.new(uri.host, uri.port)
106
- http.use_ssl = true
107
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
108
- response = http.start { |http| http.request(request) }
112
+ verbose "Sending data to #{uri}"
113
+ verbose "Payload: #{::JSON.pretty_generate(data, :max_nesting => false)}"
114
+
115
+ compressed_body = @compressor.compress(::JSON.generate(data))
116
+ headers_new = @headers.merge('Content-Length' => compressed_body.bytesize.to_s)
117
+ response = @http_transport.post(uri, body: compressed_body, headers: headers_new)
118
+
109
119
  rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
110
- error format('Atatus transport [%s%s] failed with exception: %s', notify_host, endpoint, e.message)
120
+ error format('Atatus transport [%s%s] failed with exception: %s', server_url, endpoint, e.message)
111
121
  return
112
122
  end
113
123
 
@@ -115,8 +125,8 @@ module Atatus
115
125
  @blocked = false
116
126
  end
117
127
 
118
- case response
119
- when Net::HTTPSuccess
128
+ case response.status
129
+ when 200
120
130
  if endpoint == HOSTINFO_ENDPOINT
121
131
  @capture_percentiles = false
122
132
 
@@ -125,11 +135,16 @@ module Atatus
125
135
  end
126
136
 
127
137
  resp = JSON.parse response.body
138
+
128
139
  if resp
129
140
  if resp.key?("analytics")
130
141
  @hostinfo_response['analytics'] = resp["analytics"]
131
142
  end
132
143
 
144
+ if resp.key?("tracing")
145
+ @hostinfo_response['tracing'] = resp["tracing"]
146
+ end
147
+
133
148
  if resp.key?("capturePercentiles")
134
149
  @capture_percentiles = resp["capturePercentiles"]
135
150
  @hostinfo_response['capturePercentiles'] = @capture_percentiles
@@ -138,24 +153,43 @@ module Atatus
138
153
  if resp.key?("extRequestPatterns")
139
154
  @hostinfo_response['extRequestPatterns'] = resp["extRequestPatterns"]
140
155
  end
156
+
157
+ if resp.key?("ignoreTxnUrlPatterns")
158
+ @hostinfo_response['ignoreTxnUrlPatterns'] = resp["ignoreTxnUrlPatterns"]
159
+ else
160
+ @hostinfo_response['ignoreTxnUrlPatterns'] = []
161
+ end
141
162
 
142
163
  if resp.key?("ignoreTxnNamePatterns")
143
- @hostinfo_response['ignoreTxnNamePatterns'] = resp["ignoreTxnNamePatterns"]
164
+ @hostinfo_response['ignoreTxnNamePatterns'] = @wildcard_pattern_factory.call(resp["ignoreTxnNamePatterns"])
144
165
  end
145
166
 
146
167
  if resp.key?("ignoreHTTPFailurePatterns")
147
- @hostinfo_response['ignoreHTTPFailurePatterns'] = resp["ignoreHTTPFailurePatterns"]
168
+ @hostinfo_response['ignoreHTTPFailurePatterns'] = @wildcard_pattern_factory.call(resp["ignoreHTTPFailurePatterns"])
148
169
  end
149
170
 
150
171
  if resp.key?("ignoreExceptionPatterns")
151
- @hostinfo_response['ignoreExceptionPatterns'] = resp["ignoreExceptionPatterns"]
172
+ @hostinfo_response['ignoreExceptionPatterns'] = @wildcard_pattern_factory.call(resp["ignoreExceptionPatterns"])
152
173
  end
153
174
 
175
+ if resp.key?("performance")
176
+ @hostinfo_response['performance'] = resp["performance"]
177
+ else
178
+ @hostinfo_response['performance'] = true
179
+ end
180
+
181
+ if resp.key?("errorLimit") and resp["errorLimit"].is_a?(Integer)
182
+ if resp["errorLimit"] < 20
183
+ @hostinfo_response['errorLimit'] = 20
184
+ else
185
+ @hostinfo_response['errorLimit'] = resp["errorLimit"]
186
+ end
187
+ end
154
188
  end
155
189
  else
156
190
  true
157
191
  end
158
- when Net::HTTPBadRequest
192
+ when 400..499
159
193
  if not response.body
160
194
  error format('Transport status 400, failed without content')
161
195
  return
@@ -0,0 +1,17 @@
1
+ module Atatus
2
+ class Config
3
+ class ErrorLimitConverter
4
+ def call(value)
5
+ value = value.to_i
6
+
7
+ if value < 20
8
+ return 20
9
+ elsif value > 200
10
+ return 200
11
+ else
12
+ return value
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -22,6 +22,7 @@ module Atatus
22
22
  # @api private
23
23
  class LogLevelMap
24
24
  LEVELS = {
25
+ verbose: Logger::VERBOSE,
25
26
  debug: Logger::DEBUG,
26
27
  info: Logger::INFO,
27
28
  warn: Logger::WARN,
@@ -90,8 +90,17 @@ module Atatus
90
90
  end
91
91
 
92
92
  def normalize_url(val)
93
- val = val.to_s
94
- val.end_with?('/') ? val.chomp('/') : val
93
+ if key.to_s == "server_url" and val == ""
94
+ return ""
95
+ end
96
+
97
+ uri = URI(val)
98
+
99
+ if not uri.kind_of?(URI::HTTPS) and not uri.kind_of?(URI::HTTP)
100
+ raise InternalError, "Invalid URL for #{key}: #{val}"
101
+ end
102
+
103
+ uri.to_s.chomp('/')
95
104
  end
96
105
  end
97
106
 
@@ -33,6 +33,12 @@ module Atatus
33
33
  !!@pattern.match(other)
34
34
  end
35
35
 
36
+ def ==(other)
37
+ other.is_a?(WildcardPattern) && @pattern == other.pattern
38
+ end
39
+
40
+ alias eql? ==
41
+
36
42
  alias :match :match?
37
43
 
38
44
  private
@@ -55,11 +61,40 @@ module Atatus
55
61
  case_sensitive ? nil : Regexp::IGNORECASE
56
62
  )
57
63
  end
58
- end
64
+ end
59
65
 
60
66
  def call(value)
61
- value = value.is_a?(String) ? value.split(',') : Array(value)
62
- value.map { |p| WildcardPattern.new(p) }
67
+ hash = false
68
+ if value.is_a?(Hash)
69
+ hash = true
70
+ elsif value.is_a?(String)
71
+ value = value.split(',')
72
+ else
73
+ value = Array(value)
74
+ end
75
+
76
+ if !hash
77
+ value = value.map do |p|
78
+ if p.is_a?(WildcardPattern)
79
+ p
80
+ else
81
+ begin
82
+ WildcardPattern.new(p)
83
+ rescue RegexpError
84
+ nil
85
+ end
86
+ end
87
+ end
88
+
89
+ else
90
+ new_hash = {}
91
+ value = value.map do |k, v|
92
+ new_hash[WildcardPattern.new(k)] = v
93
+ end
94
+ value = new_hash
95
+ end
96
+
97
+ value.uniq.compact
63
98
  end
64
99
  end
65
100
  end
data/lib/atatus/config.rb CHANGED
@@ -26,6 +26,7 @@ require 'atatus/config/regexp_list'
26
26
  require 'atatus/config/wildcard_pattern_list'
27
27
  require 'atatus/deprecations'
28
28
  require 'atatus/config/server_info'
29
+ require 'atatus/config/error_limit_converter'
29
30
 
30
31
  module Atatus
31
32
  # @api private
@@ -39,12 +40,17 @@ module Atatus
39
40
  # rubocop:disable Layout/LineLength, Layout/ExtraSpacing
40
41
  option :app_name, type: :string
41
42
  option :license_key, type: :string
42
- option :notify_host, type: :string, default: 'https://apm-rx.atatus.com'
43
- option :analytics_notify_host, type: :string, default: 'https://an-rx.atatus.com'
43
+ option :notify_host, type: :url, default: 'https://apm-rx.atatus.com'
44
+ option :analytics_notify_host, type: :url, default: 'https://an-rx.atatus.com'
44
45
  option :trace_threshold, type: :int, default: 2000
45
46
  option :analytics, type: :bool, default: false
47
+ option :tracing, type: :bool, default: false
46
48
  option :config_file, type: :string, default: 'config/atatus.yml'
47
49
  option :server_url, type: :url, default: ''
50
+ option :apm_server_url, type: :url, default: 'https://apm-rx.atatus.com'
51
+ option :analytics_server_url, type: :url, default: 'https://an-rx.atatus.com'
52
+ option :traces_server_url, type: :url, default: 'https://dt-rx.atatus.com'
53
+ option :logs_server_url, type: :url, default: 'https://log-rx.atatus.com'
48
54
  option :secret_token, type: :string
49
55
  option :api_key, type: :string
50
56
 
@@ -105,8 +111,11 @@ module Atatus
105
111
  option :transaction_ignore_urls, type: :list, default: [], converter: WildcardPatternList.new
106
112
  option :transaction_max_spans, type: :int, default: 500
107
113
  option :transaction_sample_rate, type: :float, default: 1.0, converter: RoundFloat.new
108
- option :use_atatus_traceparent_header, type: :bool, default: true
114
+ option :use_atatus_traceparent_header, type: :bool, default: true
109
115
  option :verify_server_cert, type: :bool, default: true
116
+ # option :error_limit, type: :int, default: 20, converter: ErrorLimitConverter.new
117
+ option :performance, type: :bool, default: true
118
+
110
119
 
111
120
  def log_ecs_formatting
112
121
  log_ecs_reformatting
@@ -14,6 +14,25 @@
14
14
  # KIND, either express or implied. See the License for the
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
+ require 'logger'
18
+
19
+ class Logger
20
+ module Severity
21
+ VERBOSE = -1
22
+ end
23
+
24
+ def format_severity(severity)
25
+ if severity == Severity::VERBOSE
26
+ 'VERBOSE'
27
+ else
28
+ SEV_LABEL[severity] || 'ANY'
29
+ end
30
+ end
31
+
32
+ def verbose(progname = nil, &block)
33
+ add(Severity::VERBOSE, nil, progname, &block)
34
+ end
35
+ end
17
36
 
18
37
  # frozen_string_literal: true
19
38
 
@@ -23,6 +42,7 @@ module Atatus
23
42
  PREFIX = '[Atatus] '
24
43
 
25
44
  LEVELS = {
45
+ verbose: Logger::VERBOSE,
26
46
  debug: Logger::DEBUG,
27
47
  info: Logger::INFO,
28
48
  warn: Logger::WARN,
@@ -30,6 +50,10 @@ module Atatus
30
50
  fatal: Logger::FATAL
31
51
  }.freeze
32
52
 
53
+ def verbose(msg, *args, &block)
54
+ log(:verbose, msg, *args, &block)
55
+ end
56
+
33
57
  def debug(msg, *args, &block)
34
58
  log(:debug, msg, *args, &block)
35
59
  end
@@ -55,8 +79,12 @@ module Atatus
55
79
  def log(lvl, msg, *args)
56
80
  return unless (logger = @config&.logger)
57
81
  return unless LEVELS[lvl] >= (@config&.log_level || 0)
58
-
59
- formatted_msg = prepend_prefix(format(msg.to_s, *args))
82
+
83
+ if not args.length == 0
84
+ formatted_msg = prepend_prefix(format(msg.to_s, *args))
85
+ else
86
+ formatted_msg = prepend_prefix(msg.to_s)
87
+ end
60
88
 
61
89
  return logger.send(lvl, formatted_msg) unless block_given?
62
90
 
@@ -23,6 +23,7 @@ module Atatus
23
23
  # @api private
24
24
  class ContainerInfo
25
25
  CGROUP_PATH = '/proc/self/cgroup'
26
+ MOUNTINFO_PATH = '/proc/self/mountinfo'
26
27
 
27
28
  attr_accessor :container_id, :kubernetes_namespace,
28
29
  :kubernetes_node_name, :kubernetes_pod_name, :kubernetes_pod_uid
@@ -34,7 +35,9 @@ module Atatus
34
35
  attr_reader :cgroup_path
35
36
 
36
37
  def read!(hostname)
37
- read_from_cgroup!
38
+ read_from_mountinfo!
39
+ read_from_cgroup! unless container_id && kubernetes_pod_uid
40
+
38
41
  self.kubernetes_pod_name = hostname if kubernetes_pod_uid
39
42
  read_from_env!
40
43
  self
@@ -98,8 +101,37 @@ module Atatus
98
101
  }x
99
102
  ].freeze
100
103
  SYSTEMD_SCOPE_SUFFIX = '.scope'
104
+
105
+ CONTAINER_ID_V2_REGEX = %r{
106
+ ([[:xdigit:]]{64}|
107
+ [[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,})
108
+ }x.freeze
109
+
110
+ POD_UID_V2_REGEX = %r{/kubelet/pods/([[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,})}.freeze
111
+
101
112
  # rubocop:enable Style/RegexpLiteral
102
113
 
114
+ def read_from_mountinfo!
115
+ return unless File.exist?(MOUNTINFO_PATH)
116
+
117
+ IO.readlines(MOUNTINFO_PATH).each do |line|
118
+ if line.include?('/etc/hostname')
119
+ parts = line.split(' ')
120
+ if parts.length > 3
121
+ if (match = CONTAINER_ID_V2_REGEX.match(parts[3]))
122
+ self.container_id = match[1]
123
+ end
124
+ end
125
+ end
126
+
127
+ if (match = POD_UID_V2_REGEX.match(line))
128
+ self.kubernetes_pod_uid = match[1] if match.size > 1
129
+ end
130
+
131
+ break if container_id && kubernetes_pod_uid
132
+ end
133
+ end
134
+
103
135
  # rubocop:disable Metrics/PerceivedComplexity
104
136
  # rubocop:disable Metrics/CyclomaticComplexity
105
137
  def read_from_cgroup!
@@ -81,7 +81,7 @@ module Atatus
81
81
  return unless File.exist?(LINUX_CPUINFO_PATH)
82
82
  cpuinfo = File.read(LINUX_CPUINFO_PATH)
83
83
  self.cpuinfo_cores = cpuinfo.scan(PROCESSOR_COUNT_REGEX).size
84
- self.cpuinfo_model = cpuinfo.scan(MODEL_NAME_REGEX).flatten.first.strip
84
+ self.cpuinfo_model = cpuinfo.scan(MODEL_NAME_REGEX).flatten.first&.strip
85
85
  self.cpuinfo_mhz = cpuinfo.scan(CPU_MHZ_REGEX).flatten.first
86
86
  end
87
87
  # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
@@ -116,8 +116,8 @@ module Atatus
116
116
  begin
117
117
  resp = post(url, body: @rd, headers: @headers.chunked.to_h)
118
118
 
119
- if resp&.status == 202
120
- debug 'APM Server responded with status 202'
119
+ if resp&.status == 200
120
+ debug 'APM Server responded with status 200'
121
121
  elsif resp
122
122
  error "APM Server responded with an error:\n%p", resp.body.to_s
123
123
  end
@@ -42,7 +42,12 @@ module Atatus
42
42
  Metadata.new(config)
43
43
  )
44
44
  )
45
- @url = "#{config.server_url}/intake/v2/events"
45
+
46
+ uri = URI(@config.traces_server_url + "/track/traces/spans")
47
+ uri.query = URI.encode_www_form({"license_key": @config.license_key, "agent_name": AGENT_NAME, "agent_version": VERSION, "app_name": @config.app_name})
48
+
49
+ @url = uri
50
+
46
51
  @mutex = Mutex.new
47
52
  end
48
53
 
@@ -30,14 +30,16 @@ module Atatus
30
30
 
31
31
  attr_reader :context_serializer
32
32
 
33
- def build(span)
33
+ def build(span)
34
34
  {
35
35
  span: {
36
36
  id: span.id,
37
37
  transaction_id: span.transaction.id,
38
38
  parent_id: span.parent_id,
39
39
  name: keyword_field(span.name),
40
- type: join_type(span),
40
+ type: keyword_field(span.type),
41
+ subtype: keyword_field(span.subtype),
42
+ action: keyword_field(span.action),
41
43
  duration: ms(span.duration),
42
44
  context: context_serializer.build(span.context),
43
45
  stacktrace: span.stacktrace.to_a,
@@ -19,5 +19,5 @@
19
19
 
20
20
  module Atatus
21
21
  AGENT_NAME = 'Ruby'
22
- VERSION = '2.0.3'
22
+ VERSION = '2.1.0'
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atatus
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atatus
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-16 00:00:00.000000000 Z
11
+ date: 2026-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -79,12 +79,14 @@ files:
79
79
  - lib/atatus/child_durations.rb
80
80
  - lib/atatus/collector/base.rb
81
81
  - lib/atatus/collector/builder.rb
82
+ - lib/atatus/collector/compress_body.rb
82
83
  - lib/atatus/collector/hist.rb
83
84
  - lib/atatus/collector/layer.rb
84
85
  - lib/atatus/collector/transport.rb
85
86
  - lib/atatus/config.rb
86
87
  - lib/atatus/config/bytes.rb
87
88
  - lib/atatus/config/duration.rb
89
+ - lib/atatus/config/error_limit_converter.rb
88
90
  - lib/atatus/config/log_level_map.rb
89
91
  - lib/atatus/config/options.rb
90
92
  - lib/atatus/config/regexp_list.rb