elastic-apm 0.5.1 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of elastic-apm might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2efd1832264da2eb5036fdbf6b3fcb26e47b0f3272779caed2d92c8315a0db7a
4
- data.tar.gz: c9f91f38ae3433566cdc16584ee862e7c8ac7f364c0fd05879c96ef87a6406d5
3
+ metadata.gz: da4eac00d32100e5f82cd8dfe3042278eb4f81865c797ee743b4e2fa89269c18
4
+ data.tar.gz: bd07824d56e89d645b08cfb2b5090a6ec4fcefbf360cb559420cd7f050629cd1
5
5
  SHA512:
6
- metadata.gz: 29c6d93909f2726527d09a62345ef920aba5b301a0fef1bd72e7739e2ace645afb22fd73039360e321e1d2cf68ac26745341deeada4f2e2aeb37e5f7bd3d70d3
7
- data.tar.gz: 48e84149066cb00fd66f2dc03d275626e7b4a9750b1cd97c6530f5cbf0c7d70f1fade887b228ad9d6e178f5e51c7225673e0f0a1a97a2ffe25dae9cab44721df
6
+ metadata.gz: 1b57fe86030e4f0aeb667cf886ac45708c4c41d26a0e85ed7736ee8c98319763cc15e0504bee2407f29a4503e98c32899061ad7a5b7fd035fcc5ed527cd7b410
7
+ data.tar.gz: bfc683d303d7b0a7c5400cd064c76f12dca648eeb6c6cecf73bc25cdb6bae4253e32e708aa57168aa9f0963e5531c7e4cdba10dd5f9d10782197c6bc3426e382
data/Gemfile CHANGED
@@ -40,7 +40,7 @@ else
40
40
  end
41
41
 
42
42
  # doesn't work with Rails master, so skip testing
43
- gem 'delayed_job' unless [framework, version] == %w[rails master]
43
+ gem 'delayed_job', '~> 4' unless [framework, version] == %w[rails 5.2]
44
44
 
45
45
  gem 'rails' if framework == 'sinatra'
46
46
  gem 'sinatra' if framework == 'rails'
@@ -7,7 +7,7 @@ require 'elastic_apm/error'
7
7
  require 'elastic_apm/http'
8
8
  require 'elastic_apm/injectors'
9
9
  require 'elastic_apm/serializers'
10
- require 'elastic_apm/worker'
10
+ require 'elastic_apm/timed_worker'
11
11
 
12
12
  module ElasticAPM
13
13
  # rubocop:disable Metrics/ClassLength
@@ -59,20 +59,17 @@ module ElasticAPM
59
59
  def initialize(config)
60
60
  @config = config
61
61
 
62
+ @messages = Queue.new
63
+ @pending_transactions = Queue.new
62
64
  @http = Http.new(config)
63
- @queue = Queue.new
64
65
 
65
66
  @instrumenter = Instrumenter.new(config, self)
66
67
  @context_builder = ContextBuilder.new(config)
67
68
  @error_builder = ErrorBuilder.new(config)
68
-
69
- @serializers = Struct.new(:transactions, :errors).new(
70
- Serializers::Transactions.new(config),
71
- Serializers::Errors.new(config)
72
- )
73
69
  end
74
70
 
75
- attr_reader :config, :queue, :instrumenter, :context_builder, :http
71
+ attr_reader :config, :messages, :pending_transactions, :instrumenter,
72
+ :context_builder, :http
76
73
 
77
74
  def start
78
75
  debug '[%s] Starting agent, reporting to %s', VERSION, config.server_url
@@ -98,20 +95,16 @@ module ElasticAPM
98
95
  stop
99
96
  end
100
97
 
101
- def enqueue_transactions(transactions)
98
+ def enqueue_transaction(transaction)
102
99
  boot_worker unless worker_running?
103
100
 
104
- data = @serializers.transactions.build_all(transactions)
105
- @queue << Worker::Request.new('/v1/transactions', data)
106
- transactions
101
+ pending_transactions.push(transaction)
107
102
  end
108
103
 
109
- def enqueue_errors(errors)
104
+ def enqueue_error(error)
110
105
  boot_worker unless worker_running?
111
106
 
112
- data = @serializers.errors.build_all(errors)
113
- @queue << Worker::Request.new('/v1/errors', data)
114
- errors
107
+ messages.push(TimedWorker::ErrorMsg.new(error))
115
108
  end
116
109
 
117
110
  # instrumentation
@@ -141,7 +134,7 @@ module ElasticAPM
141
134
  exception,
142
135
  handled: handled
143
136
  )
144
- enqueue_errors error
137
+ enqueue_error error
145
138
  end
146
139
 
147
140
  def report_message(message, backtrace: nil, **attrs)
@@ -150,7 +143,7 @@ module ElasticAPM
150
143
  backtrace: backtrace,
151
144
  **attrs
152
145
  )
153
- enqueue_errors error
146
+ enqueue_error error
154
147
  end
155
148
 
156
149
  # context
@@ -181,12 +174,17 @@ module ElasticAPM
181
174
  debug 'Booting worker'
182
175
 
183
176
  @worker_thread = Thread.new do
184
- Worker.new(@config, @queue, @http).run_forever
177
+ TimedWorker.new(
178
+ config,
179
+ messages,
180
+ pending_transactions,
181
+ http
182
+ ).run_forever
185
183
  end
186
184
  end
187
185
 
188
186
  def kill_worker
189
- @queue << Worker::StopMessage.new
187
+ messages << TimedWorker::StopMsg.new
190
188
 
191
189
  if @worker_thread && !@worker_thread.join(5) # 5 secs
192
190
  raise 'Failed to wait for worker, not all messages sent'
@@ -8,7 +8,6 @@ module ElasticAPM
8
8
  class Config
9
9
  DEFAULTS = {
10
10
  config_file: 'config/elastic_apm.yml',
11
- worker_process: false,
12
11
 
13
12
  server_url: 'http://localhost:8200',
14
13
  secret_token: nil,
@@ -31,18 +30,21 @@ module ElasticAPM
31
30
  transaction_max_spans: 500,
32
31
  filter_exception_types: [],
33
32
 
34
- http_timeout: 10,
35
- http_open_timeout: 10,
33
+ http_read_timeout: 120,
34
+ http_open_timeout: 60,
36
35
  debug_transactions: false,
37
36
  debug_http: false,
38
37
  verify_server_cert: true,
38
+ http_compression: true,
39
+ compression_minimum_size: 1024 * 5,
40
+ compression_level: 6,
39
41
 
40
42
  source_lines_error_app_frames: 5,
41
43
  source_lines_span_app_frames: 5,
42
44
  source_lines_error_library_frames: 0,
43
45
  source_lines_span_library_frames: 0,
44
46
 
45
- disabled_injectors: %w[],
47
+ disabled_injectors: %w[json],
46
48
 
47
49
  current_user_id_method: :id,
48
50
  current_user_email_method: :email,
@@ -94,7 +96,6 @@ module ElasticAPM
94
96
  end
95
97
 
96
98
  attr_accessor :config_file
97
- attr_accessor :worker_process
98
99
 
99
100
  attr_accessor :server_url
100
101
  attr_accessor :secret_token
@@ -117,10 +118,13 @@ module ElasticAPM
117
118
  attr_accessor :verify_server_cert
118
119
  attr_accessor :filter_exception_types
119
120
 
120
- attr_accessor :http_timeout
121
+ attr_accessor :http_read_timeout
121
122
  attr_accessor :http_open_timeout
122
123
  attr_accessor :debug_transactions
123
124
  attr_accessor :debug_http
125
+ attr_accessor :http_compression
126
+ attr_accessor :compression_minimum_size
127
+ attr_accessor :compression_level
124
128
 
125
129
  attr_accessor :source_lines_error_app_frames
126
130
  attr_accessor :source_lines_span_app_frames
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'net/http'
4
+ require 'zlib'
4
5
 
5
6
  require 'elastic_apm/service_info'
6
7
  require 'elastic_apm/system_info'
@@ -32,14 +33,15 @@ module ElasticAPM
32
33
  def post(path, payload = {})
33
34
  payload.merge! @base_payload
34
35
  filters.apply(payload)
36
+
35
37
  request = prepare_request path, payload.to_json
36
38
  response = @adapter.perform request
37
39
 
38
40
  status = response.code.to_i
39
41
  return response if status >= 200 && status <= 299
40
42
 
41
- error "POST returned an unsuccessful status code (#{response.code})"
42
- error response.body
43
+ error 'POST returned an unsuccessful status code (%d)', response.code
44
+ error "apm-server's response: %s", response.body
43
45
 
44
46
  response
45
47
  end
@@ -51,12 +53,25 @@ module ElasticAPM
51
53
  req['Accept'] = ACCEPT
52
54
  req['Content-Type'] = CONTENT_TYPE
53
55
  req['User-Agent'] = USER_AGENT
54
- req['Content-Length'] = data.bytesize.to_s
55
56
 
56
57
  if (token = @config.secret_token)
57
58
  req['Authorization'] = "Bearer #{token}"
58
59
  end
59
60
 
61
+ prepare_request_body! req, data
62
+ end
63
+ end
64
+
65
+ def prepare_request_body!(req, data)
66
+ if @config.http_compression &&
67
+ data.bytesize > @config.compression_minimum_size
68
+ deflated = Zlib.deflate data, @config.compression_level
69
+
70
+ req['Content-Encoding'] = 'deflate'
71
+ req['Content-Length'] = deflated.bytesize.to_s
72
+ req.body = deflated
73
+ else
74
+ req['Content-Length'] = data.bytesize.to_s
60
75
  req.body = data
61
76
  end
62
77
  end
@@ -92,7 +107,7 @@ module ElasticAPM
92
107
  http = Net::HTTP.new server_uri.host, server_uri.port
93
108
  http.use_ssl = @config.use_ssl?
94
109
  http.verify_mode = verify_mode
95
- http.read_timeout = @config.http_timeout
110
+ http.read_timeout = @config.http_read_timeout
96
111
  http.open_timeout = @config.http_open_timeout
97
112
 
98
113
  if @config.debug_http
@@ -5,27 +5,21 @@ module ElasticAPM
5
5
  module Injectors
6
6
  # @api private
7
7
  class RedisInjector
8
- # rubocop:disable Metrics/MethodLength
9
8
  def install
10
9
  ::Redis::Client.class_eval do
11
10
  alias call_without_apm call
12
11
 
13
12
  def call(command, &block)
14
13
  name = command[0].upcase
15
- statement =
16
- format('%s %s', name, command[1..command.length].join(' '))
17
- context = Span::Context.new(
18
- statement: statement,
19
- type: 'redis'
20
- )
21
14
 
22
- ElasticAPM.span(name.to_s, 'db.redis', context: context) do
15
+ return call_without_apm(command, &block) if command[0] == :auth
16
+
17
+ ElasticAPM.span(name.to_s, 'db.redis') do
23
18
  call_without_apm(command, &block)
24
19
  end
25
20
  end
26
21
  end
27
22
  end
28
- # rubocop:enable Metrics/MethodLength
29
23
  end
30
24
 
31
25
  register 'Redis', 'redis', RedisInjector.new
@@ -33,9 +33,6 @@ module ElasticAPM
33
33
  @transaction_info = TransactionInfo.new
34
34
 
35
35
  @subscriber = subscriber_class.new(config)
36
-
37
- @pending_transactions = []
38
- @last_sent_transactions = Time.now.utc
39
36
  end
40
37
 
41
38
  attr_reader :config, :pending_transactions
@@ -64,12 +61,10 @@ module ElasticAPM
64
61
  return transaction
65
62
  end
66
63
 
67
- sample = rand <= config.transaction_sample_rate
68
-
69
64
  if args.last.is_a? Hash
70
- args.last[:sampled] = sample
65
+ args.last[:sampled] = random_sample?
71
66
  else
72
- args.push(sampled: sample)
67
+ args.push(sampled: random_sample?)
73
68
  end
74
69
 
75
70
  transaction = Transaction.new self, *args
@@ -88,6 +83,10 @@ module ElasticAPM
88
83
  end
89
84
  # rubocop:enable Metrics/MethodLength
90
85
 
86
+ def random_sample?
87
+ rand <= config.transaction_sample_rate
88
+ end
89
+
91
90
  def span(*args, &block)
92
91
  unless current_transaction
93
92
  return yield if block_given?
@@ -113,36 +112,10 @@ module ElasticAPM
113
112
  end
114
113
 
115
114
  def submit_transaction(transaction)
116
- @pending_transactions << transaction
117
-
118
- if config.debug_transactions
119
- debug('Submitted transaction:') { Util.inspect_transaction transaction }
120
- end
121
-
122
- return unless should_flush_transactions?
123
- flush_transactions
124
- end
125
-
126
- def should_flush_transactions?
127
- interval = config.flush_interval
128
-
129
- return true if interval.nil?
130
- return true if @pending_transactions.length >= config.max_queue_size
131
-
132
- Time.now.utc - @last_sent_transactions >= interval
133
- end
134
-
135
- def flush_transactions
136
- return if @pending_transactions.empty?
137
-
138
- debug 'Sending %i transactions', @pending_transactions.length
139
-
140
- @agent.enqueue_transactions @pending_transactions
141
-
142
- @last_sent_transactions = Time.now.utc
143
- @pending_transactions = []
115
+ @agent.enqueue_transaction transaction
144
116
 
145
- true
117
+ return unless config.debug_transactions
118
+ debug('Submitted transaction:') { Util.inspect_transaction transaction }
146
119
  end
147
120
 
148
121
  def inspect
@@ -9,7 +9,7 @@ module ElasticAPM
9
9
  JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/
10
10
  RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/
11
11
 
12
- RUBY_VERS_REGEX = %r{ruby[-/](\d+\.)+\d}
12
+ RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}
13
13
  JRUBY_ORG_REGEX = %r{org/jruby}
14
14
 
15
15
  def initialize(backtrace)
@@ -61,7 +61,7 @@ module ElasticAPM
61
61
  return false unless abs_path
62
62
 
63
63
  if abs_path.start_with?(config.root_path)
64
- return true if abs_path.match(config.root_path + '/vendor')
64
+ return true if abs_path.start_with?(config.root_path + '/vendor')
65
65
  return false
66
66
  end
67
67
 
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class TimedWorker
6
+ include Log
7
+
8
+ SLEEP_INTERVAL = 0.1
9
+
10
+ # @api private
11
+ class StopMsg
12
+ end
13
+
14
+ # @api private
15
+ class ErrorMsg
16
+ def initialize(error)
17
+ @error = error
18
+ end
19
+
20
+ attr_reader :error
21
+ end
22
+
23
+ def initialize(config, messages, pending_transactions, adapter)
24
+ @config = config
25
+ @messages = messages
26
+ @pending_transactions = pending_transactions
27
+ @adapter = adapter
28
+
29
+ @last_sent_transactions = Time.now.utc
30
+
31
+ @serializers = Struct.new(:transactions, :errors).new(
32
+ Serializers::Transactions.new(config),
33
+ Serializers::Errors.new(config)
34
+ )
35
+ end
36
+
37
+ attr_reader :config, :messages, :pending_transactions
38
+
39
+ def run_forever
40
+ loop do
41
+ run_once
42
+ sleep SLEEP_INTERVAL
43
+ end
44
+ end
45
+
46
+ def run_once
47
+ collect_and_send_transactions if should_flush_transactions?
48
+ process_messages
49
+ end
50
+
51
+ private
52
+
53
+ # rubocop:disable Metrics/MethodLength
54
+ def process_messages
55
+ should_exit = false
56
+
57
+ while (msg = messages.pop(true))
58
+ case msg
59
+ when ErrorMsg
60
+ post_error msg
61
+ when StopMsg
62
+ should_exit = true
63
+
64
+ # empty collected transactions before exiting
65
+ collect_and_send_transactions
66
+ end
67
+ end
68
+ rescue ThreadError # queue empty
69
+ Thread.exit if should_exit
70
+ end
71
+ # rubocop:enable Metrics/MethodLength
72
+
73
+ def post_error(msg)
74
+ payload = @serializers.errors.build_all([msg.error])
75
+ @adapter.post('/v1/errors', payload)
76
+ end
77
+
78
+ def collect_and_send_transactions
79
+ return if pending_transactions.empty?
80
+
81
+ transactions = collect_batched_transactions
82
+
83
+ payload = @serializers.transactions.build_all(transactions)
84
+
85
+ begin
86
+ @adapter.post('/v1/transactions', payload)
87
+ rescue ::Exception => e
88
+ fatal 'Failed posting: %s', e.inspect
89
+ debug e.backtrace.join("\n")
90
+ nil
91
+ end
92
+ end
93
+
94
+ def collect_batched_transactions
95
+ batch = []
96
+
97
+ begin
98
+ while (transaction = pending_transactions.pop(true)) &&
99
+ batch.length <= config.max_queue_size
100
+ batch << transaction
101
+ end
102
+ rescue ThreadError # queue empty
103
+ end
104
+
105
+ batch
106
+ end
107
+
108
+ def should_flush_transactions?
109
+ interval = config.flush_interval
110
+
111
+ return true if interval.nil?
112
+ return true if pending_transactions.length >= config.max_queue_size
113
+
114
+ Time.now.utc - @last_sent_transactions >= interval
115
+ end
116
+ end
117
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- VERSION = '0.5.1'.freeze
4
+ VERSION = '0.6.1'.freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic-apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-26 00:00:00.000000000 Z
11
+ date: 2018-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -106,6 +106,7 @@ files:
106
106
  - lib/elastic_apm/stacktrace/line_cache.rb
107
107
  - lib/elastic_apm/subscriber.rb
108
108
  - lib/elastic_apm/system_info.rb
109
+ - lib/elastic_apm/timed_worker.rb
109
110
  - lib/elastic_apm/transaction.rb
110
111
  - lib/elastic_apm/util.rb
111
112
  - lib/elastic_apm/util/dig.rb
@@ -113,7 +114,6 @@ files:
113
114
  - lib/elastic_apm/util/inspector.rb
114
115
  - lib/elastic_apm/util/lru_cache.rb
115
116
  - lib/elastic_apm/version.rb
116
- - lib/elastic_apm/worker.rb
117
117
  - vendor/.gitkeep
118
118
  homepage: https://github.com/elastic/apm-agent-ruby
119
119
  licenses:
@@ -136,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
136
  version: '0'
137
137
  requirements: []
138
138
  rubyforge_project:
139
- rubygems_version: 2.7.3
139
+ rubygems_version: 2.7.6
140
140
  signing_key:
141
141
  specification_version: 4
142
142
  summary: The official Elastic APM agent for Ruby
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticAPM
4
- # @api private
5
- class Worker
6
- include Log
7
-
8
- # @api private
9
- class StopMessage; end
10
-
11
- # @api private
12
- Request = Struct.new(:path, :payload) do
13
- # require all params
14
- def initialize(path, payload)
15
- super
16
- end
17
- end
18
-
19
- def initialize(config, queue, adapter)
20
- @config = config
21
- @adapter = adapter
22
- @queue = queue
23
- end
24
-
25
- def run_forever
26
- loop do
27
- while (item = @queue.pop)
28
- case item
29
- when Request
30
- process item
31
- when StopMessage
32
- Thread.exit
33
- end
34
- end
35
- end
36
- end
37
-
38
- def process(item)
39
- @adapter.post(item.path, item.payload)
40
- rescue ::Exception => e
41
- fatal 'Failed posting: %s', e.inspect
42
- debug e.backtrace.join("\n")
43
- nil
44
- end
45
- end
46
- end