lograge 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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in lograge.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ Lograge - Taming Rails' Default Request Logging
2
+ =======
3
+
4
+ Lograge is an attempt to bring sanity to Rails' noisy and unusable, unparsable
5
+ and, in the context of running multiple processes and servers, unreadable
6
+ default logging output. Rails' default approach to log everything is great
7
+ during development, it's terrible when running it in production. It pretty much
8
+ renders Rails logs useless to me.
9
+
10
+ Instead of trying solving the problem of having multiple lines per request by
11
+ switching Rails' logger for something that outputs syslog lines or adds a
12
+ request token, Lograge replaces Rails' request logging entirely, reducing the
13
+ output per request to a single line with all the important information, removing
14
+ all that clutter Rails likes to include and that gets mingled up so nicely when
15
+ multiple processes dump their output into a single file.
16
+
17
+ Instead of having an unparsable amount of logging output like this:
18
+
19
+ ```
20
+ Started GET "/" for 127.0.0.1 at 2012-03-10 14:28:14 +0100
21
+ Processing by HomeController#index as HTML
22
+ Rendered text template within layouts/application (0.0ms)
23
+ Rendered layouts/_assets.html.erb (2.0ms)
24
+ Rendered layouts/_top.html.erb (2.6ms)
25
+ Rendered layouts/_about.html.erb (0.3ms)
26
+ Rendered layouts/_google_analytics.html.erb (0.4ms)
27
+ Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
28
+ ```
29
+
30
+ you get a single line with all the important information, like this:
31
+
32
+ ```
33
+ GET /jobs/833552.json format=json action=jobs#show status=200 duration=58.33 view=40.43 db=15.26
34
+ ```
35
+
36
+ The second line is easy to grasp with a single glance and still includes all the
37
+ relevant information as simple key-value pairs. The syntax is heavily inspired
38
+ by the log output of the Heroku router. It doesn't include any timestamp by
39
+ default, instead it assumes you use a proper log formatter instead.
40
+
41
+ **Installation**
42
+
43
+ In your Gemfile
44
+
45
+ ```ruby
46
+ gem "lograge"
47
+ ```
48
+
49
+ Enable it for the relevant environments, e.g. production:
50
+
51
+ ```
52
+ # config/environments/production.rb
53
+ MyApp::Application.configure do
54
+ config.lograge.enabled = true
55
+ end
56
+ ```
57
+
58
+ Done.
59
+
60
+ **Internals**
61
+
62
+ Thanks to the notification system that was introduced in Rails 3, replacing the
63
+ logging is easy. Lograge unhooks all subscriptions from
64
+ `ActionController::LogSubscriber` and `ActionView::LogSubscriber`, and hooks in
65
+ its own log subscription, but only listening for two events: `process_action`
66
+ and `redirect_to`. It makes sure that only subscriptions from those two classes
67
+ are removed. If you happened to hook in your own, they'll be safe.
68
+
69
+ Unfortunately, when a redirect is triggered by your application's code,
70
+ ActionController fires two events. One for the redirect itself, and another one
71
+ when the request is finished. Unfortunately the final event doesn't include the
72
+ redirect, so Lograge stores the redirect URL as a thread-local attribute and
73
+ refers to it in `process_action`.
74
+
75
+ The event itself contains most of the relevant information to build up the log
76
+ line, including view processing and database access times.
77
+
78
+ While the LogSubscribers encapsulate most logging pretty nicely, there are still
79
+ two lines that show up no matter what. The first line that's output for every
80
+ Rails request, you know, this one:
81
+
82
+ And the verbose output coming from rack-cache:
83
+
84
+ Both are independent of the LogSubscribers, and both need to be shut up using
85
+ different means.
86
+
87
+ For the first one, the starting line of every Rails request log, Lograge removes
88
+ the `Rails::Rack::Logger` middleware from the stack. This may look like a drastic
89
+ means, but all the middleware does is log that useless line, log exceptions, and
90
+ create a request transaction id (Rails 3.2). A future version may replace with
91
+ its own middleware, that simply removes the log line.
92
+
93
+ To remove rack-cache's output (which is only enabled if caching in Rails is
94
+ enabled), Lograge disables verbosity for rack-cache, which is unfortunately
95
+ enabled by default.
96
+
97
+ There, a single line per request. Beautiful.
98
+
99
+ **What it doesn't do**
100
+
101
+ Lograge removes ActionView logging, which also includes rendering times for
102
+ partials. If you're into those, Lograge is probably not for you. In my honest
103
+ opinion, those rendering times don't belong in the log file, they should be
104
+ collected in a system like New Relic, Librato Metrics or some other metrics
105
+ service that allows graphing rendering percentiles. I assume this for everything
106
+ that represents a moving target. That kind of data is better off being
107
+ visualized in graphs than dumped (and ignored) in a log file.
108
+
109
+ **License**
110
+
111
+ MIT. Code extracted from [Travis CI](http://travis-ci.org).
112
+ (c) 2012 Mathias Meyer
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/lograge.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'lograge/version'
2
+ require 'lograge/log_subscriber'
3
+ require 'active_support/core_ext/module/attribute_accessors'
4
+ require 'active_support/core_ext/string/inflections'
5
+ require 'active_support/ordered_options'
6
+
7
+ module Lograge
8
+ mattr_accessor :logger
9
+
10
+ def self.remove_existing_log_subscriptions
11
+ %w(redirect_to process_action start_processing send_data write_fragment exist_fragment? send_file).each do |event|
12
+ unsubscribe_from_event(:action_controller, event)
13
+ end
14
+
15
+ %w{render_template render_partial render_collection}.each do |event|
16
+ unsubscribe_from_event(:action_view, event)
17
+ end
18
+ end
19
+
20
+ def self.unsubscribe_from_event(component, event)
21
+ delegate_type = component.to_s.classify
22
+ ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
23
+ if listener.inspect =~ /delegate[^a-z]+#{delegate_type}/
24
+ ActiveSupport::Notifications.unsubscribe listener
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.setup(app)
30
+ app.config.action_dispatch.rack_cache[:verbose] = false
31
+ require 'lograge/rails_ext/rack/logger'
32
+ Lograge.remove_existing_log_subscriptions
33
+ Lograge::RequestLogSubscriber.attach_to :action_controller
34
+ end
35
+ end
36
+
37
+ require 'lograge/railtie' if defined?(Rails)
@@ -0,0 +1,35 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/log_subscriber'
3
+
4
+ module Lograge
5
+ class RequestLogSubscriber < ActiveSupport::LogSubscriber
6
+ def process_action(event)
7
+ payload = event.payload
8
+ message = "#{payload[:method]} #{payload[:path]} format=#{payload[:format]} action=#{payload[:params]['controller']}##{payload[:params]['action']}"
9
+ message << extract_status(payload)
10
+ message << runtimes(event)
11
+ logger.info(message)
12
+ end
13
+
14
+ private
15
+
16
+ def extract_status(payload)
17
+ if payload[:status]
18
+ " status=#{payload[:status]}"
19
+ elsif payload[:exception]
20
+ exception, message = payload[:exception]
21
+ " status=500 error='#{exception}:#{message}'"
22
+ end
23
+ end
24
+
25
+ def runtimes(event)
26
+ message = ""
27
+ {:duration => event.duration,
28
+ :view => event.payload[:view_runtime],
29
+ :db => event.payload[:db_runtime]}.each do |name, runtime|
30
+ message << " #{name}=%.2f" % runtime if runtime
31
+ end
32
+ message
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ require 'rails/rack/logger'
2
+
3
+ module Rails
4
+ module Rack
5
+ # Overwrites defaults of Rails::Rack::Logger that cause
6
+ # unnecessary logging.
7
+ # This effectively removes the log lines from the log
8
+ # that say:
9
+ # Started GET / for 192.168.2.1...
10
+ class Logger
11
+ # Overwrites Rails 3.2 code that logs new requests
12
+ def call_app(env)
13
+ @app.call(env)
14
+ ensure
15
+ ActiveSupport::LogSubscriber.flush_all!
16
+ end
17
+
18
+ # Overwrites Rails 3.0/3.1 code that logs new requests
19
+ def before_dispatch(env)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ require 'rails/railtie'
2
+
3
+ module Lograge
4
+ class Railtie < Rails::Railtie
5
+ config.lograge = ActiveSupport::OrderedOptions.new
6
+ config.lograge.enabled = false
7
+
8
+ initializer :lograge do |app|
9
+ Lograge.setup(app) if app.config.lograge.enabled
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Lograge
2
+ VERSION = "0.0.1"
3
+ end
data/lograge.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "lograge/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "lograge"
7
+ s.version = Lograge::VERSION
8
+ s.authors = ["Mathias Meyer"]
9
+ s.email = ["meyer@paperplanes.de"]
10
+ s.homepage = ""
11
+ s.summary = %q{Tame Rails' multi-line logging into a single line per request}
12
+ s.description = %q{Tame Rails' multi-line logging into a single line per request}
13
+
14
+ s.rubyforge_project = "lograge"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "guard-rspec"
24
+ s.add_runtime_dependency "activesupport"
25
+ s.add_runtime_dependency "actionpack"
26
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'lograge/log_subscriber'
3
+ require 'active_support/notifications'
4
+ require 'active_support/core_ext/string'
5
+ require 'logger'
6
+
7
+ describe Lograge::RequestLogSubscriber do
8
+ let(:log_output) {StringIO.new}
9
+ let(:logger) {
10
+ logger = Logger.new(log_output)
11
+ logger.formatter = ->(_, _, _, msg) {
12
+ msg
13
+ }
14
+ logger
15
+ }
16
+ before do
17
+ Lograge::RequestLogSubscriber.logger = logger
18
+ end
19
+
20
+ let(:subscriber) {Lograge::RequestLogSubscriber.new}
21
+ let(:event) {
22
+ ActiveSupport::Notifications::Event.new(
23
+ 'process_action.action_controller', Time.now, Time.now, 2, {
24
+ status: 200, format: 'application/json', method: 'GET', path: '/home', params: {
25
+ 'controller' => 'home', 'action' => 'index'
26
+ }, db_runtime: 0.02, view_runtime: 0.01
27
+ }
28
+ )
29
+ }
30
+
31
+ let(:redirect) {
32
+ ActiveSupport::Notifications::Event.new(
33
+ 'redirect_to.action_controller', Time.now, Time.now, 1, location: 'http://example.com', status: 302
34
+ )
35
+ }
36
+
37
+ describe "when processing an action" do
38
+ it "should include the URL in the log output" do
39
+ subscriber.process_action(event)
40
+ log_output.string.should include('/home')
41
+ end
42
+
43
+ it "should start the log line with the HTTP method" do
44
+ subscriber.process_action(event)
45
+ log_output.string.starts_with?('GET').should == true
46
+ end
47
+
48
+ it "should include the status code" do
49
+ subscriber.process_action(event)
50
+ log_output.string.should include('status=200')
51
+ end
52
+
53
+ it "should include the controller and action" do
54
+ subscriber.process_action(event)
55
+ log_output.string.should include('action=home#index')
56
+ end
57
+
58
+ it "should include the duration" do
59
+ subscriber.process_action(event)
60
+ log_output.string.should =~ /duration=[\.0-9]{4,4}/
61
+ end
62
+
63
+ it "should include the view rendering time" do
64
+ subscriber.process_action(event)
65
+ log_output.string.should =~ /view=0.01/
66
+ end
67
+
68
+ it "should include the database rendering time" do
69
+ subscriber.process_action(event)
70
+ log_output.string.should =~ /db=0.02/
71
+ end
72
+
73
+ it "should add a 500 status when an exception occurred" do
74
+ event.payload[:status] = nil
75
+ event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
76
+ subscriber.process_action(event)
77
+ log_output.string.should =~ /status=500/
78
+ log_output.string.should =~ /error='AbstractController::ActionNotFound:Route not found'/
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'lograge'
3
+ require 'active_support/notifications'
4
+ require 'active_support/core_ext/string'
5
+ require 'active_support/log_subscriber'
6
+ require 'action_controller/log_subscriber'
7
+ require 'action_view/log_subscriber'
8
+
9
+ describe Lograge do
10
+ describe "when removing Rails' log subscribers" do
11
+ after do
12
+ ActionController::LogSubscriber.attach_to :action_controller
13
+ ActionView::LogSubscriber.attach_to :action_view
14
+ end
15
+
16
+ it "should remove subscribers for controller events" do
17
+ expect {
18
+ Lograge.remove_existing_log_subscriptions
19
+ }.to change {
20
+ ActiveSupport::Notifications.notifier.listeners_for('process_action.action_controller')
21
+ }
22
+ end
23
+
24
+ it "should remove subscribers for all events" do
25
+ expect {
26
+ Lograge.remove_existing_log_subscriptions
27
+ }.to change {
28
+ ActiveSupport::Notifications.notifier.listeners_for('render_template.action_view')
29
+ }
30
+ end
31
+
32
+ it "shouldn't remove subscribers that aren't from Rails" do
33
+ blk = -> {}
34
+ ActiveSupport::Notifications.subscribe("process_action.action_controller", &blk)
35
+ Lograge.remove_existing_log_subscriptions
36
+ listeners = ActiveSupport::Notifications.notifier.listeners_for('process_action.action_controller')
37
+ listeners.size.should > 0
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lograge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mathias Meyer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70337094742500 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70337094742500
25
+ - !ruby/object:Gem::Dependency
26
+ name: guard-rspec
27
+ requirement: &70337094741880 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70337094741880
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ requirement: &70337094740860 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70337094740860
47
+ - !ruby/object:Gem::Dependency
48
+ name: actionpack
49
+ requirement: &70337094739040 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70337094739040
58
+ description: Tame Rails' multi-line logging into a single line per request
59
+ email:
60
+ - meyer@paperplanes.de
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - .rspec
67
+ - .travis.yml
68
+ - Gemfile
69
+ - Guardfile
70
+ - README.md
71
+ - Rakefile
72
+ - lib/lograge.rb
73
+ - lib/lograge/log_subscriber.rb
74
+ - lib/lograge/rails_ext/rack/logger.rb
75
+ - lib/lograge/railtie.rb
76
+ - lib/lograge/version.rb
77
+ - lograge.gemspec
78
+ - spec/lograge_logsubscriber_spec.rb
79
+ - spec/lograge_spec.rb
80
+ - spec/spec_helper.rb
81
+ homepage: ''
82
+ licenses: []
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project: lograge
101
+ rubygems_version: 1.8.11
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Tame Rails' multi-line logging into a single line per request
105
+ test_files:
106
+ - spec/lograge_logsubscriber_spec.rb
107
+ - spec/lograge_spec.rb
108
+ - spec/spec_helper.rb
109
+ has_rdoc: