timber 1.1.14 → 2.0.0

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