appsignal 0.9.6 → 0.10.0.beta.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
  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