timberio 1.0.0.beta1
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 +7 -0
- data/.codeclimate.yml +34 -0
- data/.gitignore +14 -0
- data/Appraisals +37 -0
- data/Gemfile +22 -0
- data/LICENSE +38 -0
- data/README.md +22 -0
- data/Rakefile +4 -0
- data/TODO +4 -0
- data/benchmark/README.md +26 -0
- data/benchmark/rails_request.rb +68 -0
- data/benchmark/support/rails.rb +69 -0
- data/circle.yml +27 -0
- data/docs/installation/rails_on_heroku.md +31 -0
- data/docs/installation/rails_over_http.md +22 -0
- data/gemfiles/rails_3.0.X.gemfile +25 -0
- data/gemfiles/rails_3.1.X.gemfile +25 -0
- data/gemfiles/rails_3.2.X.gemfile +25 -0
- data/gemfiles/rails_4.0.X.gemfile +26 -0
- data/gemfiles/rails_4.1.X.gemfile +26 -0
- data/gemfiles/rails_4.2.X.gemfile +26 -0
- data/gemfiles/rails_5.0.X.gemfile +26 -0
- data/gemfiles/rails_edge.gemfile +27 -0
- data/lib/timber/api_settings.rb +17 -0
- data/lib/timber/bootstrap.rb +45 -0
- data/lib/timber/config.rb +25 -0
- data/lib/timber/context.rb +76 -0
- data/lib/timber/context_snapshot.rb +64 -0
- data/lib/timber/contexts/dynamic_values.rb +59 -0
- data/lib/timber/contexts/exception.rb +40 -0
- data/lib/timber/contexts/http_request.rb +22 -0
- data/lib/timber/contexts/http_requests/action_controller_specific.rb +48 -0
- data/lib/timber/contexts/http_requests/rack/params.rb +26 -0
- data/lib/timber/contexts/http_requests/rack.rb +105 -0
- data/lib/timber/contexts/http_response.rb +19 -0
- data/lib/timber/contexts/http_responses/action_controller.rb +76 -0
- data/lib/timber/contexts/logger.rb +33 -0
- data/lib/timber/contexts/organization.rb +33 -0
- data/lib/timber/contexts/organizations/action_controller.rb +34 -0
- data/lib/timber/contexts/server.rb +21 -0
- data/lib/timber/contexts/servers/heroku_specific.rb +48 -0
- data/lib/timber/contexts/sql_queries/active_record.rb +30 -0
- data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +37 -0
- data/lib/timber/contexts/sql_queries/active_record_specific.rb +59 -0
- data/lib/timber/contexts/sql_query.rb +18 -0
- data/lib/timber/contexts/template_render.rb +17 -0
- data/lib/timber/contexts/template_renders/action_view.rb +29 -0
- data/lib/timber/contexts/template_renders/action_view_specific.rb +51 -0
- data/lib/timber/contexts/user.rb +39 -0
- data/lib/timber/contexts/users/action_controller.rb +34 -0
- data/lib/timber/contexts.rb +23 -0
- data/lib/timber/current_context.rb +58 -0
- data/lib/timber/current_line_indexes.rb +35 -0
- data/lib/timber/frameworks/rails.rb +24 -0
- data/lib/timber/frameworks.rb +21 -0
- data/lib/timber/internal_logger.rb +35 -0
- data/lib/timber/log_device.rb +40 -0
- data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +14 -0
- data/lib/timber/log_devices/heroku_logplex.rb +14 -0
- data/lib/timber/log_devices/http/log_pile.rb +86 -0
- data/lib/timber/log_devices/http/log_truck/delivery.rb +116 -0
- data/lib/timber/log_devices/http/log_truck.rb +87 -0
- data/lib/timber/log_devices/http.rb +28 -0
- data/lib/timber/log_devices/io/formatter.rb +46 -0
- data/lib/timber/log_devices/io/hybrid_formatter.rb +41 -0
- data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +36 -0
- data/lib/timber/log_devices/io/json_formatter.rb +11 -0
- data/lib/timber/log_devices/io/logfmt_formatter.rb +11 -0
- data/lib/timber/log_devices/io.rb +41 -0
- data/lib/timber/log_devices.rb +4 -0
- data/lib/timber/log_line.rb +33 -0
- data/lib/timber/logger.rb +20 -0
- data/lib/timber/macros/compactor.rb +16 -0
- data/lib/timber/macros/date_formatter.rb +9 -0
- data/lib/timber/macros/deep_merger.rb +11 -0
- data/lib/timber/macros/logfmt_encoder.rb +77 -0
- data/lib/timber/macros.rb +4 -0
- data/lib/timber/patterns/delegated_singleton.rb +21 -0
- data/lib/timber/patterns/to_json.rb +22 -0
- data/lib/timber/patterns/to_logfmt.rb +9 -0
- data/lib/timber/patterns.rb +3 -0
- data/lib/timber/probe.rb +21 -0
- data/lib/timber/probes/action_controller_base.rb +31 -0
- data/lib/timber/probes/action_dispatch_debug_exceptions.rb +57 -0
- data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +15 -0
- data/lib/timber/probes/active_support_log_subscriber/action_view.rb +26 -0
- data/lib/timber/probes/active_support_log_subscriber/active_record.rb +13 -0
- data/lib/timber/probes/active_support_log_subscriber.rb +62 -0
- data/lib/timber/probes/heroku.rb +30 -0
- data/lib/timber/probes/logger.rb +31 -0
- data/lib/timber/probes/rack.rb +36 -0
- data/lib/timber/probes/server.rb +18 -0
- data/lib/timber/probes.rb +24 -0
- data/lib/timber/version.rb +3 -0
- data/lib/timber.rb +27 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/action_controller.rb +4 -0
- data/spec/support/action_view.rb +4 -0
- data/spec/support/active_record.rb +28 -0
- data/spec/support/coveralls.rb +2 -0
- data/spec/support/rails/templates/_partial.html +1 -0
- data/spec/support/rails/templates/template.html +1 -0
- data/spec/support/rails.rb +33 -0
- data/spec/support/simplecov.rb +9 -0
- data/spec/support/socket_hostname.rb +12 -0
- data/spec/support/timber.rb +23 -0
- data/spec/support/timecop.rb +3 -0
- data/spec/support/webmock.rb +2 -0
- data/spec/timber/bootstrap_spec.rb +31 -0
- data/spec/timber/context_snapshot_spec.rb +10 -0
- data/spec/timber/context_spec.rb +4 -0
- data/spec/timber/contexts/exception_spec.rb +34 -0
- data/spec/timber/contexts/organizations/action_controller_spec.rb +49 -0
- data/spec/timber/contexts/users/action_controller_spec.rb +65 -0
- data/spec/timber/current_line_indexes_spec.rb +40 -0
- data/spec/timber/frameworks/rails_spec.rb +9 -0
- data/spec/timber/log_devices/heroku_logplex_spec.rb +45 -0
- data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +66 -0
- data/spec/timber/log_devices/http/log_truck_spec.rb +65 -0
- data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +28 -0
- data/spec/timber/log_line_spec.rb +49 -0
- data/spec/timber/macros/compactor_spec.rb +19 -0
- data/spec/timber/macros/logfmt_encoder_spec.rb +89 -0
- data/spec/timber/patterns/to_json_spec.rb +40 -0
- data/spec/timber/probes/action_controller_base_spec.rb +43 -0
- data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +35 -0
- data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +44 -0
- data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +26 -0
- data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +45 -0
- data/spec/timber/probes/logger_spec.rb +20 -0
- data/spec/timber/probes/rack_spec.rb +26 -0
- data/timberio.gemspec +20 -0
- metadata +210 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Macros
|
|
3
|
+
# Encodes a hash into a simple logfmt string.
|
|
4
|
+
#
|
|
5
|
+
# A couple of important points:
|
|
6
|
+
#
|
|
7
|
+
# 1. This module is designed to be fast, as it is executed inline with
|
|
8
|
+
# each log line.
|
|
9
|
+
# 2. It makes assumptions about the hash structure, and is designed
|
|
10
|
+
# specifically for Timber. We can reduce the edge cases and improve
|
|
11
|
+
# performance drastically.
|
|
12
|
+
class LogfmtEncoder
|
|
13
|
+
ARRAY_DELIMITER = ",".freeze
|
|
14
|
+
ARRAY_END = "]".freeze
|
|
15
|
+
ARRAY_START = "[".freeze
|
|
16
|
+
ESCAPE = "\\".freeze
|
|
17
|
+
KEY_DELIMITER = ".".freeze
|
|
18
|
+
KEY_VALUE_DELIMITER = "=".freeze
|
|
19
|
+
PAIR_DELIMITER = " ".freeze
|
|
20
|
+
STRING_WRAPPER = "\"".freeze
|
|
21
|
+
SPECIAL_KEY_CHARACTERS = [KEY_DELIMITER, PAIR_DELIMITER].freeze
|
|
22
|
+
SPECIAL_VALUE_CHARACTERS = [ARRAY_DELIMITER, ARRAY_END, ARRAY_START, ESCAPE, PAIR_DELIMITER, STRING_WRAPPER].freeze
|
|
23
|
+
|
|
24
|
+
def self.encode(hash, ancestors = [])
|
|
25
|
+
if !hash.is_a?(Hash)
|
|
26
|
+
raise ArgumentError.new("hash must be a Hash")
|
|
27
|
+
end
|
|
28
|
+
items = hash.collect do |key, value|
|
|
29
|
+
keys = ancestors + [key]
|
|
30
|
+
if value.is_a?(Hash)
|
|
31
|
+
encode(value, keys)
|
|
32
|
+
else
|
|
33
|
+
"#{encode_keys(keys)}#{KEY_VALUE_DELIMITER}#{encode_value(value)}"
|
|
34
|
+
end
|
|
35
|
+
end.flatten
|
|
36
|
+
join(*items)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.join(*items)
|
|
40
|
+
items.join(PAIR_DELIMITER)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def self.encode_keys(keys)
|
|
45
|
+
keys.collect { |key| encode_key(key) }.join(KEY_DELIMITER)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.encode_key(key)
|
|
49
|
+
key = key.to_s
|
|
50
|
+
if SPECIAL_KEY_CHARACTERS.any? { |c| key.include?(c) }
|
|
51
|
+
escape(key)
|
|
52
|
+
else
|
|
53
|
+
key
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.encode_value(value)
|
|
58
|
+
if value.is_a?(Array)
|
|
59
|
+
values = value.collect { |v| encode_value(v) }.join(ARRAY_DELIMITER)
|
|
60
|
+
"#{ARRAY_START}#{values}#{ARRAY_END}"
|
|
61
|
+
elsif value.is_a?(String) && SPECIAL_VALUE_CHARACTERS.any? { |c| value.include?(c) }
|
|
62
|
+
escape(value)
|
|
63
|
+
else
|
|
64
|
+
value
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.escape(value)
|
|
69
|
+
# Simple gsub that is better for performance, we do not need to handle
|
|
70
|
+
# all of the edgecases that to_json handled. #to_json is also much slower.
|
|
71
|
+
new_value = value.gsub(STRING_WRAPPER, "#{ESCAPE}#{STRING_WRAPPER}")
|
|
72
|
+
new_value.gsub!(ESCAPE, "#{ESCAPE}#{ESCAPE}")
|
|
73
|
+
"#{STRING_WRAPPER}#{new_value}#{STRING_WRAPPER}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "singleton"
|
|
2
|
+
|
|
3
|
+
module Timber
|
|
4
|
+
module Patterns
|
|
5
|
+
module DelegatedSingleton
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.class_eval do
|
|
8
|
+
extend ClassMethods
|
|
9
|
+
include Singleton
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
private
|
|
15
|
+
def method_missing(name, *args, &block)
|
|
16
|
+
instance.send(name, *args, &block)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Patterns
|
|
3
|
+
# Module to fall inline with Rail's core object crazy changes.
|
|
4
|
+
# If Rails is present, it will play nice. If not, it will work just fine.
|
|
5
|
+
module ToJSON
|
|
6
|
+
# We explicitly do not do anything with the arguments as we do not need them.
|
|
7
|
+
# We avoid the unneccssary complexity.
|
|
8
|
+
def as_json(*_args)
|
|
9
|
+
@as_json ||= Macros::Compactor.compact(json_payload).freeze
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_json(*_args)
|
|
13
|
+
@to_json ||= as_json.to_json.freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
def json_payload
|
|
18
|
+
raise NotImplementedError.new("#json_payload is not implemented for #{self.class}")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/timber/probe.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
class Probe
|
|
3
|
+
class RequirementNotMetError < StandardError; end
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def insert!(*args)
|
|
7
|
+
new(*args).insert!
|
|
8
|
+
Config.logger.debug("Inserted probe #{name}")
|
|
9
|
+
true
|
|
10
|
+
# RequirementUnsatisfiedError is the only silent failure we support
|
|
11
|
+
rescue RequirementNotMetError => e
|
|
12
|
+
Config.logger.debug("Failed inserting probe #{name}: #{e.message}")
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def insert!
|
|
18
|
+
raise NotImplementedError.new("You must implement #insert")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
# Wraps process_action so that we get *anu* logs written within the controller.
|
|
4
|
+
# This is superior to using process_action in ActionController::LogSubscriber.
|
|
5
|
+
class ActionControllerBase < Probe
|
|
6
|
+
module InstanceMethods
|
|
7
|
+
def process(*args)
|
|
8
|
+
request_context = Contexts::HTTPRequests::ActionControllerSpecific.new(self)
|
|
9
|
+
organization_context = Contexts::Organizations::ActionController.new(self)
|
|
10
|
+
user_context = Contexts::Users::ActionController.new(self)
|
|
11
|
+
response_context = Contexts::HTTPResponses::ActionController.new(self)
|
|
12
|
+
# The order is relevant here, request_context is higher in the hierarchy
|
|
13
|
+
CurrentContext.add(request_context, organization_context, user_context, response_context) do
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
require "action_controller"
|
|
21
|
+
rescue LoadError => e
|
|
22
|
+
raise RequirementNotMetError.new(e.message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def insert!
|
|
26
|
+
return true if ::ActionController::Base.include?(InstanceMethods)
|
|
27
|
+
::ActionController::Base.send(:include, InstanceMethods)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
class ActionDispatchDebugExceptions < Probe
|
|
4
|
+
module InstanceMethods
|
|
5
|
+
def self.included(klass)
|
|
6
|
+
klass.class_eval do
|
|
7
|
+
private
|
|
8
|
+
# We have to monkey patch because ruby < 2.0 does not support prepend.
|
|
9
|
+
alias_method :_timber_old_log_error, :log_error
|
|
10
|
+
|
|
11
|
+
def log_error(*args)
|
|
12
|
+
# Rails 3.0 has 1 arg, >= 3.1 uses 2 args, the last being an exception wrapper
|
|
13
|
+
exception = args.size == 1 ? args.first : args.last.exception
|
|
14
|
+
# AR only logs queries if debugging, no need to do anything otherwise
|
|
15
|
+
context = Contexts::Exception.new(exception)
|
|
16
|
+
CurrentContext.add(context) do
|
|
17
|
+
_timber_old_log_error(*args)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :target_class
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
load_debug_exceptions
|
|
28
|
+
rescue RequirementNotMetError
|
|
29
|
+
load_show_exceptions
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def insert!
|
|
33
|
+
return true if target_class.include?(InstanceMethods)
|
|
34
|
+
target_class.send(:include, InstanceMethods)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
# Rails >= 3.1 logs the error here
|
|
39
|
+
def load_debug_exceptions
|
|
40
|
+
require "action_dispatch/middleware/debug_exceptions"
|
|
41
|
+
@target_class = ::ActionDispatch::DebugExceptions
|
|
42
|
+
true
|
|
43
|
+
rescue LoadError => e
|
|
44
|
+
raise RequirementNotMetError.new(e.message)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Rails 3.0 logs the error here
|
|
48
|
+
def load_show_exceptions
|
|
49
|
+
require "action_dispatch/middleware/show_exceptions"
|
|
50
|
+
@target_class = ::ActionDispatch::ShowExceptions
|
|
51
|
+
true
|
|
52
|
+
rescue LoadError => e
|
|
53
|
+
raise RequirementNotMetError.new(e.message)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
class ActiveSupportLogSubscriber < Probe
|
|
4
|
+
module ActionController
|
|
5
|
+
def self.process_action(_log_subscriber, event, &_block)
|
|
6
|
+
context = CurrentContext.get(Contexts::HTTPResponses::ActionController)
|
|
7
|
+
if context
|
|
8
|
+
context.event = event
|
|
9
|
+
end
|
|
10
|
+
yield
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
class ActiveSupportLogSubscriber < Probe
|
|
4
|
+
class ActionView
|
|
5
|
+
def self.render_collection(_log_subscriber, event, &_block)
|
|
6
|
+
wrap(event) { yield }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.render_partial(_log_subscriber, event, &_block)
|
|
10
|
+
wrap(event) { yield }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.render_template(_log_subscriber, event, &_block)
|
|
14
|
+
wrap(event) { yield }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
def self.wrap(event, &_block)
|
|
19
|
+
context1 = Contexts::TemplateRenders::ActionView.new(event)
|
|
20
|
+
context2 = Contexts::TemplateRenders::ActionViewSpecific.new(event)
|
|
21
|
+
CurrentContext.add(context1, context2) { yield }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
class ActiveSupportLogSubscriber < Probe
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
def self.sql(log_subscriber, event, &_block)
|
|
6
|
+
context1 = Contexts::SQLQueries::ActiveRecord.new(log_subscriber, event)
|
|
7
|
+
context2 = Contexts::SQLQueries::ActiveRecordSpecific.new(log_subscriber, event)
|
|
8
|
+
CurrentContext.add(context1, context2) { yield }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require "timber/probes/active_support_log_subscriber/action_controller"
|
|
2
|
+
require "timber/probes/active_support_log_subscriber/action_view"
|
|
3
|
+
require "timber/probes/active_support_log_subscriber/active_record"
|
|
4
|
+
|
|
5
|
+
module Timber
|
|
6
|
+
module Probes
|
|
7
|
+
# We want to wrap every log subscriber. Think about something like
|
|
8
|
+
# lograge. We want to add context to those logs as well, not just
|
|
9
|
+
# the internal rails log subscribers.
|
|
10
|
+
class ActiveSupportLogSubscriber < Probe
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def self.included(klass)
|
|
13
|
+
klass.class_eval do
|
|
14
|
+
alias_method :_timber_old_send, :send
|
|
15
|
+
|
|
16
|
+
# Override send since the #finish method uses that to send the event.
|
|
17
|
+
# This allows us to wrap the actual method.
|
|
18
|
+
def send(method_name, *args, &block)
|
|
19
|
+
if args.first.is_a?(ActiveSupport::Notifications::Event)
|
|
20
|
+
ActiveSupportLogSubscriber.wrap(self, args.first) do
|
|
21
|
+
_timber_old_send(method_name, *args, &block)
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
_timber_old_send(method_name, *args, &block)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
WRAPPER_MAP = {
|
|
33
|
+
"action_controller" => ActionController,
|
|
34
|
+
"action_view" => ActionView,
|
|
35
|
+
"active_record" => ActiveRecord
|
|
36
|
+
}.freeze
|
|
37
|
+
EVENT_DELIMITER = ".".freeze
|
|
38
|
+
|
|
39
|
+
def wrap(log_subscriber, event, &_block)
|
|
40
|
+
event_name, namespace = event.name.split(EVENT_DELIMITER)
|
|
41
|
+
wrapper = WRAPPER_MAP[namespace]
|
|
42
|
+
if wrapper && wrapper.respond_to?(event_name)
|
|
43
|
+
wrapper.send(event_name, log_subscriber, event) { yield }
|
|
44
|
+
else
|
|
45
|
+
yield
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize
|
|
51
|
+
require "active_support/log_subscriber"
|
|
52
|
+
rescue LoadError => e
|
|
53
|
+
raise RequirementNotMetError.new(e.message)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def insert!
|
|
57
|
+
return true if ::ActiveSupport::LogSubscriber.include?(ClassMethods)
|
|
58
|
+
::ActiveSupport::LogSubscriber.send(:include, ClassMethods)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
class Heroku < Probe
|
|
4
|
+
class << self
|
|
5
|
+
attr_accessor :inserted
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
if dyno.nil?
|
|
10
|
+
raise RequirementNotMetError.new("The DYNO environment variable is not set. " +
|
|
11
|
+
"Not in the Heroku environment.")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def insert!
|
|
16
|
+
return true if self.class.inserted == true
|
|
17
|
+
context = Contexts::Servers::HerokuSpecific.new(dyno)
|
|
18
|
+
# Note we don't use a block here, this is because
|
|
19
|
+
# the context is persistent.
|
|
20
|
+
CurrentContext.add(context)
|
|
21
|
+
self.class.inserted = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def dyno
|
|
26
|
+
ENV['DYNO']
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
|
|
3
|
+
module Timber
|
|
4
|
+
module Probes
|
|
5
|
+
class Logger < Probe
|
|
6
|
+
module InstanceMethods
|
|
7
|
+
def self.included(klass)
|
|
8
|
+
klass.class_eval do
|
|
9
|
+
alias_method :_timber_old_add, :add
|
|
10
|
+
|
|
11
|
+
def add(level, *args, &block)
|
|
12
|
+
if self == Config.logger
|
|
13
|
+
_timber_old_add(level, *args, &block)
|
|
14
|
+
else
|
|
15
|
+
context = Contexts::Logger.new(level, progname)
|
|
16
|
+
CurrentContext.add(context) do
|
|
17
|
+
_timber_old_add(level, *args, &block)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def insert!
|
|
26
|
+
return true if ::Logger.include?(InstanceMethods)
|
|
27
|
+
::Logger.send(:include, InstanceMethods)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
class Rack < Probe
|
|
4
|
+
class Middleware
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
context = Contexts::HTTPRequests::Rack.new(env)
|
|
11
|
+
CurrentContext.add(context) do
|
|
12
|
+
@app.call(env)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :middleware, :insert_before
|
|
18
|
+
|
|
19
|
+
def initialize(middleware, insert_before)
|
|
20
|
+
if middleware.nil?
|
|
21
|
+
raise RequirementNotMetError.new("The middleware class attribute is not set. " +
|
|
22
|
+
"We need a middleware to insert the probe.")
|
|
23
|
+
end
|
|
24
|
+
@middleware = middleware
|
|
25
|
+
@insert_before = insert_before
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def insert!
|
|
29
|
+
return true if middleware.instance_variable_get(:"@_timber_inserted") == true
|
|
30
|
+
# Rails uses a proxy :/
|
|
31
|
+
middleware.instance_variable_set(:"@_timber_inserted", true)
|
|
32
|
+
middleware.insert_before insert_before, Middleware
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Timber
|
|
2
|
+
module Probes
|
|
3
|
+
class Server < Probe
|
|
4
|
+
class << self
|
|
5
|
+
attr_accessor :inserted
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def insert!
|
|
9
|
+
return true if self.class.inserted == true
|
|
10
|
+
context = Contexts::Server.new
|
|
11
|
+
# Note we don't use a block here, this is because
|
|
12
|
+
# the context is persistent.
|
|
13
|
+
CurrentContext.add(context)
|
|
14
|
+
self.class.inserted = true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "timber/probes/action_controller_base"
|
|
2
|
+
require "timber/probes/action_dispatch_debug_exceptions"
|
|
3
|
+
require "timber/probes/active_support_log_subscriber"
|
|
4
|
+
require "timber/probes/heroku"
|
|
5
|
+
require "timber/probes/logger"
|
|
6
|
+
require "timber/probes/rack"
|
|
7
|
+
require "timber/probes/server"
|
|
8
|
+
|
|
9
|
+
module Timber
|
|
10
|
+
module Probes
|
|
11
|
+
def self.insert!(middleware, insert_before)
|
|
12
|
+
# Persistent probes. Order is relevant.
|
|
13
|
+
Server.insert!
|
|
14
|
+
Heroku.insert!
|
|
15
|
+
|
|
16
|
+
# Transient probes, sorted alphabetically
|
|
17
|
+
ActionControllerBase.insert!
|
|
18
|
+
ActionDispatchDebugExceptions.insert!
|
|
19
|
+
ActiveSupportLogSubscriber.insert!
|
|
20
|
+
Logger.insert!
|
|
21
|
+
Rack.insert!(middleware, insert_before)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/timber.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# core classes
|
|
2
|
+
require "json" # brings to_json to the core classes
|
|
3
|
+
|
|
4
|
+
# Base (must come first, order matters)
|
|
5
|
+
require "timber/macros"
|
|
6
|
+
require "timber/patterns"
|
|
7
|
+
require "timber/config"
|
|
8
|
+
require "timber/context"
|
|
9
|
+
require "timber/log_device"
|
|
10
|
+
require "timber/probe"
|
|
11
|
+
require "timber/version"
|
|
12
|
+
|
|
13
|
+
# Other (sorted alphabetically)
|
|
14
|
+
require "timber/api_settings"
|
|
15
|
+
require "timber/bootstrap"
|
|
16
|
+
require "timber/context_snapshot"
|
|
17
|
+
require "timber/contexts"
|
|
18
|
+
require "timber/current_context"
|
|
19
|
+
require "timber/current_line_indexes"
|
|
20
|
+
require "timber/internal_logger"
|
|
21
|
+
require "timber/log_devices"
|
|
22
|
+
require "timber/log_line"
|
|
23
|
+
require "timber/logger"
|
|
24
|
+
require "timber/probes"
|
|
25
|
+
|
|
26
|
+
# Load frameworks last
|
|
27
|
+
require "timber/frameworks"
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Base
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
# Testing
|
|
6
|
+
require 'pry'
|
|
7
|
+
require 'rspec'
|
|
8
|
+
require 'rspec/its'
|
|
9
|
+
require 'rspec/mocks'
|
|
10
|
+
|
|
11
|
+
# Support files, order is relevant
|
|
12
|
+
require File.join(File.dirname(__FILE__), 'support', 'coveralls')
|
|
13
|
+
require File.join(File.dirname(__FILE__), 'support', 'socket_hostname')
|
|
14
|
+
require File.join(File.dirname(__FILE__), 'support', 'simplecov')
|
|
15
|
+
require File.join(File.dirname(__FILE__), 'support', 'timecop')
|
|
16
|
+
require File.join(File.dirname(__FILE__), 'support', 'webmock')
|
|
17
|
+
require File.join(File.dirname(__FILE__), 'support', 'timber')
|
|
18
|
+
require File.join(File.dirname(__FILE__), 'support', 'rails')
|
|
19
|
+
require File.join(File.dirname(__FILE__), 'support', 'action_controller')
|
|
20
|
+
require File.join(File.dirname(__FILE__), 'support', 'action_view')
|
|
21
|
+
require File.join(File.dirname(__FILE__), 'support', 'active_record')
|
|
22
|
+
|
|
23
|
+
RSpec.configure do |config|
|
|
24
|
+
config.color = true
|
|
25
|
+
config.order = :random
|
|
26
|
+
config.warnings = false
|
|
27
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
|
|
3
|
+
ActiveRecord::Base.logger = Rails.logger
|
|
4
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
|
5
|
+
ActiveRecord::Schema.define do
|
|
6
|
+
self.verbose = false
|
|
7
|
+
|
|
8
|
+
create_table :organizations, :force => true do |t|
|
|
9
|
+
t.string :name
|
|
10
|
+
|
|
11
|
+
t.timestamps :null => false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
create_table :users, :force => true do |t|
|
|
15
|
+
t.integer :age
|
|
16
|
+
t.string :email
|
|
17
|
+
t.string :first_name
|
|
18
|
+
|
|
19
|
+
t.timestamps :null => false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Organization < ::ActiveRecord::Base
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class User < ::ActiveRecord::Base
|
|
28
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
I am a partial
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
I am a template
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "rails"
|
|
2
|
+
|
|
3
|
+
log_dev = Timber::LogDevices::IO.new
|
|
4
|
+
Rails.logger = Timber::Logger.new(log_dev)
|
|
5
|
+
Rails.logger.level = ::Logger::FATAL
|
|
6
|
+
|
|
7
|
+
class RailsApp < Rails::Application
|
|
8
|
+
if ::Rails.version =~ /^3\./
|
|
9
|
+
config.secret_token = '1e05af2b349457936a41427e63450937'
|
|
10
|
+
else
|
|
11
|
+
config.secret_key_base = '1e05af2b349457936a41427e63450937'
|
|
12
|
+
end
|
|
13
|
+
config.active_support.deprecation = :stderr
|
|
14
|
+
config.eager_load = false
|
|
15
|
+
config.log_level = :fatal
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
RailsApp.initialize!
|
|
19
|
+
|
|
20
|
+
module Support
|
|
21
|
+
module Rails
|
|
22
|
+
def dispatch_rails_request(path)
|
|
23
|
+
application = ::Rails.application
|
|
24
|
+
env = application.respond_to?(:env_config) ? application.env_config.clone : application.env_defaults.clone
|
|
25
|
+
env["rack.request.cookie_hash"] = {}.with_indifferent_access
|
|
26
|
+
::Rack::MockRequest.new(application).get(path, env)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
RSpec.configure do |config|
|
|
32
|
+
config.include Support::Rails
|
|
33
|
+
end
|