yarder 0.0.1

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 (77) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +87 -0
  3. data/Rakefile +38 -0
  4. data/lib/tasks/yarder_tasks.rake +4 -0
  5. data/lib/yarder.rb +15 -0
  6. data/lib/yarder/action_controller/log_subscriber.rb +74 -0
  7. data/lib/yarder/action_view/log_subscriber.rb +31 -0
  8. data/lib/yarder/active_record/log_subscriber.rb +75 -0
  9. data/lib/yarder/active_resource/log_subscriber.rb +34 -0
  10. data/lib/yarder/configuration.rb +8 -0
  11. data/lib/yarder/core_ext/object/blank.rb +105 -0
  12. data/lib/yarder/logger.rb +55 -0
  13. data/lib/yarder/rack/logger.rb +72 -0
  14. data/lib/yarder/railtie.rb +69 -0
  15. data/lib/yarder/tagged_logging.rb +102 -0
  16. data/lib/yarder/version.rb +3 -0
  17. data/test/action_controller/log_subscriber_test.rb +165 -0
  18. data/test/action_view/log_subscriber_test.rb +89 -0
  19. data/test/active_record/log_subscriber_test.rb +87 -0
  20. data/test/active_resource/log_subscriber_test.rb +42 -0
  21. data/test/dummy/README.rdoc +261 -0
  22. data/test/dummy/Rakefile +7 -0
  23. data/test/dummy/app/assets/javascripts/application.js +13 -0
  24. data/test/dummy/app/assets/javascripts/widgets.js +2 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
  27. data/test/dummy/app/assets/stylesheets/widgets.css +4 -0
  28. data/test/dummy/app/controllers/application_controller.rb +3 -0
  29. data/test/dummy/app/controllers/log_subscriber_controller.rb +56 -0
  30. data/test/dummy/app/controllers/widgets_controller.rb +83 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/helpers/widgets_helper.rb +2 -0
  33. data/test/dummy/app/models/widget.rb +3 -0
  34. data/test/dummy/app/views/customers/_customer.html.erb +1 -0
  35. data/test/dummy/app/views/good_customers/_good_customer.html.erb +1 -0
  36. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  37. data/test/dummy/app/views/test/_customer.erb +1 -0
  38. data/test/dummy/app/views/test/hello_world.erb +1 -0
  39. data/test/dummy/app/views/widgets/_form.html.erb +17 -0
  40. data/test/dummy/app/views/widgets/edit.html.erb +6 -0
  41. data/test/dummy/app/views/widgets/index.html.erb +21 -0
  42. data/test/dummy/app/views/widgets/new.html.erb +5 -0
  43. data/test/dummy/app/views/widgets/show.html.erb +5 -0
  44. data/test/dummy/config.ru +4 -0
  45. data/test/dummy/config/application.rb +69 -0
  46. data/test/dummy/config/boot.rb +10 -0
  47. data/test/dummy/config/database.yml +22 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +31 -0
  50. data/test/dummy/config/environments/production.rb +64 -0
  51. data/test/dummy/config/environments/test.rb +35 -0
  52. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  53. data/test/dummy/config/initializers/inflections.rb +15 -0
  54. data/test/dummy/config/initializers/mime_types.rb +5 -0
  55. data/test/dummy/config/initializers/secret_token.rb +7 -0
  56. data/test/dummy/config/initializers/session_store.rb +8 -0
  57. data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
  58. data/test/dummy/config/locales/en.yml +5 -0
  59. data/test/dummy/config/routes.rb +69 -0
  60. data/test/dummy/db/migrate/20120927084605_create_widgets.rb +8 -0
  61. data/test/dummy/db/schema.rb +16 -0
  62. data/test/dummy/log/development.log +19 -0
  63. data/test/dummy/log/test.log +10694 -0
  64. data/test/dummy/public/404.html +26 -0
  65. data/test/dummy/public/422.html +26 -0
  66. data/test/dummy/public/500.html +25 -0
  67. data/test/dummy/public/favicon.ico +0 -0
  68. data/test/dummy/script/rails +6 -0
  69. data/test/logger_test.rb +126 -0
  70. data/test/rack/logger_test.rb +68 -0
  71. data/test/support/fake_models.rb +12 -0
  72. data/test/support/integration_case.rb +5 -0
  73. data/test/support/multibyte_test_helpers.rb +19 -0
  74. data/test/tagged_logging_test.rb +155 -0
  75. data/test/test_helper.rb +31 -0
  76. data/test/yarder_test.rb +7 -0
  77. metadata +236 -0
@@ -0,0 +1,55 @@
1
+ require 'logger'
2
+
3
+ module Yarder
4
+
5
+ # Based on the ActiveSupport::Logger (Formerly known as BufferedLogger)
6
+ class Logger < ::Logger
7
+ # Broadcasts logs to multiple loggers.
8
+ def self.broadcast(logger) # :nodoc:
9
+ Module.new do
10
+ define_method(:add) do |*args, &block|
11
+ logger.add(*args, &block)
12
+ super(*args, &block)
13
+ end
14
+
15
+ define_method(:<<) do |x|
16
+ logger << x
17
+ super(x)
18
+ end
19
+
20
+ define_method(:close) do
21
+ logger.close
22
+ super()
23
+ end
24
+
25
+ define_method(:progname=) do |name|
26
+ logger.progname = name
27
+ super(name)
28
+ end
29
+
30
+ define_method(:formatter=) do |formatter|
31
+ logger.formatter = formatter
32
+ super(formatter)
33
+ end
34
+
35
+ define_method(:level=) do |level|
36
+ logger.level = level
37
+ super(level)
38
+ end
39
+ end
40
+ end
41
+
42
+ def initialize(*args)
43
+ super
44
+ @formatter = SimpleFormatter.new
45
+ end
46
+
47
+ # Simple formatter which only displays the message.
48
+ class SimpleFormatter < ::Logger::Formatter
49
+ # This method is invoked when a log event occurs
50
+ def call(severity, timestamp, progname, msg)
51
+ "#{String === msg ? msg : msg.inspect}\n"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,72 @@
1
+ module Yarder
2
+
3
+ module Rack
4
+
5
+ class Logger
6
+
7
+ def initialize(app, tags = nil)
8
+ @app, @tags = app, tags.presence
9
+ end
10
+
11
+ def call(env)
12
+
13
+ t1 = Time.now
14
+ request = ActionDispatch::Request.new(env)
15
+
16
+ event = LogStash::Event.new
17
+ event.message = "#{request.request_method} #{request.filtered_path} for #{request.ip}"
18
+ event.fields['client_ip'] = request.ip
19
+ event.fields['method'] = request.request_method
20
+ event.fields['path'] = request.filtered_path
21
+ #TODO Should really move this into the base logger
22
+ event.source = "http://#{Socket.gethostname}#{request.filtered_path}"
23
+ event.type = "rails_json_log"
24
+
25
+ add_tags_to_logger(request) if @tags
26
+
27
+ Yarder.log_entries[Thread.current] = event
28
+
29
+ status, headers, response = @app.call(env)
30
+ [status, headers, response]
31
+
32
+ ensure
33
+ if event
34
+ event.fields['total_duration'] = Time.now - t1
35
+ event.fields['status'] = status
36
+
37
+ ['rendering','sql'].each do |type|
38
+ if event.fields[type] && !event.fields[type].empty?
39
+ duration = event.fields[type].inject(0) {|result, event| result += event[:duration].to_f }
40
+ event.fields["#{type}_duration"] = duration
41
+ end
42
+ end
43
+
44
+ Rails.logger.info event
45
+ end
46
+
47
+ Yarder.log_entries[Thread.current] = nil
48
+ end
49
+
50
+ def add_tags_to_logger(request)
51
+ tags = []
52
+ if @tags
53
+ @tags.each do |tag|
54
+ case tag
55
+ when Symbol
56
+ tags << {tag.to_s => request.send(tag) }
57
+ when Proc
58
+ tags << tag.call(request)
59
+ else
60
+ tags << tag
61
+ end
62
+ end
63
+ end
64
+
65
+ Rails.logger.push_request_tags(tags)
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,69 @@
1
+ require 'yarder/core_ext/object/blank'
2
+ require 'yarder/action_controller/log_subscriber'
3
+ require 'yarder/action_view/log_subscriber'
4
+ require 'yarder/active_record/log_subscriber' if defined?(ActiveRecord)
5
+ require 'yarder/active_resource/log_subscriber' if defined?(ActiveResource)
6
+
7
+ module Yarder
8
+
9
+ # Railtie to hook Yarder into Rails
10
+ #
11
+ # This Railtie hooks Yarder into Rails by adding middleware and loggers as well as
12
+ # adding a completely new set of LogSubscribers which parallel the default rails ones but
13
+ # are JSON based rather than string based
14
+ class Railtie < Rails::Railtie
15
+
16
+ initializer "yarder.swap_rack_logger_middleware" do |app|
17
+ app.middleware.swap(Rails::Rack::Logger, Yarder::Rack::Logger, app.config.log_tags)
18
+ end
19
+
20
+ # Silence the asset logger. This has to be done in a before_initialize block because
21
+ # the initializer is too late. (There might be a better part of the boot process for
22
+ # this, keep an eye out)
23
+ config.before_initialize do |app|
24
+ app.config.assets.logger = false
25
+
26
+ if app.config.logger.nil? && Rails.logger.class == ActiveSupport::TaggedLogging
27
+ raise IncompatibleLogger, "Please replace the default rails logger (See the " +
28
+ "Configuration section of the Yarder README)"
29
+ end
30
+
31
+ # Take the current logger and replace it with itself wrapped by the
32
+ # Yarder::TaggedLogging class
33
+ app.config.logger = Yarder::TaggedLogging.new(app.config.logger)
34
+ end
35
+
36
+
37
+ # We need to do the following in an after_initialize block to make sure we get all the
38
+ # subscribers. Ideally rails would allow us the ability to stop the LogSubscribers from
39
+ # registering themselves using a config option.
40
+ config.after_initialize do
41
+
42
+ # Kludge the removal of the default LogSubscribers for the moment. We will use the yarder
43
+ # LogSubscribers (since they subscribe to the same hooks in the public methods) to create
44
+ # a list of hooks we want to unsubscribe current subscribers from.
45
+ modules = ["ActionController", "ActionView"]
46
+ modules << "ActiveRecord" if defined?(ActiveRecord)
47
+ modules << "ActiveResource" if defined?(ActiveResource)
48
+
49
+ notifier = ActiveSupport::Notifications.notifier
50
+
51
+ modules.each do |mod|
52
+ "Yarder::#{mod}::LogSubscriber".constantize.instance_methods(false).each do |method|
53
+ notifier.listeners_for("#{method}.#{mod.underscore}").each do |subscriber|
54
+ ActiveSupport::Notifications.unsubscribe subscriber
55
+ end
56
+ end
57
+ end
58
+
59
+ # We then subscribe using the yarder versions of the default rails LogSubscribers
60
+ Yarder::ActionController::LogSubscriber.attach_to :action_controller
61
+ Yarder::ActionView::LogSubscriber.attach_to :action_view
62
+ Yarder::ActiveRecord::LogSubscriber.attach_to :active_record if defined?(ActiveRecord)
63
+ Yarder::ActiveResource::LogSubscriber.attach_to :active_resource if defined?(ActiveResource)
64
+
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,102 @@
1
+ require 'yarder/core_ext/object/blank'
2
+ require 'logger'
3
+ require 'yarder/logger'
4
+
5
+ module Yarder
6
+ # Wraps any standard Logger object to provide tagging capabilities.
7
+ #
8
+ # logger = Yarder::TaggedLogging.new(Logger.new(STDOUT))
9
+ # logger.tagged('BCX') { logger.info 'Stuff' } # Adds BCX to the @tags array and "Stuff" to the @message
10
+ # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Adds 'BCX' and 'Jason' to the @tags array and "Stuff"
11
+ # to the @message
12
+ # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Adds 'BCX' and 'Jason' to the @tags
13
+ # array and "Stuff" to the @message
14
+ #
15
+ # This is used by the default Rails.logger when the Yarder gem is added to a rails application
16
+ # to make it easy to stamp JSON logs with subdomains, request ids, and anything else
17
+ # to aid debugging of multi-user production applications.
18
+ module TaggedLogging
19
+ module Formatter # :nodoc:
20
+ # This method is invoked when a log event occurs.
21
+ def call(severity, timestamp, progname, msg)
22
+ @entry = nil
23
+ if msg.class == LogStash::Event
24
+ @entry = msg
25
+ else
26
+ @entry = LogStash::Event.new
27
+ @entry.message = msg
28
+ end
29
+ @entry.fields['severity'] = severity
30
+ process_tags(current_tags)
31
+ process_tags(current_request_tags)
32
+ #TODO Should we do anything with progname? What about source?
33
+ super(severity, timestamp, progname, @entry.to_json)
34
+ end
35
+
36
+ def tagged(*tags)
37
+ new_tags = push_tags(*tags)
38
+ yield self
39
+ ensure
40
+ pop_tags(new_tags.size)
41
+ end
42
+
43
+ def push_tags(*tags)
44
+ tags.flatten.reject(&:blank?).tap do |new_tags|
45
+ current_tags.concat new_tags
46
+ end
47
+ end
48
+
49
+ def push_request_tags(tags)
50
+ Thread.current[:activesupport_tagged_logging_request_tags] = tags
51
+ end
52
+
53
+ def process_tags(tags)
54
+ tags.each do |tag|
55
+ if tag.class == Hash
56
+ tag.each_pair do |k,v|
57
+ @entry.fields[k] = v
58
+ end
59
+ else
60
+ @entry.tags << tag
61
+ end
62
+ end
63
+ end
64
+
65
+ def pop_tags(size = 1)
66
+ current_tags.pop size
67
+ end
68
+
69
+ def clear_tags!
70
+ current_request_tags.clear
71
+ current_tags.clear
72
+ end
73
+
74
+ def current_tags
75
+ Thread.current[:activesupport_tagged_logging_tags] ||= []
76
+ end
77
+
78
+ def current_request_tags
79
+ Thread.current[:activesupport_tagged_logging_request_tags] ||= []
80
+ end
81
+
82
+ end
83
+
84
+ def self.new(logger)
85
+ # Ensure we set a default formatter so we aren't extending nil!
86
+ logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new
87
+ logger.formatter.extend Formatter
88
+ logger.extend(self)
89
+ end
90
+
91
+ delegate :push_tags, :push_request_tags, :pop_tags, :clear_tags!, to: :formatter
92
+
93
+ def tagged(*tags)
94
+ formatter.tagged(*tags) { yield self }
95
+ end
96
+
97
+ def flush
98
+ clear_tags!
99
+ super if defined?(super)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module Yarder
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,165 @@
1
+ require "active_support/log_subscriber/test_helper"
2
+ require "test_helper"
3
+
4
+ class ACLogSubscriberTest < ActionController::TestCase
5
+ tests LogSubscribersController
6
+ include ActiveSupport::LogSubscriber::TestHelper
7
+
8
+ def setup
9
+ super
10
+
11
+ @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__))
12
+ ActionController::Base.page_cache_directory = @cache_path
13
+ @controller.cache_store = :file_store, @cache_path
14
+
15
+ Yarder::ActionController::LogSubscriber.attach_to :action_controller
16
+ Yarder.log_entries[Thread.current] = LogStash::Event.new
17
+ @log_entry = Yarder.log_entries[Thread.current]
18
+ end
19
+
20
+ def teardown
21
+ super
22
+ ActiveSupport::LogSubscriber.log_subscribers.clear
23
+ FileUtils.rm_rf(@cache_path)
24
+ end
25
+
26
+ def set_logger(logger)
27
+ ActionController::Base.logger = logger
28
+ end
29
+
30
+ def test_start_processing
31
+ get :show, {:test => 'test'}
32
+ wait
33
+
34
+ assert_equal "LogSubscribersController", @log_entry.fields['controller']
35
+ assert_equal "show", @log_entry.fields['action']
36
+ assert_equal "html", @log_entry.fields['format']
37
+ end
38
+
39
+
40
+ def test_halted_callback
41
+ get :never_executed
42
+ wait
43
+
44
+ assert_equal ":redirector" ,@log_entry.fields['halted_callback']
45
+ end
46
+
47
+ def test_process_action
48
+ get :show
49
+ wait
50
+
51
+ assert_present @log_entry.fields['controller_duration']
52
+ end
53
+
54
+ def test_process_action_without_parameters
55
+ get :show
56
+ wait
57
+
58
+ assert_blank @log_entry.fields['parameters']
59
+ end
60
+
61
+ def test_process_action_with_parameters
62
+ get :show, :id => '10'
63
+ wait
64
+
65
+ assert_equal '10', @log_entry.fields['parameters']['id']
66
+ end
67
+
68
+ def test_process_action_with_wrapped_parameters
69
+ @request.env['CONTENT_TYPE'] = 'application/json'
70
+ post :show, :id => '10', :name => 'jose'
71
+ wait
72
+
73
+ assert_equal '10', @log_entry.fields['parameters']['id']
74
+ assert_equal 'jose', @log_entry.fields['parameters']['name']
75
+ end
76
+
77
+ def test_process_action_with_filter_parameters
78
+ @request.env["action_dispatch.parameter_filter"] = [:lifo, :amount]
79
+
80
+ get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
81
+ wait
82
+
83
+ params = @log_entry.fields['parameters']
84
+ assert_equal '[FILTERED]', params['amount']
85
+ assert_equal '[FILTERED]', params['lifo']
86
+ assert_equal '1', params['step']
87
+ end
88
+
89
+ def test_redirect_to
90
+ get :redirector
91
+ wait
92
+
93
+ assert_equal 'http://foo.bar/', @log_entry.fields['redirect_to']
94
+ end
95
+
96
+
97
+ def test_send_data
98
+ get :data_sender
99
+ wait
100
+
101
+ assert_equal 'file.txt', @log_entry.fields['send_data']
102
+ assert_present @log_entry.fields['send_data_duration']
103
+ end
104
+
105
+
106
+ def test_send_file
107
+ get :file_sender
108
+ wait
109
+
110
+ assert_match 'test/dummy/public/favicon.ico', @log_entry.fields['send_file']
111
+ assert_present @log_entry.fields['send_file_duration']
112
+ end
113
+
114
+ def test_with_fragment_cache
115
+ @controller.config.perform_caching = true
116
+ get :with_fragment_cache
117
+ wait
118
+
119
+ assert_present @log_entry.fields['cache']
120
+
121
+ assert_match('Read fragment', @log_entry.fields['cache'].first['type'])
122
+ assert_match('views/foo', @log_entry.fields['cache'].first['key_or_path'])
123
+
124
+ assert_match('Write fragment', @log_entry.fields['cache'].last['type'])
125
+ assert_match('views/foo', @log_entry.fields['cache'].last['key_or_path'])
126
+ ensure
127
+ LogSubscribersController.config.perform_caching = true
128
+ end
129
+
130
+
131
+ def test_with_fragment_cache_and_percent_in_key
132
+ @controller.config.perform_caching = true
133
+ get :with_fragment_cache_and_percent_in_key
134
+ wait
135
+
136
+ assert_present @log_entry.fields['cache']
137
+
138
+ assert_match('Read fragment', @log_entry.fields['cache'].first['type'])
139
+ assert_match('views/foo', @log_entry.fields['cache'].first['key_or_path'])
140
+
141
+ assert_match('Write fragment', @log_entry.fields['cache'].last['type'])
142
+ assert_match('views/foo', @log_entry.fields['cache'].last['key_or_path'])
143
+ ensure
144
+ LogSubscribersController.config.perform_caching = true
145
+ end
146
+
147
+ =begin TODO Figure out why this last test fails.
148
+ def test_with_page_cache
149
+ @controller.config.perform_caching = true
150
+ get :with_page_cache
151
+ wait
152
+
153
+ assert_present @log_entry.fields['cache']
154
+
155
+ assert_match('Write page', @log_entry.fields['cache'][1]['type'])
156
+ assert_match('index.html', @log_entry.fields['cache'][1]['key_or_path'])
157
+ ensure
158
+ @controller.config.perform_caching = true
159
+ end
160
+ =end
161
+
162
+ def logs
163
+ @logs ||= @logger.logged(:info)
164
+ end
165
+ end