atatus 1.6.2 → 1.7.0

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