timber 1.0.3

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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +6 -0
  5. data/Appraisals +41 -0
  6. data/Gemfile +30 -0
  7. data/LICENSE.md +15 -0
  8. data/README.md +194 -0
  9. data/circle.yml +33 -0
  10. data/lib/timber/config.rb +18 -0
  11. data/lib/timber/context.rb +17 -0
  12. data/lib/timber/contexts/custom.rb +27 -0
  13. data/lib/timber/contexts/http.rb +28 -0
  14. data/lib/timber/contexts/organization.rb +35 -0
  15. data/lib/timber/contexts/user.rb +36 -0
  16. data/lib/timber/contexts.rb +10 -0
  17. data/lib/timber/current_context.rb +43 -0
  18. data/lib/timber/event.rb +17 -0
  19. data/lib/timber/events/controller_call.rb +40 -0
  20. data/lib/timber/events/custom.rb +42 -0
  21. data/lib/timber/events/exception.rb +35 -0
  22. data/lib/timber/events/http_request.rb +50 -0
  23. data/lib/timber/events/http_response.rb +36 -0
  24. data/lib/timber/events/sql_query.rb +26 -0
  25. data/lib/timber/events/template_render.rb +26 -0
  26. data/lib/timber/events.rb +37 -0
  27. data/lib/timber/frameworks/rails.rb +13 -0
  28. data/lib/timber/frameworks.rb +19 -0
  29. data/lib/timber/log_devices/http.rb +87 -0
  30. data/lib/timber/log_devices.rb +8 -0
  31. data/lib/timber/log_entry.rb +59 -0
  32. data/lib/timber/logger.rb +142 -0
  33. data/lib/timber/probe.rb +23 -0
  34. data/lib/timber/probes/action_controller_log_subscriber/log_subscriber.rb +64 -0
  35. data/lib/timber/probes/action_controller_log_subscriber.rb +20 -0
  36. data/lib/timber/probes/action_dispatch_debug_exceptions.rb +80 -0
  37. data/lib/timber/probes/action_view_log_subscriber/log_subscriber.rb +62 -0
  38. data/lib/timber/probes/action_view_log_subscriber.rb +20 -0
  39. data/lib/timber/probes/active_record_log_subscriber/log_subscriber.rb +29 -0
  40. data/lib/timber/probes/active_record_log_subscriber.rb +20 -0
  41. data/lib/timber/probes/rack_http_context.rb +51 -0
  42. data/lib/timber/probes/rails_rack_logger.rb +76 -0
  43. data/lib/timber/probes.rb +21 -0
  44. data/lib/timber/util/active_support_log_subscriber.rb +33 -0
  45. data/lib/timber/util/hash.rb +14 -0
  46. data/lib/timber/util.rb +8 -0
  47. data/lib/timber/version.rb +3 -0
  48. data/lib/timber.rb +22 -0
  49. data/spec/spec_helper.rb +30 -0
  50. data/spec/support/action_controller.rb +4 -0
  51. data/spec/support/action_view.rb +4 -0
  52. data/spec/support/active_record.rb +28 -0
  53. data/spec/support/coveralls.rb +2 -0
  54. data/spec/support/rails/templates/_partial.html +1 -0
  55. data/spec/support/rails/templates/template.html +1 -0
  56. data/spec/support/rails.rb +37 -0
  57. data/spec/support/simplecov.rb +9 -0
  58. data/spec/support/socket_hostname.rb +12 -0
  59. data/spec/support/timber.rb +4 -0
  60. data/spec/support/timecop.rb +3 -0
  61. data/spec/support/webmock.rb +2 -0
  62. data/spec/timber/events_spec.rb +55 -0
  63. data/spec/timber/log_devices/http_spec.rb +62 -0
  64. data/spec/timber/logger_spec.rb +89 -0
  65. data/spec/timber/probes/action_controller_log_subscriber_spec.rb +70 -0
  66. data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +51 -0
  67. data/spec/timber/probes/action_view_log_subscriber_spec.rb +61 -0
  68. data/spec/timber/probes/active_record_log_subscriber_spec.rb +52 -0
  69. data/spec/timber/probes/rack_http_context_spec.rb +54 -0
  70. data/spec/timber/probes/rails_rack_logger_spec.rb +46 -0
  71. data/timber.gemspec +22 -0
  72. metadata +149 -0
@@ -0,0 +1,80 @@
1
+ module Timber
2
+ module Probes
3
+ # Reponsible for automatically tracking exception events in Rails applications. While
4
+ # still preserving the default log style.
5
+ class ActionDispatchDebugExceptions < Probe
6
+ # For Rails >= 3.1
7
+ # @private
8
+ module DebugExceptionsInstanceMethods
9
+ def self.included(klass)
10
+ klass.class_eval do
11
+ private
12
+ def log_error(request, wrapper)
13
+ logger = logger(request)
14
+ return unless logger
15
+
16
+ exception = wrapper.exception
17
+
18
+ trace = wrapper.application_trace
19
+ trace = wrapper.framework_trace if trace.empty?
20
+
21
+ event = Events::Exception.new(
22
+ name: exception.class.name,
23
+ exception_message: exception.message,
24
+ backtrace: trace
25
+ )
26
+
27
+ logger.fatal event
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ # For Rails < 3.1
34
+ # @private
35
+ module ShowExceptionsInstanceMethods #:nodoc:
36
+ def self.included(klass)
37
+ klass.class_eval do
38
+ private
39
+ # We have to monkey patch because ruby < 2.0 does not support prepend.
40
+ alias_method :_timber_old_log_error, :log_error
41
+
42
+ def log_error(exception)
43
+ return unless logger
44
+
45
+ event = Events::Exception.new(
46
+ name: exception.class.name,
47
+ exception_message: exception.message,
48
+ backtrace: application_trace(exception)
49
+ )
50
+
51
+ logger.fatal event
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def initialize
58
+ begin
59
+ # Rails >= 3.1
60
+ require "action_dispatch/middleware/debug_exceptions"
61
+ rescue LoadError
62
+ # Rails < 3.1
63
+ require "action_dispatch/middleware/show_exceptions"
64
+ end
65
+ rescue LoadError => e
66
+ raise RequirementNotMetError.new(e.message)
67
+ end
68
+
69
+ def insert!
70
+ if defined?(::ActionDispatch::DebugExceptions)
71
+ return true if ::ActionDispatch::DebugExceptions.include?(DebugExceptionsInstanceMethods)
72
+ ::ActionDispatch::DebugExceptions.send(:include, DebugExceptionsInstanceMethods)
73
+ else
74
+ return true if ::ActionDispatch::ShowExceptions.include?(ShowExceptionsInstanceMethods)
75
+ ::ActionDispatch::ShowExceptions.send(:include, ShowExceptionsInstanceMethods)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,62 @@
1
+ module Timber
2
+ module Probes
3
+ class ActionViewLogSubscriber < Probe
4
+ # The log subscriber that replaces the default `ActionView::LogSubscriber`.
5
+ # The intent of this subscriber is to, as transparently as possible, properly
6
+ # track events that are being logged here. This LogSubscriber will never change
7
+ # default behavior / log messages.
8
+ class LogSubscriber < ::ActionView::LogSubscriber
9
+ def render_template(event)
10
+ info do
11
+ full_name = from_rails_root(event.payload[:identifier])
12
+ message = " Rendered #{full_name}"
13
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
14
+ message << " (#{event.duration.round(1)}ms)"
15
+
16
+ Events::TemplateRender.new(
17
+ name: full_name,
18
+ time_ms: event.duration,
19
+ message: message
20
+ )
21
+ end
22
+ end
23
+
24
+ def render_partial(event)
25
+ info do
26
+ full_name = from_rails_root(event.payload[:identifier])
27
+ message = " Rendered #{full_name}"
28
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
29
+ message << " (#{event.duration.round(1)}ms)"
30
+ message << " #{cache_message(event.payload)}" if event.payload.key?(:cache_hit)
31
+
32
+ Events::TemplateRender.new(
33
+ name: full_name,
34
+ time_ms: event.duration,
35
+ message: message
36
+ )
37
+ end
38
+ end
39
+
40
+ def render_collection(event)
41
+ info do
42
+ identifier = event.payload[:identifier] || "templates"
43
+ full_name = from_rails_root(identifier)
44
+ message = " Rendered collection of #{full_name}" \
45
+ " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
46
+
47
+ Events::TemplateRender.new(
48
+ name: full_name,
49
+ time_ms: event.duration,
50
+ message: message
51
+ )
52
+ end
53
+ end
54
+
55
+ private
56
+ def log_rendering_start(payload)
57
+ # Rails, you silly. We don't need to template rendering messages :)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ module Timber
2
+ module Probes
3
+ # Reponsible for automatically tracking template rendering events in `ActionView` while
4
+ # still preserving the default log style.
5
+ class ActionViewLogSubscriber < Probe
6
+ def initialize
7
+ require "action_view/log_subscriber"
8
+ require "timber/probes/action_view_log_subscriber/log_subscriber"
9
+ rescue LoadError => e
10
+ raise RequirementNotMetError.new(e.message)
11
+ end
12
+
13
+ def insert!
14
+ return true if Util::ActiveSupportLogSubscriber.subscribed?(:action_view, LogSubscriber)
15
+ Util::ActiveSupportLogSubscriber.unsubscribe(:action_view, ::ActionView::LogSubscriber)
16
+ LogSubscriber.attach_to(:action_view)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ module Timber
2
+ module Probes
3
+ class ActiveRecordLogSubscriber < Probe
4
+ # The log subscriber that replaces the default `ActiveRecord::LogSubscriber`.
5
+ # The intent of this subscriber is to, as transparently as possible, properly
6
+ # track events that are being logged here. This LogSubscriber will never change
7
+ # default behavior / log messages.
8
+ class LogSubscriber < ::ActiveRecord::LogSubscriber #:nodoc:
9
+ def sql(event)
10
+ super(event)
11
+
12
+ payload = event.payload
13
+ event = Events::SQLQuery.new(
14
+ sql: payload[:sql],
15
+ time_ms: event.duration,
16
+ message: @message
17
+ )
18
+
19
+ logger.debug event
20
+ end
21
+
22
+ private
23
+ def debug(message)
24
+ @message = message
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module Timber
2
+ module Probes
3
+ # Reponsible for automatimcally tracking SQL query events in `ActiveRecord`, while still
4
+ # preserving the default log style.
5
+ class ActiveRecordLogSubscriber < Probe
6
+ def initialize
7
+ require "active_record/log_subscriber"
8
+ require "timber/probes/active_record_log_subscriber/log_subscriber"
9
+ rescue LoadError => e
10
+ raise RequirementNotMetError.new(e.message)
11
+ end
12
+
13
+ def insert!
14
+ return true if Util::ActiveSupportLogSubscriber.subscribed?(:active_record, LogSubscriber)
15
+ Util::ActiveSupportLogSubscriber.unsubscribe(:active_record, ::ActiveRecord::LogSubscriber)
16
+ LogSubscriber.attach_to(:active_record)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ module Timber
2
+ module Probes
3
+ # Reponsible for automatically adding the HTTP context for applications that use `Rack`.
4
+ class RackHTTPContext < Probe
5
+ class Middleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ request = ::Rack::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_id(env)
17
+ )
18
+ CurrentContext.instance.with(context) do
19
+ @app.call(env)
20
+ end
21
+ end
22
+
23
+ private
24
+ def request_id(env)
25
+ env["X-Request-ID"] ||
26
+ env["X-Request-Id"] ||
27
+ env["action_dispatch.request_id"]
28
+ end
29
+ end
30
+
31
+ attr_reader :middleware, :insert_before
32
+
33
+ def initialize(middleware, insert_before)
34
+ if middleware.nil?
35
+ raise RequirementNotMetError.new("The middleware class attribute is not set. " +
36
+ "We need a middleware to insert the probe.")
37
+ end
38
+ @middleware = middleware
39
+ @insert_before = insert_before
40
+ end
41
+
42
+ def insert!
43
+ var_name = :"@_timber_rack_http_inserted"
44
+ return true if middleware.instance_variable_defined?(var_name) && middleware.instance_variable_get(var_name) == true
45
+ # Rails uses a proxy :/, so we need to do this instance variable hack
46
+ middleware.instance_variable_set(var_name, true)
47
+ middleware.insert_before insert_before, Middleware
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ module Timber
2
+ module Probes
3
+ # Responsible for automatically tracking the http request events for applications
4
+ # that use `Rack`.
5
+ class RailsRackLogger < Probe
6
+ module InstanceMethods
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ protected
10
+ if klass.method_defined?(:started_request_message)
11
+ def started_request_message(request)
12
+ http_request_event(request)
13
+ end
14
+ elsif klass.method_defined?(:before_dispatch)
15
+ def before_dispatch(env)
16
+ request = ActionDispatch::Request.new(env)
17
+ info do
18
+ http_request_event(request)
19
+ end
20
+ end
21
+ end
22
+
23
+ def http_request_event(request)
24
+ # No idea why rails 3.X returns a "/" :/
25
+ referrer = request.referer == "/" ? nil : request.referer
26
+ Events::HTTPRequest.new(
27
+ host: request.host,
28
+ method: request.request_method,
29
+ path: request.filtered_path,
30
+ port: request.port,
31
+ query_params: request.GET,
32
+ content_type: request.content_type,
33
+ remote_addr: request.ip,
34
+ referrer: referrer,
35
+ request_id: request_id(request.env),
36
+ user_agent: request.user_agent
37
+ )
38
+ end
39
+
40
+ def request_id(env)
41
+ env["X-Request-ID"] ||
42
+ env["X-Request-Id"] ||
43
+ env["action_dispatch.request_id"]
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ module BeforeDispatchInstanceMethods
50
+ def self.included(klass)
51
+ klass.class_eval do
52
+ protected
53
+ def before_dispatch(env)
54
+ request = ActionDispatch::Request.new(env)
55
+ path = request.filtered_path
56
+
57
+ info "\n\nStarted #{request.request_method} \"#{path}\" " \
58
+ "for #{request.ip} at #{Time.now.to_default_s}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def initialize
65
+ require "rails/rack/logger"
66
+ rescue LoadError => e
67
+ raise RequirementNotMetError.new(e.message)
68
+ end
69
+
70
+ def insert!
71
+ return true if ::Rails::Rack::Logger.include?(InstanceMethods)
72
+ ::Rails::Rack::Logger.send(:include, InstanceMethods)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,21 @@
1
+ require "timber/probes/action_controller_log_subscriber"
2
+ require "timber/probes/action_dispatch_debug_exceptions"
3
+ require "timber/probes/action_view_log_subscriber"
4
+ require "timber/probes/active_record_log_subscriber"
5
+ require "timber/probes/rack_http_context"
6
+ require "timber/probes/rails_rack_logger"
7
+
8
+ module Timber
9
+ # Namespace for all probes.
10
+ # @private
11
+ module Probes
12
+ def self.insert!(middleware, insert_before)
13
+ ActionControllerLogSubscriber.insert!
14
+ ActionDispatchDebugExceptions.insert!
15
+ ActionViewLogSubscriber.insert!
16
+ ActiveRecordLogSubscriber.insert!
17
+ RackHTTPContext.insert!(middleware, insert_before)
18
+ RailsRackLogger.insert!
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ module Timber
2
+ module Util
3
+ # @private
4
+ module ActiveSupportLogSubscriber
5
+ extend self
6
+
7
+ def find(component, type)
8
+ ActiveSupport::LogSubscriber.log_subscribers.find do |subscriber|
9
+ subscriber.class == type
10
+ end
11
+ end
12
+
13
+ def subscribed?(component, type)
14
+ !find(component, type).nil?
15
+ end
16
+
17
+ def unsubscribe(component, type)
18
+ subscriber = find(component, type)
19
+
20
+ if subscriber
21
+ events = subscriber.public_methods(false).reject { |method| method.to_s == 'call' }
22
+ events.each do |event|
23
+ ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
24
+ if listener.instance_variable_get('@delegate') == subscriber
25
+ ActiveSupport::Notifications.unsubscribe listener
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ module Timber
2
+ module Util
3
+ # @private
4
+ module Hash
5
+ extend self
6
+
7
+ def compact(hash)
8
+ hash.select do |k, v|
9
+ v != nil && v != {} && v != []
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ require "timber/util/active_support_log_subscriber"
2
+ require "timber/util/hash"
3
+
4
+ module Timber
5
+ # @private
6
+ module Util
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Timber
2
+ VERSION = "1.0.3"
3
+ end
data/lib/timber.rb ADDED
@@ -0,0 +1,22 @@
1
+ # core classes
2
+ require "json" # brings to_json to the core classes
3
+
4
+ # Base (must come first, order matters)
5
+ require "timber/config"
6
+ require "timber/context"
7
+ require "timber/event"
8
+ require "timber/probe"
9
+ require "timber/util"
10
+ require "timber/version"
11
+
12
+ # Other (sorted alphabetically)
13
+ require "timber/contexts"
14
+ require "timber/current_context"
15
+ require "timber/events"
16
+ require "timber/log_devices"
17
+ require "timber/log_entry"
18
+ require "timber/logger"
19
+ require "timber/probes"
20
+
21
+ # Load frameworks
22
+ require "timber/frameworks"
@@ -0,0 +1,30 @@
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
+
19
+ if !ENV["RAILS_23"]
20
+ require File.join(File.dirname(__FILE__), 'support', 'rails')
21
+ require File.join(File.dirname(__FILE__), 'support', 'action_controller')
22
+ require File.join(File.dirname(__FILE__), 'support', 'action_view')
23
+ require File.join(File.dirname(__FILE__), 'support', 'active_record')
24
+ end
25
+
26
+ RSpec.configure do |config|
27
+ config.color = true
28
+ config.order = :random
29
+ config.warnings = false
30
+ 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,37 @@
1
+ require "rails"
2
+
3
+ # Defualt the rails logger to nothing, each test shoould be
4
+ # responsible for setting up the logger
5
+ logger = ::Logger.new(nil)
6
+ Rails.logger = logger
7
+
8
+ class RailsApp < Rails::Application
9
+ if ::Rails.version =~ /^3\./
10
+ config.secret_token = '1e05af2b349457936a41427e63450937'
11
+ else
12
+ config.secret_key_base = '1e05af2b349457936a41427e63450937'
13
+ end
14
+ config.active_support.deprecation = :stderr
15
+ config.eager_load = false
16
+ end
17
+
18
+ RailsApp.initialize!
19
+
20
+ module Support
21
+ module Rails
22
+ def dispatch_rails_request(path, additional_env_options = {})
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
+ env["REMOTE_ADDR"] = "123.456.789.10"
27
+ env["X-Request-Id"] = "unique-request-id-1234"
28
+ env["action_dispatch.request_id"] = env["X-Request-Id"]
29
+ env = env.merge(additional_env_options)
30
+ ::Rack::MockRequest.new(application).get(path, env)
31
+ end
32
+ end
33
+ end
34
+
35
+ RSpec.configure do |config|
36
+ config.include Support::Rails
37
+ end
@@ -0,0 +1,9 @@
1
+ require 'simplecov'
2
+
3
+ # save to CircleCI's artifacts directory if we're on CircleCI
4
+ if ENV['CIRCLE_ARTIFACTS']
5
+ dir = File.join(ENV['CIRCLE_ARTIFACTS'], "coverage")
6
+ SimpleCov.coverage_dir(dir)
7
+ end
8
+
9
+ SimpleCov.start
@@ -0,0 +1,12 @@
1
+ require "socket"
2
+
3
+ # Stub out the hostname for tests only. This can't use a normal stub in the
4
+ # test life cycle since our test rails app is loaded once upon initialization.
5
+ # In other words, the rails app gets loaded with the server context inserted
6
+ # before any tests are run.
7
+
8
+ class ::Socket
9
+ def self.gethostname
10
+ "computer-name.domain.com"
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # Must require last in order to be mocked via webmock
2
+ require 'timber'
3
+
4
+ Timber::Config.instance.logger = ::Logger.new(nil)
@@ -0,0 +1,3 @@
1
+ require 'timecop'
2
+
3
+ Timecop.safe_mode = true
@@ -0,0 +1,2 @@
1
+ require 'webmock/rspec'
2
+ WebMock.disable_net_connect!
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ describe Timber::Events, :rails_23 => true do
4
+ describe ".build" do
5
+ it "should build a Timber::Event" do
6
+ event = Timber::Events::Custom.new(
7
+ type: :payment_rejected,
8
+ message: "Payment rejected",
9
+ data: {customer_id: "abcd1234", amount: 100}
10
+ )
11
+ built_event = Timber::Events.build(event)
12
+ expect(built_event).to eq(event)
13
+ end
14
+
15
+ it "should use #to_timber_event" do
16
+ PaymentRejectedEvent = Struct.new(:customer_id, :amount) do
17
+ def to_timber_event
18
+ Timber::Events::Custom.new(
19
+ type: :payment_rejected,
20
+ message: "Payment rejected for #{customer_id}",
21
+ data: respond_to?(:hash) ? hash : to_hash
22
+ )
23
+ end
24
+ end
25
+ built_event = Timber::Events.build(PaymentRejectedEvent.new("abcd1234", 100))
26
+ expect(built_event).to be_kind_of(Timber::Events::Custom)
27
+ expect(built_event.type).to eq(:payment_rejected)
28
+ expect(built_event.message).to eq("Payment rejected for abcd1234")
29
+ Object.send(:remove_const, :PaymentRejectedEvent)
30
+ end
31
+
32
+ it "should accept a properly structured hash" do
33
+ built_event = Timber::Events.build({type: :payment_rejected, message: "Payment rejected", data: {customer_id: "abcd1234", amount: 100}})
34
+ expect(built_event).to be_kind_of(Timber::Events::Custom)
35
+ expect(built_event.type).to eq(:payment_rejected)
36
+ expect(built_event.message).to eq("Payment rejected")
37
+ end
38
+
39
+ it "should accept a struct" do
40
+ PaymentRejectedEvent = Struct.new(:customer_id, :amount) do
41
+ def message; "Payment rejected for #{customer_id}"; end
42
+ def type; :payment_rejected; end
43
+ end
44
+ built_event = Timber::Events.build(PaymentRejectedEvent.new("abcd1234", 100))
45
+ expect(built_event).to be_kind_of(Timber::Events::Custom)
46
+ expect(built_event.type).to eq(:payment_rejected)
47
+ expect(built_event.message).to eq("Payment rejected for abcd1234")
48
+ Object.send(:remove_const, :PaymentRejectedEvent)
49
+ end
50
+
51
+ it "should return nil for unsupported" do
52
+ expect(Timber::Events.build(1)).to be_nil
53
+ end
54
+ end
55
+ end