timber 1.1.14 → 2.0.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -2
  3. data/.travis.yml +47 -0
  4. data/Gemfile +1 -28
  5. data/README.md +83 -298
  6. data/bin/timber +13 -0
  7. data/gemfiles/rails-3.0.gemfile +5 -0
  8. data/gemfiles/rails-3.1.gemfile +5 -0
  9. data/gemfiles/rails-3.2.gemfile +5 -0
  10. data/gemfiles/rails-4.0.gemfile +9 -0
  11. data/gemfiles/rails-4.1.gemfile +9 -0
  12. data/gemfiles/rails-4.2.gemfile +9 -0
  13. data/gemfiles/rails-5.0.gemfile +9 -0
  14. data/gemfiles/rails-edge.gemfile +7 -0
  15. data/lib/timber.rb +7 -7
  16. data/lib/timber/cli.rb +72 -0
  17. data/lib/timber/cli/api.rb +104 -0
  18. data/lib/timber/cli/application.rb +28 -0
  19. data/lib/timber/cli/install.rb +186 -0
  20. data/lib/timber/cli/io_helper.rb +58 -0
  21. data/lib/timber/cli/messages.rb +170 -0
  22. data/lib/timber/config.rb +47 -6
  23. data/lib/timber/contexts/http.rb +2 -2
  24. data/lib/timber/current_context.rb +1 -1
  25. data/lib/timber/event.rb +8 -0
  26. data/lib/timber/events.rb +2 -0
  27. data/lib/timber/events/controller_call.rb +12 -3
  28. data/lib/timber/events/exception.rb +4 -3
  29. data/lib/timber/events/http_client_request.rb +61 -0
  30. data/lib/timber/events/http_client_response.rb +47 -0
  31. data/lib/timber/events/http_server_request.rb +15 -23
  32. data/lib/timber/events/http_server_response.rb +9 -9
  33. data/lib/timber/events/sql_query.rb +2 -2
  34. data/lib/timber/events/template_render.rb +2 -2
  35. data/lib/timber/frameworks/rails.rb +31 -6
  36. data/lib/timber/integrations.rb +22 -0
  37. data/lib/timber/integrations/action_controller/log_subscriber.rb +25 -0
  38. data/lib/timber/integrations/action_controller/log_subscriber/timber_log_subscriber.rb +40 -0
  39. data/lib/timber/integrations/action_dispatch/debug_exceptions.rb +51 -0
  40. data/lib/timber/integrations/action_view/log_subscriber.rb +25 -0
  41. data/lib/timber/integrations/action_view/log_subscriber/timber_log_subscriber.rb +73 -0
  42. data/lib/timber/integrations/active_record/log_subscriber.rb +25 -0
  43. data/lib/timber/integrations/active_record/log_subscriber/timber_log_subscriber.rb +39 -0
  44. data/lib/timber/integrations/active_support/tagged_logging.rb +71 -0
  45. data/lib/timber/integrations/rack.rb +16 -0
  46. data/lib/timber/integrations/rack/exception_event.rb +28 -0
  47. data/lib/timber/integrations/rack/http_context.rb +25 -0
  48. data/lib/timber/integrations/rack/http_events.rb +46 -0
  49. data/lib/timber/integrations/rack/user_context.rb +59 -0
  50. data/lib/timber/integrations/rails/rack_logger.rb +49 -0
  51. data/lib/timber/integrator.rb +24 -0
  52. data/lib/timber/log_devices/http.rb +14 -21
  53. data/lib/timber/log_entry.rb +1 -1
  54. data/lib/timber/logger.rb +38 -12
  55. data/lib/timber/overrides.rb +9 -0
  56. data/lib/timber/overrides/lograge.rb +14 -0
  57. data/lib/timber/overrides/rails_server.rb +10 -0
  58. data/lib/timber/util.rb +2 -0
  59. data/lib/timber/util/active_support_log_subscriber.rb +13 -9
  60. data/lib/timber/util/http_event.rb +54 -0
  61. data/lib/timber/util/request.rb +44 -0
  62. data/lib/timber/version.rb +1 -1
  63. data/spec/README.md +5 -9
  64. data/spec/spec_helper.rb +1 -4
  65. data/spec/support/action_controller.rb +7 -3
  66. data/spec/support/active_record.rb +23 -19
  67. data/spec/support/rails.rb +56 -32
  68. data/spec/support/timber.rb +2 -3
  69. data/spec/support/webmock.rb +1 -0
  70. data/spec/timber/integrations/action_controller/log_subscriber_spec.rb +55 -0
  71. data/spec/timber/integrations/action_dispatch/debug_exceptions_spec.rb +53 -0
  72. data/spec/timber/integrations/action_view/log_subscriber_spec.rb +115 -0
  73. data/spec/timber/integrations/active_record/log_subscriber_spec.rb +46 -0
  74. data/spec/timber/integrations/rack/http_context_spec.rb +60 -0
  75. data/spec/timber/integrations/rails/rack_logger_spec.rb +58 -0
  76. data/spec/timber/logger_spec.rb +45 -9
  77. data/timber.gemspec +29 -3
  78. metadata +143 -46
  79. data/Appraisals +0 -41
  80. data/circle.yml +0 -33
  81. data/lib/timber/overrides/logger_add.rb +0 -38
  82. data/lib/timber/probe.rb +0 -23
  83. data/lib/timber/probes.rb +0 -23
  84. data/lib/timber/probes/action_controller_log_subscriber.rb +0 -20
  85. data/lib/timber/probes/action_controller_log_subscriber/log_subscriber.rb +0 -64
  86. data/lib/timber/probes/action_controller_user_context.rb +0 -52
  87. data/lib/timber/probes/action_dispatch_debug_exceptions.rb +0 -80
  88. data/lib/timber/probes/action_view_log_subscriber.rb +0 -20
  89. data/lib/timber/probes/action_view_log_subscriber/log_subscriber.rb +0 -69
  90. data/lib/timber/probes/active_record_log_subscriber.rb +0 -20
  91. data/lib/timber/probes/active_record_log_subscriber/log_subscriber.rb +0 -31
  92. data/lib/timber/probes/active_support_tagged_logging.rb +0 -63
  93. data/lib/timber/probes/rails_rack_logger.rb +0 -77
  94. data/lib/timber/rack_middlewares.rb +0 -12
  95. data/lib/timber/rack_middlewares/http_context.rb +0 -30
  96. data/spec/support/action_view.rb +0 -4
  97. data/spec/support/coveralls.rb +0 -2
  98. data/spec/support/simplecov.rb +0 -9
  99. data/spec/timber/overrides/logger_add_spec.rb +0 -26
  100. data/spec/timber/probes/action_controller_log_subscriber_spec.rb +0 -65
  101. data/spec/timber/probes/action_controller_user_context_spec.rb +0 -53
  102. data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +0 -48
  103. data/spec/timber/probes/action_view_log_subscriber_spec.rb +0 -107
  104. data/spec/timber/probes/active_record_log_subscriber_spec.rb +0 -47
  105. data/spec/timber/probes/rails_rack_logger_spec.rb +0 -46
  106. data/spec/timber/rack_middlewares/http_context_spec.rb +0 -47
@@ -0,0 +1,25 @@
1
+ module Timber
2
+ module Integrations
3
+ module ActiveRecord
4
+ # Reponsible for uninstalling the default `ActiveRecord::LogSubscriber` and replacing it
5
+ # with the `TimberLogSubscriber`.
6
+ #
7
+ # @private
8
+ class LogSubscriber < Integrator
9
+ def initialize
10
+ require "active_record/log_subscriber"
11
+ require "timber/integrations/active_record/log_subscriber/timber_log_subscriber"
12
+ rescue LoadError => e
13
+ raise RequirementNotMetError.new(e.message)
14
+ end
15
+
16
+ def integrate!
17
+ return true if Util::ActiveSupportLogSubscriber.subscribed?(:active_record, TimberLogSubscriber)
18
+
19
+ Util::ActiveSupportLogSubscriber.unsubscribe!(:active_record, ::ActiveRecord::LogSubscriber)
20
+ TimberLogSubscriber.attach_to(:active_record)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ module Timber
2
+ module Integrations
3
+ module ActiveRecord
4
+ class LogSubscriber < Integrator
5
+ # The log subscriber that replaces the default `ActiveRecord::LogSubscriber`.
6
+ # The intent of this subscriber is to, as transparently as possible, properly
7
+ # track events that are being logged here. This LogSubscriber will never change
8
+ # default behavior / log messages.
9
+ #
10
+ # @private
11
+ class TimberLogSubscriber < ::ActiveRecord::LogSubscriber
12
+ def sql(event)
13
+ r = super(event)
14
+
15
+ if @message
16
+ payload = event.payload
17
+ event = Events::SQLQuery.new(
18
+ sql: payload[:sql],
19
+ time_ms: event.duration,
20
+ message: @message
21
+ )
22
+
23
+ logger.debug event
24
+
25
+ @message = nil
26
+ end
27
+
28
+ r
29
+ end
30
+
31
+ private
32
+ def debug(message)
33
+ @message = message
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,71 @@
1
+ module Timber
2
+ module Integrations
3
+ module ActiveSupport
4
+ # Reponsible for automatimcally tracking SQL query events in `ActiveRecord`, while still
5
+ # preserving the default log style.
6
+ #
7
+ # @private
8
+ class TaggedLogging < Integrator
9
+ # @private
10
+ module FormatterMethods
11
+ def self.included(mod)
12
+ mod.module_eval do
13
+ alias_method :_timber_original_push_tags, :push_tags
14
+ alias_method :_timber_original_pop_tags, :pop_tags
15
+
16
+ def call(severity, timestamp, progname, msg)
17
+ if is_a?(Timber::Logger::Formatter)
18
+ # Don't convert the message into a string
19
+ super(severity, timestamp, progname, msg)
20
+ else
21
+ super(severity, timestamp, progname, "#{tags_text}#{msg}")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ # @private
29
+ module LoggerMethods
30
+ def self.included(klass)
31
+ klass.class_eval do
32
+ def add(severity, message = nil, progname = nil, &block)
33
+ if message.nil?
34
+ if block_given?
35
+ message = block.call
36
+ else
37
+ message = progname
38
+ progname = nil #No instance variable for this like Logger
39
+ end
40
+ end
41
+ if @logger.is_a?(Timber::Logger)
42
+ @logger.add(severity, message, progname)
43
+ else
44
+ @logger.add(severity, "#{tags_text}#{message}", progname)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def initialize
52
+ require "active_support/tagged_logging"
53
+ rescue LoadError => e
54
+ raise RequirementNotMetError.new(e.message)
55
+ end
56
+
57
+ def integrate!
58
+ if defined?(::ActiveSupport::TaggedLogging::Formatter)
59
+ return true if ::ActiveSupport::TaggedLogging::Formatter.include?(FormatterMethods)
60
+
61
+ ::ActiveSupport::TaggedLogging::Formatter.send(:include, FormatterMethods)
62
+ else
63
+ return true if ::ActiveSupport::TaggedLogging.include?(LoggerMethods)
64
+
65
+ ::ActiveSupport::TaggedLogging.send(:include, LoggerMethods)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ require "timber/integrations/rack/exception_event"
2
+ require "timber/integrations/rack/http_context"
3
+ require "timber/integrations/rack/http_events"
4
+ require "timber/integrations/rack/user_context"
5
+
6
+ module Timber
7
+ module Integrations
8
+ module Rack
9
+ # All available middlewares. The order is relevant. Middlewares that set
10
+ # context are added first so that context is included in subsequent log lines.
11
+ def self.middlewares
12
+ @middlewares ||= [HTTPContext, UserContext, HTTPEvents, ExceptionEvent]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ module Timber
2
+ module Integrations
3
+ module Rack
4
+ # Reponsible for capturing exceptions events within a Rack stack.
5
+ class ExceptionEvent
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ begin
12
+ status, headers, body = @app.call(env)
13
+ rescue Exception => exception
14
+ Config.instance.logger.fatal do
15
+ Events::Exception.new(
16
+ name: exception.class.name,
17
+ exception_message: exception.message,
18
+ backtrace: exception.backtrace
19
+ )
20
+ end
21
+
22
+ raise exception
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module Timber
2
+ module Integrations
3
+ module Rack
4
+ # Reponsible for adding the HTTP context for applications that use `Rack`.
5
+ class HTTPContext
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ request = Util::Request.new(env)
12
+ context = Contexts::HTTP.new(
13
+ method: request.request_method,
14
+ path: request.path,
15
+ remote_addr: request.ip,
16
+ request_id: request.request_id
17
+ )
18
+ CurrentContext.with(context) do
19
+ @app.call(env)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ module Timber
2
+ module Integrations
3
+ module Rack
4
+ # Reponsible for capturing and logging HTTP server requests and response events.
5
+ class HTTPEvents
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ start = Time.now
12
+ request = Util::Request.new(env)
13
+ body = Config.instance.capture_http_bodies? ? request.body_content : nil
14
+
15
+ Config.instance.logger.info do
16
+ Events::HTTPServerRequest.new(
17
+ body: body,
18
+ headers: request.headers,
19
+ host: request.host,
20
+ method: request.request_method,
21
+ path: request.path,
22
+ port: request.port,
23
+ query_string: request.query_string,
24
+ request_id: request.request_id, # we insert this middleware after ActionDispatch::RequestId
25
+ scheme: request.scheme
26
+ )
27
+ end
28
+
29
+ status, headers, body = @app.call(env)
30
+
31
+ Config.instance.logger.info do
32
+ time_ms = (Time.now - start) * 1000.0
33
+ Events::HTTPServerResponse.new(
34
+ headers: headers,
35
+ request_id: request.request_id,
36
+ status: status,
37
+ time_ms: time_ms
38
+ )
39
+ end
40
+
41
+ [status, headers, body]
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,59 @@
1
+ module Timber
2
+ module Integrations
3
+ module Rack
4
+ # Reponsible for adding the user context.
5
+ class UserContext
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ user = get_user(env)
12
+ if user
13
+ context = Contexts::User.new(
14
+ id: user_id(user),
15
+ name: user_name(user),
16
+ email: user_email(user)
17
+ )
18
+ CurrentContext.with(context) do
19
+ @app.call(env)
20
+ end
21
+ else
22
+ @app.call(env)
23
+ end
24
+ end
25
+
26
+ private
27
+ def get_user(env)
28
+ if env['warden']
29
+ env['warden'].user
30
+ else
31
+ nil
32
+ end
33
+ end
34
+
35
+ def user_id(user)
36
+ user.respond_to?(:id) ? user.id : nil
37
+ end
38
+
39
+ def user_name(user)
40
+ if user.respond_to?(:name) && user.name.is_a?(String)
41
+ user.name
42
+ elsif user.respond_to?(:first_name) && user.first_name.is_a?(String) && user.respond_to?(:last_name) && user.last_name.is_a?(String)
43
+ "#{user.first_name} #{user.last_name}"
44
+ else
45
+ nil
46
+ end
47
+ end
48
+
49
+ def user_email(user)
50
+ if user.respond_to?(:email) && user.email.is_a?(String)
51
+ user.email
52
+ else
53
+ nil
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,49 @@
1
+ module Timber
2
+ module Integrations
3
+ module Rails
4
+ # Disables the default rail's rack logging. Note, we cannot simply uninstall this rack
5
+ # middleware because rails couples this with ActiveSupport instrumentation. As such,
6
+ # we simply disable the logger and let our Rack middleware handle the logging.
7
+ #
8
+ # See: https://github.com/rails/rails/blob/80e66cc4d90bf8c15d1a5f6e3152e90147f00772/railties/lib/rails/rack/logger.rb#L34
9
+ #
10
+ # @private
11
+ class RackLogger < Integrator
12
+
13
+ # @private
14
+ module InstanceMethods
15
+ LOGGER = ::Logger.new(nil)
16
+
17
+ def self.included(klass)
18
+ klass.class_eval do
19
+ private
20
+ # Rails 3.2 calls Rails.logger directly, so this is the first place
21
+ # we can mute the logger calls.
22
+ def started_request_message(*args)
23
+ ""
24
+ end
25
+
26
+ # Rails > 3.2 uses a logger method. Muting logs is accomplished by
27
+ # passing a dummy logger instance with a nil log device.
28
+ def logger
29
+ LOGGER
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def initialize
36
+ require "rails/rack/logger"
37
+ rescue LoadError => e
38
+ raise RequirementNotMetError.new(e.message)
39
+ end
40
+
41
+ def integrate!
42
+ return true if ::Rails::Rack::Logger.include?(InstanceMethods)
43
+
44
+ ::Rails::Rack::Logger.send(:include, InstanceMethods)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ module Timber
2
+ # Base class for `Timber::Integrations::*`.
3
+ #
4
+ # @private
5
+ class Integrator
6
+ class RequirementNotMetError < StandardError; end
7
+
8
+ class << self
9
+ def integrate!(*args)
10
+ new(*args).integrate!
11
+ Config.instance.debug_logger.debug("Integrated #{name}") if Config.instance.debug_logger
12
+ true
13
+ # RequirementUnsatisfiedError is the only silent failure we support
14
+ rescue RequirementNotMetError => e
15
+ Config.instance.debug_logger.debug("Failed integrating #{name}: #{e.message}") if Config.instance.debug_logger
16
+ false
17
+ end
18
+ end
19
+
20
+ def integrate!
21
+ raise NotImplementedError.new
22
+ end
23
+ end
24
+ end
@@ -80,8 +80,6 @@ module Timber
80
80
  # each HTTP payload. If the queue exceeds this limit an HTTP request will be issued. Bigger
81
81
  # payloads mean higher throughput, but also use more memory. Timber will not accept
82
82
  # payloads larger than 1mb.
83
- # @option attributes [Symbol] :debug (false) Whether to print debug output or not. This is also
84
- # inferred from ENV['debug']. Output will be sent to `Timber::Config.logger`.
85
83
  # @option attributes [Symbol] :flush_interval (1) How often the client should
86
84
  # attempt to deliver logs to the Timber API in fractional seconds. The HTTP client buffers
87
85
  # logs and this options represents how often that will happen, assuming `:batch_byte_size`
@@ -105,8 +103,7 @@ module Timber
105
103
  # request_queue: Timber::LogDevices::HTTP::DroppingSizedQueue.new(3))
106
104
  # Timber::Logger.new(http_log_device)
107
105
  def initialize(api_key, options = {})
108
- @api_key = api_key
109
- @debug = options[:debug] || ENV['debug']
106
+ @api_key = api_key || raise(ArgumentError.new("The api_key parameter cannot be blank"))
110
107
  @timber_url = URI.parse(options[:timber_url] || ENV['TIMBER_URL'] || TIMBER_URL)
111
108
  @batch_size = options[:batch_size] || 1_000
112
109
  @flush_interval = options[:flush_interval] || 1 # 1 second
@@ -126,7 +123,7 @@ module Timber
126
123
  def write(msg)
127
124
  @msg_queue.enqueue(msg)
128
125
  if @msg_queue.full?
129
- logger.debug("Flushing timber buffer via write") if debug?
126
+ debug_logger.debug("Flushing timber buffer via write") if debug_logger
130
127
  flush
131
128
  end
132
129
  true
@@ -140,8 +137,8 @@ module Timber
140
137
  end
141
138
 
142
139
  private
143
- def debug?
144
- !@debug.nil?
140
+ def debug_logger
141
+ Timber::Config.instance.debug_logger
145
142
  end
146
143
 
147
144
  def flush
@@ -163,7 +160,7 @@ module Timber
163
160
  loop do
164
161
  begin
165
162
  if intervaled_flush_ready?
166
- logger.debug("Flushing timber buffer via the interval") if debug?
163
+ debug_logger.debug("Flushing timber buffer via the interval") if debug_logger
167
164
  flush
168
165
  end
169
166
  sleep(0.1)
@@ -180,20 +177,20 @@ module Timber
180
177
  def outlet
181
178
  loop do
182
179
  http = Net::HTTP.new(@timber_url.host, @timber_url.port)
183
- http.set_debug_output(logger) if debug?
180
+ http.set_debug_output(debug_logger) if debug_logger
184
181
  http.use_ssl = true if @timber_url.scheme == 'https'
185
182
  http.read_timeout = 30
186
183
  http.ssl_timeout = 10
187
184
  http.open_timeout = 10
188
185
 
189
186
  begin
190
- logger.info("Starting Timber HTTP connection") if debug?
187
+ debug_logger.info("Starting Timber HTTP connection") if debug_logger
191
188
  http.start do |conn|
192
189
  num_reqs = 0
193
190
  while num_reqs < @requests_per_conn
194
- if debug?
195
- logger.debug("Waiting on next Timber request")
196
- logger.debug("Number of threads waiting on Timber request queue: #{@request_queue.num_waiting}")
191
+ if debug_logger
192
+ debug_logger.debug("Waiting on next Timber request")
193
+ debug_logger.debug("Number of threads waiting on Timber request queue: #{@request_queue.num_waiting}")
197
194
  end
198
195
 
199
196
  # Blocks waiting for a request.
@@ -203,19 +200,19 @@ module Timber
203
200
  begin
204
201
  resp = conn.request(req)
205
202
  rescue => e
206
- logger.error("Timber request error: #{e.message}") if debug?
203
+ debug_logger.error("Timber request error: #{e.message}") if debug_logger
207
204
  next
208
205
  ensure
209
206
  @requests_in_flight -= 1
210
207
  end
211
208
  num_reqs += 1
212
- logger.debug("Timber request successful: #{resp.code}") if debug?
209
+ debug_logger.debug("Timber request successful: #{resp.code}") if debug_logger
213
210
  end
214
211
  end
215
212
  rescue => e
216
- logger.error("Timber request error: #{e.message}") if debug?
213
+ debug_logger.error("Timber request error: #{e.message}") if debug_logger
217
214
  ensure
218
- logger.debug("Finishing Timber HTTP connection") if debug?
215
+ debug_logger.debug("Finishing Timber HTTP connection") if debug_logger
219
216
  http.finish if http.started?
220
217
  end
221
218
  end
@@ -224,10 +221,6 @@ module Timber
224
221
  def authorization_payload
225
222
  @authorization_payload ||= "Basic #{Base64.urlsafe_encode64(@api_key).chomp}"
226
223
  end
227
-
228
- def logger
229
- Config.instance.logger
230
- end
231
224
  end
232
225
  end
233
226
  end