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