atatus 1.6.2 → 1.7.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: 48a2777c09d7af196314fb322c9a55feadb46f50d90ffd88976ff978eb5039a2
4
- data.tar.gz: 59f1f63ebd4ad84f23359cc2c96ccb62e0c0eabceaa6b48bf63c711c3383bf64
3
+ metadata.gz: 3b14ce033d2727c9e1773987897f569026bfd7bb45ff3387ab7bed2d8e5dc912
4
+ data.tar.gz: 7d006a8ccc31f1cbb59d700eae5d7d83760c174e3dd8b0be7fc97e328feff8f0
5
5
  SHA512:
6
- metadata.gz: 1a705d56af3228c31ce400a16eae3d961fe2bbcae949db3effc4828682563bd4ce3070adb14613cb096caa48d71e8ccce2969dc21f4b0c910c73ca99b28bf566
7
- data.tar.gz: 30461593c406cf6f7b8f2e08c9bef4aa64b9f60f0f4c2b3f0bff9de1cff8a26f5cf25e07507b263e8638a37345916f15d479eb11bcb3e58f69a7f34d79d51d70
6
+ metadata.gz: b8ee79aac253e143ea0867530643b800a226277dd644ca514a2aa07fbc656065f2a316eb3da46a5630e90ae2bb3041f984331453f475a6fa1a5afcccb349f1df
7
+ data.tar.gz: df8621294a91c15d6afec8ac6fc76aba8f40376deb989b213b7a6cc5c6d8e6b91fb40c517fcb227f8ddfb0cd1c5dfc1e6f14908aa655d7491df0f41a5fa17eea
data/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ 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
+ ## 1.7.0 (Fri, 15 Jul 2022)
8
+
9
+ - Added support for analytics.
10
+ - Fixed sequel instrumentation.
11
+
12
+
7
13
  ## 1.6.2 (Mon, 31 Jan 2022)
8
14
 
9
15
  - Fixed config environment.
data/lib/atatus/agent.rb CHANGED
@@ -247,6 +247,14 @@ module Atatus
247
247
  instrumenter.set_user(user)
248
248
  end
249
249
 
250
+ def set_company(company)
251
+ instrumenter.set_company(company)
252
+ end
253
+
254
+ def set_response_body(response_body)
255
+ instrumenter.set_response_body(response_body)
256
+ end
257
+
250
258
  def build_context(rack_env:, for_type:)
251
259
  @context_builder.build(rack_env: rack_env, for_type: for_type)
252
260
  end
@@ -42,7 +42,12 @@ module Atatus
42
42
  @metrics_lock = Mutex.new
43
43
  @metrics_agg = []
44
44
 
45
+ @analytics = @config.analytics
46
+ @analytics_lock = Mutex.new
47
+ @analytics_agg = []
48
+
45
49
  @hostinfo_response = {}
50
+ @hostinfo_response["analytics"] = true
46
51
  @transport = Atatus::BaseTransport.new(config)
47
52
  @collect_counter = 0
48
53
  @running = false
@@ -154,6 +159,95 @@ module Atatus
154
159
  end
155
160
  end
156
161
 
162
+ def add_analytics(txn)
163
+ analytics_txn = {}
164
+ analytics_txn[:timestamp] = Util.ms(txn.timestamp).to_i
165
+ analytics_txn[:txnId] = ""
166
+ analytics_txn[:txnId] = txn.id if !txn.id.nil?
167
+ analytics_txn[:traceId] = ""
168
+ analytics_txn[:traceId] = txn.trace_id if !txn.trace_id.nil?
169
+ analytics_txn[:name] = txn.name
170
+ analytics_txn[:duration] = Util.ms(txn.duration)
171
+
172
+ if !txn.context.nil?
173
+ if !txn.context.request.nil?
174
+ r = txn.context.request
175
+
176
+ analytics_txn[:method] = r.method if !r.method.nil?
177
+
178
+ if !r.headers.nil?
179
+ analytics_txn[:requestHeaders] = r.headers
180
+ analytics_txn[:userAgent] = r.headers['User-Agent'] if r.headers.key?('User-Agent')
181
+ end
182
+
183
+ if !r.body.nil?
184
+ if !r.body.empty?
185
+ if r.body != "[SKIPPED]"
186
+ analytics_txn[:requestBody] = r.body.to_json
187
+ end
188
+ end
189
+ end
190
+
191
+ if !r.url.nil?
192
+ analytics_txn[:url] = r.url.full
193
+ end
194
+
195
+ if !r.socket.nil?
196
+ analytics_txn[:ip] = r.socket.remote_addr
197
+ end
198
+
199
+ end
200
+
201
+ if
202
+ defined?(txn.context.response_body) &&
203
+ !txn.context.response_body.nil?
204
+ then
205
+ analytics_txn[:responseBody] = txn.context.response_body
206
+ end
207
+
208
+ if !txn.context.response.nil?
209
+ if !txn.context.response.headers.nil?
210
+ analytics_txn[:responseHeaders] = txn.context.response.headers
211
+ end
212
+ if !txn.context.response.status_code.nil?
213
+ analytics_txn[:statusCode] = txn.context.response.status_code.to_i
214
+ end
215
+ end
216
+
217
+ if
218
+ defined?(txn.context.custom) &&
219
+ !txn.context.custom.nil? &&
220
+ !txn.context.custom.empty?
221
+ then
222
+ analytics_txn[:customData] = txn.context.custom
223
+ end
224
+
225
+ if
226
+ defined?(txn.context.user) &&
227
+ !txn.context.user.nil? &&
228
+ !txn.context.user.empty?
229
+ then
230
+ analytics_txn[:userId] = txn.context.user.id
231
+ analytics_txn[:userEmail] = txn.context.user.email
232
+ analytics_txn[:userName] = txn.context.user.username
233
+ end
234
+
235
+ if
236
+ defined?(txn.context.company) &&
237
+ !txn.context.company.nil? &&
238
+ !txn.context.company.empty?
239
+ then
240
+ analytics_txn[:companyId] = txn.context.company.id
241
+ end
242
+ end
243
+
244
+ @analytics_lock.synchronize do
245
+ if @analytics_agg.length < 10000
246
+ @analytics_agg << analytics_txn
247
+ end
248
+ end
249
+ end
250
+
157
251
  def add_span(span)
158
252
  ensure_worker_running
159
253
 
@@ -377,6 +471,13 @@ module Atatus
377
471
  end
378
472
  end
379
473
 
474
+ if @hostinfo_response.key?("analytics")
475
+ if @hostinfo_response["analytics"] == true && @analytics == true
476
+ if background == false
477
+ add_analytics txn
478
+ end
479
+ end
480
+ end
380
481
  end
381
482
  end
382
483
 
@@ -420,7 +521,6 @@ module Atatus
420
521
  @collect_counter += 1
421
522
 
422
523
  end_time = (Time.now.to_f * 1000).to_i
423
- debug '%s: data collector', pid_str
424
524
 
425
525
  txns_data = nil
426
526
  txn_hist_data = nil
@@ -429,6 +529,7 @@ module Atatus
429
529
  error_requests_data = nil
430
530
  errors_data = nil
431
531
  metrics_data = nil
532
+ analytics_data = nil
432
533
 
433
534
  @txns_lock.synchronize do
434
535
  txns_data = @txns_agg
@@ -447,6 +548,11 @@ module Atatus
447
548
  @error_requests_agg = []
448
549
  end
449
550
 
551
+ @analytics_lock.synchronize do
552
+ analytics_data = @analytics_agg
553
+ @analytics_agg = []
554
+ end
555
+
450
556
  @errors_lock.synchronize do
451
557
  errors_data = @errors_aggs
452
558
  @errors_aggs = []
@@ -473,6 +579,12 @@ module Atatus
473
579
  @transport.errors(start_time, end_time, errors_data) unless errors_data.empty?
474
580
 
475
581
  @transport.metrics(start_time, end_time, metrics_data) unless metrics_data.empty?
582
+
583
+ if @hostinfo_response.key?("analytics")
584
+ if @hostinfo_response["analytics"] == true && @analytics == true
585
+ @transport.analytics(start_time, end_time, analytics_data) unless analytics_data.empty?
586
+ end
587
+ end
476
588
  end
477
589
  end
478
590
  end
@@ -92,6 +92,14 @@ module Atatus
92
92
  payload
93
93
  end
94
94
 
95
+ def analytics(start_time, end_time, analytics_data)
96
+ payload = common()
97
+ payload[:startTime] = start_time
98
+ payload[:endTime] = end_time
99
+ payload[:requests] = analytics_data
100
+ payload
101
+ end
102
+
95
103
  private
96
104
 
97
105
  def keyword_field(value)
@@ -14,6 +14,7 @@ module Atatus
14
14
  ERROR_ENDPOINT = "/track/apm/error".freeze
15
15
  ERROR_METRIC_ENDPOINT = "/track/apm/error_metric".freeze
16
16
  METRIC_ENDPOINT = "/track/apm/metric".freeze
17
+ ANALYTICS_ENDPOINT = "/track/apm/analytics/txn".freeze
17
18
 
18
19
  def initialize(config)
19
20
  @config = config
@@ -24,6 +25,12 @@ module Atatus
24
25
  @notify_host = "https://apm-rx.atatus.com"
25
26
  end
26
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
33
+
27
34
  @builder = Atatus::Builder.new(config)
28
35
  @headers = {}
29
36
  @headers['Content-Type'] = "application/json"
@@ -71,6 +78,11 @@ module Atatus
71
78
  post(METRIC_ENDPOINT, payload)
72
79
  end
73
80
 
81
+ def analytics(start_time, end_time, analytics_data)
82
+ payload = @builder.analytics(start_time, end_time, analytics_data)
83
+ post(ANALYTICS_ENDPOINT, payload)
84
+ end
85
+
74
86
  private
75
87
 
76
88
  def post(endpoint, data)
@@ -80,7 +92,12 @@ module Atatus
80
92
  end
81
93
 
82
94
  begin
83
- uri = URI(@notify_host + endpoint)
95
+ notify_host = @notify_host
96
+ if endpoint == ANALYTICS_ENDPOINT
97
+ notify_host = @analytics_notify_host
98
+ end
99
+
100
+ uri = URI(notify_host + endpoint)
84
101
  uri.query = URI.encode_www_form({"license_key": @config.license_key, "agent_name": AGENT_NAME, "agent_version": VERSION})
85
102
 
86
103
  request = Net::HTTP::Post.new(uri.request_uri, @headers)
@@ -90,7 +107,7 @@ module Atatus
90
107
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
91
108
  response = http.start { |http| http.request(request) }
92
109
  rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
93
- error format('Atatus transport [%s%s] failed with exception: %s', @notify_host, endpoint, e.message)
110
+ error format('Atatus transport [%s%s] failed with exception: %s', notify_host, endpoint, e.message)
94
111
  return
95
112
  end
96
113
 
@@ -109,6 +126,10 @@ module Atatus
109
126
 
110
127
  resp = JSON.parse response.body
111
128
  if resp
129
+ if resp.key?("analytics")
130
+ @hostinfo_response['analytics'] = resp["analytics"]
131
+ end
132
+
112
133
  if resp.key?("capturePercentiles")
113
134
  @capture_percentiles = resp["capturePercentiles"]
114
135
  @hostinfo_response['capturePercentiles'] = @capture_percentiles
data/lib/atatus/config.rb CHANGED
@@ -34,7 +34,9 @@ module Atatus
34
34
  option :app_name, type: :string
35
35
  option :license_key, type: :string
36
36
  option :notify_host, type: :string, default: 'https://apm-rx.atatus.com'
37
+ option :analytics_notify_host, type: :string, default: 'https://an-rx.atatus.com'
37
38
  option :trace_threshold, type: :int, default: 2000
39
+ option :analytics, type: :bool, default: false
38
40
  option :config_file, type: :string, default: 'config/atatus.yml'
39
41
  option :server_url, type: :url, default: ''
40
42
  option :secret_token, type: :string
@@ -52,6 +54,7 @@ module Atatus
52
54
  option :current_user_email_method, type: :string, default: 'email'
53
55
  option :current_user_id_method, type: :string, default: 'id'
54
56
  option :current_user_username_method, type: :string, default: 'username'
57
+ option :current_company_id_method, type: :string, default: 'id'
55
58
  option :custom_key_filters, type: :list, default: [], converter: RegexpList.new
56
59
  option :default_labels, type: :dict, default: {}
57
60
  option :disable_metrics, type: :list, default: [], converter: WildcardPatternList.new
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Context
5
+ # @api private
6
+ class Company
7
+ def initialize(id: nil)
8
+ @id = id
9
+ end
10
+
11
+ def self.infer(config, record)
12
+ return unless record
13
+
14
+ new(
15
+ id: safe_get(record, config.current_company_id_method)&.to_s
16
+ )
17
+ end
18
+
19
+ attr_accessor :id
20
+
21
+ def empty?
22
+ !id
23
+ end
24
+
25
+ def any?
26
+ !empty?
27
+ end
28
+
29
+ class << self
30
+ private
31
+
32
+ def safe_get(record, method_name)
33
+ record.respond_to?(method_name) ? record.send(method_name) : nil
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -22,6 +22,7 @@ require 'atatus/context/request/socket'
22
22
  require 'atatus/context/request/url'
23
23
  require 'atatus/context/response'
24
24
  require 'atatus/context/user'
25
+ require 'atatus/context/company'
25
26
 
26
27
  module Atatus
27
28
  # @api private
@@ -42,6 +43,8 @@ module Atatus
42
43
  attr_reader :custom
43
44
  attr_reader :labels
44
45
  attr_reader :service
46
+ attr_accessor :company
47
+ attr_accessor :response_body
45
48
 
46
49
  # rubocop:disable Metrics/CyclomaticComplexity
47
50
  def empty?
@@ -253,6 +253,16 @@ module Atatus
253
253
  current_transaction.set_user(user)
254
254
  end
255
255
 
256
+ def set_company(company)
257
+ return unless current_transaction
258
+ current_transaction.set_company(company)
259
+ end
260
+
261
+ def set_response_body(response_body)
262
+ return unless current_transaction
263
+ current_transaction.set_response_body(response_body)
264
+ end
265
+
256
266
  def inspect
257
267
  '<Atatus::Instrumenter ' \
258
268
  "current_transaction=#{current_transaction.inspect}" \
@@ -66,7 +66,6 @@ module Atatus
66
66
  timeout_interval: TIMEOUT_INTERVAL
67
67
  ) do
68
68
  begin
69
- debug 'Collecting metrics'
70
69
  collect_and_send
71
70
  true
72
71
  rescue StandardError => e
@@ -28,59 +28,60 @@ module Atatus
28
28
  ACTION = 'query'
29
29
 
30
30
  def self.summarizer
31
- @summarizer ||= Sql.summarizer
31
+ @summarizer ||= Sql::Signature::Summarizer.new
32
32
  end
33
33
 
34
- def install
35
- require 'sequel/database/logging'
36
-
37
- if defined?(::Sequel) && defined?(::Sequel::Database)
38
-
39
- ::Sequel::Database.class_eval do
40
- alias log_connection_yield_without_apm log_connection_yield
41
-
42
- def log_connection_yield(sql, connection, args = nil, &block)
43
- unless Atatus.current_transaction
44
- return log_connection_yield_without_apm(
45
- sql, connection, args, &block
46
- )
47
- end
34
+ # @api private
35
+ module Ext
36
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
37
+ def log_connection_yield(sql, connection, args = nil, &block)
38
+ unless Atatus.current_transaction
39
+ return super(sql, connection, args, &block)
40
+ end
48
41
 
49
- subtype = database_type.to_s
42
+ subtype = database_type.to_s
50
43
 
51
- name =
52
- Atatus::Spies::SequelSpy.summarizer.summarize sql
44
+ name =
45
+ Atatus::Spies::SequelSpy.summarizer.summarize sql
53
46
 
54
- context = Atatus::Span::Context.new(
55
- db: { statement: sql, type: 'sql', user: opts[:user] },
56
- destination: { name: subtype, resource: subtype, type: TYPE }
57
- )
47
+ context = Atatus::Span::Context.new(
48
+ db: { statement: sql, type: 'sql', user: opts[:user] },
49
+ destination: { service: { resource: subtype } }
50
+ )
58
51
 
59
- span = Atatus.start_span(
60
- name,
61
- TYPE,
62
- subtype: subtype,
63
- action: ACTION,
64
- context: context
65
- )
66
- yield.tap do |result|
67
- if name =~ /^(UPDATE|DELETE)/
68
- if connection.respond_to?(:changes)
69
- span.context.db.rows_affected = connection.changes
70
- elsif result.is_a?(Integer)
71
- span.context.db.rows_affected = result
72
- end
73
- end
52
+ span = Atatus.start_span(
53
+ name,
54
+ TYPE,
55
+ subtype: subtype,
56
+ action: ACTION,
57
+ context: context
58
+ )
59
+ super(sql, connection, args, &block).tap do |result|
60
+ if /^(UPDATE|DELETE)/.match?(name)
61
+ if connection.respond_to?(:changes)
62
+ span.context.db.rows_affected = connection.changes
63
+ elsif result.is_a?(Integer)
64
+ span.context.db.rows_affected = result
74
65
  end
75
- ensure
76
- Atatus.end_span
77
66
  end
78
67
  end
79
-
68
+ rescue
69
+ span&.outcome = Span::Outcome::FAILURE
70
+ raise
71
+ ensure
72
+ span&.outcome ||= Span::Outcome::SUCCESS
73
+ Atatus.end_span
80
74
  end
75
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
76
+ end
77
+
78
+ def install
79
+ require 'sequel/database/logging'
80
+
81
+ ::Sequel::Database.prepend(Ext)
81
82
  end
82
83
  end
83
84
 
84
85
  register 'Sequel', 'sequel', SequelSpy.new
85
86
  end
86
- end
87
+ end
@@ -148,6 +148,14 @@ module Atatus
148
148
  context.user = Context::User.infer(@config, user)
149
149
  end
150
150
 
151
+ def set_company(company)
152
+ context.company = Context::Company.infer(@config, company)
153
+ end
154
+
155
+ def set_response_body(response_body)
156
+ context.response_body = response_body
157
+ end
158
+
151
159
  def inspect
152
160
  "<Atatus::Transaction id:#{id}" \
153
161
  " name:#{name.inspect} type:#{type.inspect}>"
@@ -19,5 +19,5 @@
19
19
 
20
20
  module Atatus
21
21
  AGENT_NAME = 'Ruby'
22
- VERSION = '1.6.2'
22
+ VERSION = '1.7.0'
23
23
  end
data/lib/atatus.rb CHANGED
@@ -376,6 +376,22 @@ module Atatus
376
376
  agent&.set_user(user)
377
377
  end
378
378
 
379
+ # Provide a company to the current transaction
380
+ #
381
+ # @param company [Object] An object representing a company
382
+ # @return [Object] Given company
383
+ def set_company(company)
384
+ agent&.set_company(company)
385
+ end
386
+
387
+ # Provide the response body to the current transaction
388
+ #
389
+ # @param response_body [String] A string containing the response body
390
+ # @return [String] Given response body
391
+ def set_response_body(response_body)
392
+ agent&.set_response_body(response_body.to_s)
393
+ end
394
+
379
395
  # Provide a filter to transform payloads before sending them off
380
396
  #
381
397
  # @param key [Symbol] Unique filter key
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: 1.6.2
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atatus
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-31 00:00:00.000000000 Z
11
+ date: 2022-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -74,6 +74,7 @@ files:
74
74
  - lib/atatus/config/regexp_list.rb
75
75
  - lib/atatus/config/wildcard_pattern_list.rb
76
76
  - lib/atatus/context.rb
77
+ - lib/atatus/context/company.rb
77
78
  - lib/atatus/context/request.rb
78
79
  - lib/atatus/context/request/socket.rb
79
80
  - lib/atatus/context/request/url.rb