appsignal 0.9.6 → 0.10.0.beta.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
  SHA1:
3
- metadata.gz: 60fe669e8daa8b4015255cecaaa4f785b8fce679
4
- data.tar.gz: f04514a5ba7022f74f1c7657a06ae584f968fa03
3
+ metadata.gz: eaaf7d6c75bf0cacbef11b40c1931a2cca2c9121
4
+ data.tar.gz: 1b7b1151ffbde430bc17267d93c7272a224c105d
5
5
  SHA512:
6
- metadata.gz: 7b0c0d4c2557517439b529f95e9e71dec934ddd44a4f4b080516faad591efb5d4392816d2d839ac0a1309b236e21e51ad1552d6a1f56033440d8f912124d10dd
7
- data.tar.gz: 72cb94912a40af82c53e166600d238f197b1c8373060e93e1100d0a6aabf55dc122f9495dd0c7f2dfc27be49d102162dd9e90955698008ef047e0b32a1377d9d
6
+ metadata.gz: 050ba5e12ac07e9f160e6be7711395f18424e4ba7a9ebb4a28cff887db5483d3762a9e70dc693d3bd7be8501663024f5e1f7e01fb67caf338976300d44acd95b
7
+ data.tar.gz: 05cd41d9e8f821244902433f910be5c42be51174f3d7e42572d84524beefee4e9e2a40bf85a28d876bbe338c89daa0a87e809d18b44a6e17abff406c3585c2b4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
- # 0.9.6
2
- * Convert to primitives before sending through pipe
1
+ # 0.10.0
2
+ * Remove ActiveSupport dependency
3
+ * Use vendored notifications if ActiveSupport is not present
4
+ * Update bundled CA certificates
5
+ * Fix issue where backtrace can be nil
6
+ * Use Appsignal.monitor_transaction to instrument and log errors for
7
+ custom actions
8
+ * Add option to ignore a specific action
9
+ * Use a Mutex in agent instead of Thread.exclusive
3
10
 
4
11
  # 0.9.4
5
12
  * Log Rails and Sinatra version
@@ -59,7 +66,7 @@ Yanked
59
66
  * Alias tag_request to tag_job, for background jobs
60
67
  * Skip sanitization of env if env is nil
61
68
  * Small bugfix in forking logic
62
- * Don't send params if send_params is off in config
69
+ * Don't send params if send_params is off in config
63
70
  * Remove --repository option in CLI
64
71
  * Name option in appsignal notify_of_deploy CLI
65
72
  * Don't call to_hash on ENV
data/appsignal.gemspec CHANGED
@@ -24,7 +24,6 @@ Gem::Specification.new do |gem|
24
24
  gem.version = Appsignal::VERSION
25
25
  gem.required_ruby_version = '>= 1.9.3'
26
26
 
27
- gem.add_dependency 'activesupport', '>= 3.0'
28
27
  gem.add_dependency 'rack'
29
28
  gem.add_dependency 'thread_safe'
30
29
 
data/lib/appsignal.rb CHANGED
@@ -2,8 +2,12 @@ require 'logger'
2
2
  require 'rack'
3
3
  require 'thread_safe'
4
4
  require 'securerandom'
5
- require 'active_support/json'
6
- require 'active_support/notifications'
5
+
6
+ begin
7
+ require 'active_support/notifications'
8
+ rescue LoadError
9
+ require 'vendor/active_support/notifications'
10
+ end
7
11
 
8
12
  module Appsignal
9
13
  class << self
@@ -69,6 +73,25 @@ module Appsignal
69
73
  agent.enqueue(transaction)
70
74
  end
71
75
 
76
+ def monitor_transaction(name, payload={})
77
+ unless active?
78
+ yield
79
+ return
80
+ end
81
+
82
+ begin
83
+ Appsignal::Transaction.create(SecureRandom.uuid, ENV)
84
+ ActiveSupport::Notifications.instrument(name, payload) do
85
+ yield
86
+ end
87
+ rescue Exception => exception
88
+ Appsignal.add_exception(exception)
89
+ raise exception
90
+ ensure
91
+ Appsignal::Transaction.complete_current!
92
+ end
93
+ end
94
+
72
95
  def listen_for_exception(&block)
73
96
  yield
74
97
  rescue Exception => exception
@@ -129,10 +152,6 @@ module Appsignal
129
152
  @logger << @in_memory_log.string if @in_memory_log
130
153
  end
131
154
 
132
- def json
133
- ActiveSupport::JSON
134
- end
135
-
136
155
  def post_processing_middleware
137
156
  @post_processing_chain ||= Appsignal::Aggregator::PostProcessor.default_middleware
138
157
  yield @post_processing_chain if block_given?
@@ -147,6 +166,10 @@ module Appsignal
147
166
  Appsignal.config[:ignore_exceptions].include?(exception.class.name)
148
167
  end
149
168
 
169
+ def is_ignored_action?(action)
170
+ Appsignal.config[:ignore_actions].include?(action)
171
+ end
172
+
150
173
  # Convenience method for skipping instrumentations around a block of code.
151
174
  #
152
175
  # @since 0.8.7
@@ -2,8 +2,8 @@ module Appsignal
2
2
  class Agent
3
3
  ACTION = 'log_entries'.freeze
4
4
 
5
- attr_accessor :aggregator, :thread, :master_pid, :pid, :active, :sleep_time,
6
- :transmitter, :subscriber, :paused
5
+ attr_accessor :aggregator, :mutex, :thread, :master_pid, :pid, :active,
6
+ :sleep_time, :transmitter, :subscriber, :paused
7
7
 
8
8
  def initialize
9
9
  return unless Appsignal.active?
@@ -16,6 +16,7 @@ module Appsignal
16
16
  @pid = @master_pid
17
17
  @aggregator = Aggregator.new
18
18
  @transmitter = Transmitter.new(ACTION)
19
+ @mutex = Mutex.new
19
20
  subscribe
20
21
  start_thread
21
22
  Appsignal.logger.info('Started Appsignal agent')
@@ -81,6 +82,10 @@ module Appsignal
81
82
 
82
83
  def enqueue(transaction)
83
84
  forked! if @pid != Process.pid
85
+ if Appsignal.is_ignored_action?(transaction.action)
86
+ Appsignal.logger.debug("Ignoring transaction: #{transaction.request_id} (#{transaction.action})")
87
+ return
88
+ end
84
89
  aggregator.add(transaction)
85
90
  end
86
91
 
@@ -89,7 +94,7 @@ module Appsignal
89
94
  # Replace aggregator while making sure no thread
90
95
  # is adding to it's queue
91
96
  aggregator_to_be_sent = nil
92
- Thread.exclusive do
97
+ mutex.synchronize do
93
98
  aggregator_to_be_sent = aggregator
94
99
  @aggregator = Aggregator.new
95
100
  end
@@ -108,7 +113,7 @@ module Appsignal
108
113
  Appsignal.logger.debug('Clearing queue')
109
114
  # Replace aggregator while making sure no thread
110
115
  # is adding to it's queue
111
- Thread.exclusive do
116
+ mutex.synchronize do
112
117
  @aggregator = Aggregator.new
113
118
  end
114
119
  end
@@ -116,7 +121,7 @@ module Appsignal
116
121
  def forked!
117
122
  Appsignal.logger.info('Forked worker process')
118
123
  @pid = Process.pid
119
- Thread.exclusive do
124
+ mutex.synchronize do
120
125
  @aggregator = Aggregator.new
121
126
  end
122
127
  resubscribe
@@ -126,7 +131,7 @@ module Appsignal
126
131
  def shutdown(send_current_queue=false, reason=nil)
127
132
  Appsignal.logger.info("Shutting down agent (#{reason})")
128
133
  unsubscribe
129
- stop_thread
134
+ Thread.kill(thread) if thread
130
135
  send_queue if send_current_queue && @aggregator.has_transactions?
131
136
  end
132
137
 
data/lib/appsignal/cli.rb CHANGED
@@ -110,7 +110,8 @@ module Appsignal
110
110
 
111
111
  def validate_required_options(required_options)
112
112
  missing = required_options.select do |required_option|
113
- options[required_option].blank?
113
+ val = options[required_option]
114
+ val.nil? || (val.respond_to?(:empty?) && val.empty?)
114
115
  end
115
116
  if missing.any?
116
117
  puts "Missing options: #{missing.join(', ')}"
@@ -8,6 +8,7 @@ module Appsignal
8
8
 
9
9
  DEFAULT_CONFIG = {
10
10
  :ignore_exceptions => [],
11
+ :ignore_actions => [],
11
12
  :send_params => true,
12
13
  :endpoint => 'https://push.appsignal.com/1',
13
14
  :slow_request_threshold => 200,
@@ -11,25 +11,17 @@ if defined?(::Delayed::Plugin)
11
11
  end
12
12
 
13
13
  def self.invoke_with_instrumentation(job, block)
14
- begin
15
- Appsignal::Transaction.create(SecureRandom.uuid, ENV)
16
- class_name, method_name = job.name.split('#')
17
- ActiveSupport::Notifications.instrument(
18
- 'perform_job.delayed_job',
19
- :class => class_name,
20
- :method => method_name,
21
- :priority => job.priority,
22
- :attempts => job.attempts,
23
- :queue => job.queue,
24
- :queue_start => job.created_at
25
- ) do
26
- block.call(job)
27
- end
28
- rescue Exception => exception
29
- Appsignal.add_exception(exception)
30
- raise exception
31
- ensure
32
- Appsignal::Transaction.complete_current!
14
+ class_name, method_name = job.name.split('#')
15
+ Appsignal.monitor_transaction(
16
+ 'perform_job.delayed_job',
17
+ :class => class_name,
18
+ :method => method_name,
19
+ :priority => job.priority,
20
+ :attempts => job.attempts,
21
+ :queue => job.queue,
22
+ :queue_start => job.created_at
23
+ ) do
24
+ block.call(job)
33
25
  end
34
26
  end
35
27
  end
@@ -4,23 +4,15 @@ if defined?(::Resque)
4
4
  module Appsignal
5
5
  module Integrations
6
6
  module ResquePlugin
7
-
8
7
  def around_perform_resque_plugin(*args)
9
- Appsignal::Transaction.create(SecureRandom.uuid, ENV)
10
- ActiveSupport::Notifications.instrument(
8
+ Appsignal.monitor_transaction(
11
9
  'perform_job.resque',
12
10
  :class => self.to_s,
13
11
  :method => 'perform'
14
12
  ) do
15
13
  yield
16
14
  end
17
- rescue Exception => exception
18
- Appsignal.add_exception(exception)
19
- raise exception
20
- ensure
21
- Appsignal::Transaction.complete_current!
22
15
  end
23
-
24
16
  end
25
17
  end
26
18
  end
@@ -5,8 +5,7 @@ if defined?(::Sidekiq)
5
5
  module Integrations
6
6
  class SidekiqPlugin
7
7
  def call(worker, item, queue)
8
- Appsignal::Transaction.create(SecureRandom.uuid, ENV)
9
- ActiveSupport::Notifications.instrument(
8
+ Appsignal.monitor_transaction(
10
9
  'perform_job.sidekiq',
11
10
  :class => item['class'],
12
11
  :method => 'perform',
@@ -16,11 +15,6 @@ if defined?(::Sidekiq)
16
15
  ) do
17
16
  yield
18
17
  end
19
- rescue Exception => exception
20
- Appsignal.add_exception(exception)
21
- raise exception
22
- ensure
23
- Appsignal::Transaction.complete_current!
24
18
  end
25
19
  end
26
20
  end
@@ -85,7 +85,7 @@
85
85
  end
86
86
 
87
87
  def exception?
88
- !! exception
88
+ !!exception
89
89
  end
90
90
 
91
91
  def slow_request?
@@ -98,32 +98,20 @@
98
98
  end
99
99
 
100
100
  def truncate!
101
- return if truncated?
102
101
  process_action_event.payload.clear
103
102
  events.clear
104
103
  tags.clear
105
104
  sanitized_environment.clear
106
105
  sanitized_session_data.clear
107
106
  @env = nil
108
- @truncated = true
109
- end
110
-
111
- def truncated?
112
- !! @truncated
113
107
  end
114
108
 
115
109
  def convert_values_to_primitives!
116
- return if have_values_been_converted_to_primitives?
117
110
  Appsignal::Transaction::ParamsSanitizer.sanitize!(@process_action_event.payload) if @process_action_event
118
111
  @events.map do |o|
119
112
  Appsignal::Transaction::ParamsSanitizer.sanitize(o.payload)
120
113
  end
121
114
  add_sanitized_context!
122
- @have_values_been_converted_to_primitives = true
123
- end
124
-
125
- def have_values_been_converted_to_primitives?
126
- !! @have_values_been_converted_to_primitives
127
115
  end
128
116
 
129
117
  def type
@@ -141,7 +129,6 @@
141
129
  Appsignal.transactions.delete(@request_id)
142
130
  if process_action_event || exception?
143
131
  if Appsignal::Pipe.current
144
- convert_values_to_primitives!
145
132
  Appsignal.logger.debug("Writing transaction to pipe: #{@request_id}")
146
133
  Appsignal::Pipe.current.write(self)
147
134
  else
@@ -67,6 +67,7 @@ module Appsignal
67
67
  end
68
68
 
69
69
  def clean_backtrace(exception)
70
+ return [] unless exception.backtrace.is_a?(Array)
70
71
  if defined?(::Rails)
71
72
  ::Rails.backtrace_cleaner.clean(exception.backtrace, nil)
72
73
  else
@@ -2,6 +2,7 @@ require 'net/http'
2
2
  require 'net/https'
3
3
  require 'uri'
4
4
  require 'rack/utils'
5
+ require 'json'
5
6
 
6
7
  module Appsignal
7
8
  class Transmitter
@@ -40,7 +41,7 @@ module Appsignal
40
41
  request['Content-Type'] = CONTENT_TYPE
41
42
  request['Content-Encoding'] = CONTENT_ENCODING
42
43
  request.body = Zlib::Deflate.deflate(
43
- Appsignal.json.encode(payload),
44
+ JSON.generate(payload, :quirks_mode => true),
44
45
  Zlib::BEST_SPEED
45
46
  )
46
47
  end
@@ -1,3 +1,3 @@
1
1
  module Appsignal
2
- VERSION = '0.9.6'
2
+ VERSION = '0.10.0.beta.0'
3
3
  end
@@ -9,6 +9,10 @@ default: &defaults
9
9
  # The cuttoff point in ms above which a request is considered slow, default is 200
10
10
  # slow_request_threshold: 200
11
11
 
12
+ # Actions that should not be monitored by AppSignal
13
+ # ignore_actions:
14
+ # - ApplicationController#isup
15
+
12
16
  # Configuration per environment, leave out an environment or set active
13
17
  # to false to not push metrics for that environment.
14
18
  <% environments.each do |environment| -%>
@@ -0,0 +1,212 @@
1
+ require 'vendor/active_support/notifications/instrumenter'
2
+ require 'vendor/active_support/notifications/fanout'
3
+ require 'vendor/active_support/per_thread_registry'
4
+
5
+ module ActiveSupport
6
+ # = Notifications
7
+ #
8
+ # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
9
+ # Ruby.
10
+ #
11
+ # == Instrumenters
12
+ #
13
+ # To instrument an event you just need to do:
14
+ #
15
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
16
+ # render text: 'Foo'
17
+ # end
18
+ #
19
+ # That executes the block first and notifies all subscribers once done.
20
+ #
21
+ # In the example above +render+ is the name of the event, and the rest is called
22
+ # the _payload_. The payload is a mechanism that allows instrumenters to pass
23
+ # extra information to subscribers. Payloads consist of a hash whose contents
24
+ # are arbitrary and generally depend on the event.
25
+ #
26
+ # == Subscribers
27
+ #
28
+ # You can consume those events and the information they provide by registering
29
+ # a subscriber.
30
+ #
31
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
32
+ # name # => String, name of the event (such as 'render' from above)
33
+ # start # => Time, when the instrumented block started execution
34
+ # finish # => Time, when the instrumented block ended execution
35
+ # id # => String, unique ID for this notification
36
+ # payload # => Hash, the payload
37
+ # end
38
+ #
39
+ # For instance, let's store all "render" events in an array:
40
+ #
41
+ # events = []
42
+ #
43
+ # ActiveSupport::Notifications.subscribe('render') do |*args|
44
+ # events << ActiveSupport::Notifications::Event.new(*args)
45
+ # end
46
+ #
47
+ # That code returns right away, you are just subscribing to "render" events.
48
+ # The block is saved and will be called whenever someone instruments "render":
49
+ #
50
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
51
+ # render text: 'Foo'
52
+ # end
53
+ #
54
+ # event = events.first
55
+ # event.name # => "render"
56
+ # event.duration # => 10 (in milliseconds)
57
+ # event.payload # => { extra: :information }
58
+ #
59
+ # The block in the <tt>subscribe</tt> call gets the name of the event, start
60
+ # timestamp, end timestamp, a string with a unique identifier for that event
61
+ # (something like "535801666f04d0298cd6"), and a hash with the payload, in
62
+ # that order.
63
+ #
64
+ # If an exception happens during that particular instrumentation the payload will
65
+ # have a key <tt>:exception</tt> with an array of two elements as value: a string with
66
+ # the name of the exception class, and the exception message.
67
+ #
68
+ # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
69
+ # is able to take the arguments as they come and provide an object-oriented
70
+ # interface to that data.
71
+ #
72
+ # It is also possible to pass an object as the second parameter passed to the
73
+ # <tt>subscribe</tt> method instead of a block:
74
+ #
75
+ # module ActionController
76
+ # class PageRequest
77
+ # def call(name, started, finished, unique_id, payload)
78
+ # Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ')
79
+ # end
80
+ # end
81
+ # end
82
+ #
83
+ # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
84
+ #
85
+ # resulting in the following output within the logs including a hash with the payload:
86
+ #
87
+ # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
88
+ # controller: "Devise::SessionsController",
89
+ # action: "new",
90
+ # params: {"action"=>"new", "controller"=>"devise/sessions"},
91
+ # format: :html,
92
+ # method: "GET",
93
+ # path: "/login/sign_in",
94
+ # status: 200,
95
+ # view_runtime: 279.3080806732178,
96
+ # db_runtime: 40.053
97
+ # }
98
+ #
99
+ # You can also subscribe to all events whose name matches a certain regexp:
100
+ #
101
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
102
+ # ...
103
+ # end
104
+ #
105
+ # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
106
+ # to all events.
107
+ #
108
+ # == Temporary Subscriptions
109
+ #
110
+ # Sometimes you do not want to subscribe to an event for the entire life of
111
+ # the application. There are two ways to unsubscribe.
112
+ #
113
+ # WARNING: The instrumentation framework is designed for long-running subscribers,
114
+ # use this feature sparingly because it wipes some internal caches and that has
115
+ # a negative impact on performance.
116
+ #
117
+ # === Subscribe While a Block Runs
118
+ #
119
+ # You can subscribe to some event temporarily while some block runs. For
120
+ # example, in
121
+ #
122
+ # callback = lambda {|*args| ... }
123
+ # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
124
+ # ...
125
+ # end
126
+ #
127
+ # the callback will be called for all "sql.active_record" events instrumented
128
+ # during the execution of the block. The callback is unsubscribed automatically
129
+ # after that.
130
+ #
131
+ # === Manual Unsubscription
132
+ #
133
+ # The +subscribe+ method returns a subscriber object:
134
+ #
135
+ # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
136
+ # ...
137
+ # end
138
+ #
139
+ # To prevent that block from being called anymore, just unsubscribe passing
140
+ # that reference:
141
+ #
142
+ # ActiveSupport::Notifications.unsubscribe(subscriber)
143
+ #
144
+ # You can also unsubscribe by passing the name of the subscriber object. Note
145
+ # that this will unsubscribe all subscriptions with the given name:
146
+ #
147
+ # ActiveSupport::Notifications.unsubscribe("render")
148
+ #
149
+ # == Default Queue
150
+ #
151
+ # Notifications ships with a queue implementation that consumes and publishes events
152
+ # to all log subscribers. You can use any queue implementation you want.
153
+ #
154
+ module Notifications
155
+ class << self
156
+ attr_accessor :notifier
157
+
158
+ def publish(name, *args)
159
+ notifier.publish(name, *args)
160
+ end
161
+
162
+ def instrument(name, payload = {})
163
+ if notifier.listening?(name)
164
+ instrumenter.instrument(name, payload) { yield payload if block_given? }
165
+ else
166
+ yield payload if block_given?
167
+ end
168
+ end
169
+
170
+ def subscribe(*args, &block)
171
+ notifier.subscribe(*args, &block)
172
+ end
173
+
174
+ def subscribed(callback, *args, &block)
175
+ subscriber = subscribe(*args, &callback)
176
+ yield
177
+ ensure
178
+ unsubscribe(subscriber)
179
+ end
180
+
181
+ def unsubscribe(subscriber_or_name)
182
+ notifier.unsubscribe(subscriber_or_name)
183
+ end
184
+
185
+ def instrumenter
186
+ InstrumentationRegistry.instance.instrumenter_for(notifier)
187
+ end
188
+ end
189
+
190
+ # This class is a registry which holds all of the +Instrumenter+ objects
191
+ # in a particular thread local. To access the +Instrumenter+ object for a
192
+ # particular +notifier+, you can call the following method:
193
+ #
194
+ # InstrumentationRegistry.instrumenter_for(notifier)
195
+ #
196
+ # The instrumenters for multiple notifiers are held in a single instance of
197
+ # this class.
198
+ class InstrumentationRegistry # :nodoc:
199
+ extend ActiveSupport::PerThreadRegistry
200
+
201
+ def initialize
202
+ @registry = {}
203
+ end
204
+
205
+ def instrumenter_for(notifier)
206
+ @registry[notifier] ||= Instrumenter.new(notifier)
207
+ end
208
+ end
209
+
210
+ self.notifier = Fanout.new
211
+ end
212
+ end