lograge 0.0.1

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