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.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +34 -0
  3. data/.gitignore +14 -0
  4. data/Appraisals +37 -0
  5. data/Gemfile +22 -0
  6. data/LICENSE +38 -0
  7. data/README.md +22 -0
  8. data/Rakefile +4 -0
  9. data/TODO +4 -0
  10. data/benchmark/README.md +26 -0
  11. data/benchmark/rails_request.rb +68 -0
  12. data/benchmark/support/rails.rb +69 -0
  13. data/circle.yml +27 -0
  14. data/docs/installation/rails_on_heroku.md +31 -0
  15. data/docs/installation/rails_over_http.md +22 -0
  16. data/gemfiles/rails_3.0.X.gemfile +25 -0
  17. data/gemfiles/rails_3.1.X.gemfile +25 -0
  18. data/gemfiles/rails_3.2.X.gemfile +25 -0
  19. data/gemfiles/rails_4.0.X.gemfile +26 -0
  20. data/gemfiles/rails_4.1.X.gemfile +26 -0
  21. data/gemfiles/rails_4.2.X.gemfile +26 -0
  22. data/gemfiles/rails_5.0.X.gemfile +26 -0
  23. data/gemfiles/rails_edge.gemfile +27 -0
  24. data/lib/timber/api_settings.rb +17 -0
  25. data/lib/timber/bootstrap.rb +45 -0
  26. data/lib/timber/config.rb +25 -0
  27. data/lib/timber/context.rb +76 -0
  28. data/lib/timber/context_snapshot.rb +64 -0
  29. data/lib/timber/contexts/dynamic_values.rb +59 -0
  30. data/lib/timber/contexts/exception.rb +40 -0
  31. data/lib/timber/contexts/http_request.rb +22 -0
  32. data/lib/timber/contexts/http_requests/action_controller_specific.rb +48 -0
  33. data/lib/timber/contexts/http_requests/rack/params.rb +26 -0
  34. data/lib/timber/contexts/http_requests/rack.rb +105 -0
  35. data/lib/timber/contexts/http_response.rb +19 -0
  36. data/lib/timber/contexts/http_responses/action_controller.rb +76 -0
  37. data/lib/timber/contexts/logger.rb +33 -0
  38. data/lib/timber/contexts/organization.rb +33 -0
  39. data/lib/timber/contexts/organizations/action_controller.rb +34 -0
  40. data/lib/timber/contexts/server.rb +21 -0
  41. data/lib/timber/contexts/servers/heroku_specific.rb +48 -0
  42. data/lib/timber/contexts/sql_queries/active_record.rb +30 -0
  43. data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +37 -0
  44. data/lib/timber/contexts/sql_queries/active_record_specific.rb +59 -0
  45. data/lib/timber/contexts/sql_query.rb +18 -0
  46. data/lib/timber/contexts/template_render.rb +17 -0
  47. data/lib/timber/contexts/template_renders/action_view.rb +29 -0
  48. data/lib/timber/contexts/template_renders/action_view_specific.rb +51 -0
  49. data/lib/timber/contexts/user.rb +39 -0
  50. data/lib/timber/contexts/users/action_controller.rb +34 -0
  51. data/lib/timber/contexts.rb +23 -0
  52. data/lib/timber/current_context.rb +58 -0
  53. data/lib/timber/current_line_indexes.rb +35 -0
  54. data/lib/timber/frameworks/rails.rb +24 -0
  55. data/lib/timber/frameworks.rb +21 -0
  56. data/lib/timber/internal_logger.rb +35 -0
  57. data/lib/timber/log_device.rb +40 -0
  58. data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +14 -0
  59. data/lib/timber/log_devices/heroku_logplex.rb +14 -0
  60. data/lib/timber/log_devices/http/log_pile.rb +86 -0
  61. data/lib/timber/log_devices/http/log_truck/delivery.rb +116 -0
  62. data/lib/timber/log_devices/http/log_truck.rb +87 -0
  63. data/lib/timber/log_devices/http.rb +28 -0
  64. data/lib/timber/log_devices/io/formatter.rb +46 -0
  65. data/lib/timber/log_devices/io/hybrid_formatter.rb +41 -0
  66. data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +36 -0
  67. data/lib/timber/log_devices/io/json_formatter.rb +11 -0
  68. data/lib/timber/log_devices/io/logfmt_formatter.rb +11 -0
  69. data/lib/timber/log_devices/io.rb +41 -0
  70. data/lib/timber/log_devices.rb +4 -0
  71. data/lib/timber/log_line.rb +33 -0
  72. data/lib/timber/logger.rb +20 -0
  73. data/lib/timber/macros/compactor.rb +16 -0
  74. data/lib/timber/macros/date_formatter.rb +9 -0
  75. data/lib/timber/macros/deep_merger.rb +11 -0
  76. data/lib/timber/macros/logfmt_encoder.rb +77 -0
  77. data/lib/timber/macros.rb +4 -0
  78. data/lib/timber/patterns/delegated_singleton.rb +21 -0
  79. data/lib/timber/patterns/to_json.rb +22 -0
  80. data/lib/timber/patterns/to_logfmt.rb +9 -0
  81. data/lib/timber/patterns.rb +3 -0
  82. data/lib/timber/probe.rb +21 -0
  83. data/lib/timber/probes/action_controller_base.rb +31 -0
  84. data/lib/timber/probes/action_dispatch_debug_exceptions.rb +57 -0
  85. data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +15 -0
  86. data/lib/timber/probes/active_support_log_subscriber/action_view.rb +26 -0
  87. data/lib/timber/probes/active_support_log_subscriber/active_record.rb +13 -0
  88. data/lib/timber/probes/active_support_log_subscriber.rb +62 -0
  89. data/lib/timber/probes/heroku.rb +30 -0
  90. data/lib/timber/probes/logger.rb +31 -0
  91. data/lib/timber/probes/rack.rb +36 -0
  92. data/lib/timber/probes/server.rb +18 -0
  93. data/lib/timber/probes.rb +24 -0
  94. data/lib/timber/version.rb +3 -0
  95. data/lib/timber.rb +27 -0
  96. data/spec/spec_helper.rb +27 -0
  97. data/spec/support/action_controller.rb +4 -0
  98. data/spec/support/action_view.rb +4 -0
  99. data/spec/support/active_record.rb +28 -0
  100. data/spec/support/coveralls.rb +2 -0
  101. data/spec/support/rails/templates/_partial.html +1 -0
  102. data/spec/support/rails/templates/template.html +1 -0
  103. data/spec/support/rails.rb +33 -0
  104. data/spec/support/simplecov.rb +9 -0
  105. data/spec/support/socket_hostname.rb +12 -0
  106. data/spec/support/timber.rb +23 -0
  107. data/spec/support/timecop.rb +3 -0
  108. data/spec/support/webmock.rb +2 -0
  109. data/spec/timber/bootstrap_spec.rb +31 -0
  110. data/spec/timber/context_snapshot_spec.rb +10 -0
  111. data/spec/timber/context_spec.rb +4 -0
  112. data/spec/timber/contexts/exception_spec.rb +34 -0
  113. data/spec/timber/contexts/organizations/action_controller_spec.rb +49 -0
  114. data/spec/timber/contexts/users/action_controller_spec.rb +65 -0
  115. data/spec/timber/current_line_indexes_spec.rb +40 -0
  116. data/spec/timber/frameworks/rails_spec.rb +9 -0
  117. data/spec/timber/log_devices/heroku_logplex_spec.rb +45 -0
  118. data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +66 -0
  119. data/spec/timber/log_devices/http/log_truck_spec.rb +65 -0
  120. data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +28 -0
  121. data/spec/timber/log_line_spec.rb +49 -0
  122. data/spec/timber/macros/compactor_spec.rb +19 -0
  123. data/spec/timber/macros/logfmt_encoder_spec.rb +89 -0
  124. data/spec/timber/patterns/to_json_spec.rb +40 -0
  125. data/spec/timber/probes/action_controller_base_spec.rb +43 -0
  126. data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +35 -0
  127. data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +44 -0
  128. data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +26 -0
  129. data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +45 -0
  130. data/spec/timber/probes/logger_spec.rb +20 -0
  131. data/spec/timber/probes/rack_spec.rb +26 -0
  132. data/timberio.gemspec +20 -0
  133. 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,4 @@
1
+ require "timber/macros/compactor"
2
+ require "timber/macros/date_formatter"
3
+ require "timber/macros/deep_merger"
4
+ require "timber/macros/logfmt_encoder"
@@ -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
@@ -0,0 +1,9 @@
1
+ module Timber
2
+ module Patterns
3
+ module ToLogfmt
4
+ def to_logfmt
5
+ @to_logfmt ||= Macros::LogfmtEncoder.encode(as_json).freeze
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ require "timber/patterns/delegated_singleton"
2
+ require "timber/patterns/to_json"
3
+ require "timber/patterns/to_logfmt"
@@ -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
@@ -0,0 +1,3 @@
1
+ module Timber
2
+ VERSION = "1.0.0.beta1"
3
+ 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"
@@ -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,4 @@
1
+ require "action_controller"
2
+
3
+ ActionController::Base.prepend_view_path("#{File.dirname(__FILE__)}/rails/templates")
4
+ ActionController::Base.logger = Rails.logger
@@ -0,0 +1,4 @@
1
+ require "action_view"
2
+
3
+ # Needed for the ActionView::LogSubscriber. If a logger is not present, nothing will be logged.
4
+ ActionView::Base.logger = Rails.logger if ActionView::Base.respond_to?(:logger=)
@@ -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,2 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
@@ -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