lograge 0.0.4 → 0.1.0

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/Gemfile CHANGED
@@ -2,3 +2,8 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in lograge.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'actionpack'
8
+ gem 'logstash-event'
9
+ end
data/README.md CHANGED
@@ -34,7 +34,7 @@ Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
34
34
  you get a single line with all the important information, like this:
35
35
 
36
36
  ```
37
- GET /jobs/833552.json format=json action=jobs#show status=200 duration=58.33 view=40.43 db=15.26
37
+ method=GET path=/jobs/833552.json format=json controller=jobs action=show status=200 duration=58.33 view=40.43 db=15.26
38
38
  ```
39
39
 
40
40
  The second line is easy to grasp with a single glance and still includes all the
@@ -70,11 +70,29 @@ MyApp::Application.configure do
70
70
  # if it's a lambda then it must return a hash
71
71
  config.lograge.custom_options = lambda do |event|
72
72
  # capture some specific timing values you are interested in
73
- {:name => "value", :name => "%2f" % float}
73
+ {:name => "value", :timing => some_float.round(2)}
74
74
  end
75
75
  end
76
76
  ```
77
77
 
78
+ Lograge supports multiple output formats. The most common is the default
79
+ lograge format described above. Alternatively, you can also generate JSON
80
+ logs in the json_event format used by [Logstash](http://logstash.net/).
81
+
82
+ ```
83
+ # config/environments/production.rb
84
+ MyApp::Application.configure do
85
+ config.lograge.log_format = :logstash
86
+ end
87
+ ```
88
+
89
+ *Note:* When using the logstash output, you need to add the additional gem
90
+ `logstash-event`. You can simply add it to your Gemfile like this
91
+
92
+ ```ruby
93
+ gem "logstash-event"
94
+ ```
95
+
78
96
  Done.
79
97
 
80
98
  **Internals**
@@ -143,6 +161,9 @@ that fits in with the general spirit of the log output generated by Lograge.
143
161
 
144
162
  **Changes**
145
163
 
164
+ * Add support for Logstash events (Holger Just, http://github.com/meineerde)
165
+ * Fix for Rails 3.2.9
166
+ * Use keys everywhere (Curt Michols, http://github.com/asenchi)
146
167
  * Add `custom_options` to allow adding custom key-value pairs at runtime (Adam
147
168
  Cooper, https://github.com/adamcooper)
148
169
 
@@ -5,12 +5,42 @@ module Lograge
5
5
  class RequestLogSubscriber < ActiveSupport::LogSubscriber
6
6
  def process_action(event)
7
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
- message << location(event)
12
- message << custom_options(event)
13
- logger.info(message)
8
+
9
+ data = extract_request(payload)
10
+ data.merge! extract_status(payload)
11
+ data.merge! runtimes(event)
12
+ data.merge! location(event)
13
+ data.merge! custom_options(event)
14
+
15
+ logger.info send(:"process_action_#{Lograge.log_format}", data)
16
+ end
17
+
18
+ LOGRAGE_FIELDS = [
19
+ :method, :path, :format, :controller, :action, :status, :error,
20
+ :duration, :view, :db, :location
21
+ ]
22
+ def process_action_lograge(data)
23
+ fields = LOGRAGE_FIELDS
24
+ fields += (data.keys - LOGRAGE_FIELDS)
25
+
26
+ event = fields.inject([]) do |message, key|
27
+ next message unless data.has_key?(key)
28
+ # Exactly preserve the previous output
29
+ # Parsing this can be ambigious if the error messages contains
30
+ # a single quote
31
+ data[key] = "'#{data[key]}'" if key == :error
32
+ # Ensure that we always have exactly two decimals
33
+ data[key] = "%.2f" % data[key] if data[key].is_a? Numeric
34
+
35
+ message << "#{key}=#{data[key]}"
36
+ message
37
+ end
38
+ event.join(" ")
39
+ end
40
+
41
+ def process_action_logstash(data)
42
+ event = LogStash::Event.new("@fields" => data)
43
+ event.to_json
14
44
  end
15
45
 
16
46
  def redirect_to(event)
@@ -19,39 +49,56 @@ module Lograge
19
49
 
20
50
  private
21
51
 
52
+ def extract_request(payload)
53
+ {
54
+ :method => payload[:method],
55
+ :path => payload[:path],
56
+ :format => extract_format(payload),
57
+ :controller => payload[:params]['controller'],
58
+ :action => payload[:params]['action']
59
+ }
60
+ end
61
+
62
+ def extract_format(payload)
63
+ if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR == 0
64
+ payload[:formats].first
65
+ else
66
+ payload[:format]
67
+ end
68
+ end
69
+
22
70
  def extract_status(payload)
23
71
  if payload[:status]
24
- " status=#{payload[:status]}"
72
+ { :status => payload[:status].to_i }
25
73
  elsif payload[:exception]
26
74
  exception, message = payload[:exception]
27
- " status=500 error='#{exception}:#{message}'"
75
+ { :status => 500, :error => "#{exception}:#{message}" }
76
+ else
77
+ { :status => 0 }
28
78
  end
29
79
  end
30
80
 
31
81
  def custom_options(event)
32
- message = ""
33
- (Lograge.custom_options(event) || {}).each do |name, value|
34
- message << " #{name}=#{value}"
35
- end
36
- message
82
+ Lograge.custom_options(event) || {}
37
83
  end
38
84
 
39
85
  def runtimes(event)
40
- message = ""
41
- {:duration => event.duration,
42
- :view => event.payload[:view_runtime],
43
- :db => event.payload[:db_runtime]}.each do |name, runtime|
44
- message << " #{name}=%.2f" % runtime if runtime
86
+ {
87
+ :duration => event.duration,
88
+ :view => event.payload[:view_runtime],
89
+ :db => event.payload[:db_runtime]
90
+ }.inject({}) do |runtimes, (name, runtime)|
91
+ runtimes[name] = runtime.to_f.round(2) if runtime
92
+ runtimes
45
93
  end
46
- message
47
94
  end
48
95
 
49
96
  def location(event)
50
97
  if location = Thread.current[:lograge_location]
51
98
  Thread.current[:lograge_location] = nil
52
- " location=#{location}"
99
+ { :location => location }
53
100
  else
54
- ""
101
+ {}
55
102
  end
56
103
  end
57
104
  end
@@ -9,7 +9,8 @@ module Rails
9
9
  # Started GET / for 192.168.2.1...
10
10
  class Logger
11
11
  # Overwrites Rails 3.2 code that logs new requests
12
- def call_app(env)
12
+ def call_app(*args)
13
+ env = args.last
13
14
  @app.call(env)
14
15
  ensure
15
16
  ActiveSupport::LogSubscriber.flush_all!
@@ -1,3 +1,3 @@
1
1
  module Lograge
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/lograge.rb CHANGED
@@ -24,21 +24,32 @@ module Lograge
24
24
  end
25
25
  end
26
26
 
27
- def self.remove_existing_log_subscriptions
28
- %w(redirect_to process_action start_processing send_data send_file write_fragment read_fragment exist_fragment? expire_fragment expire_page write_page).each do |event|
29
- unsubscribe_from_event(:action_controller, event)
30
- end
27
+ # The emitted log format
28
+ #
29
+ # Currently supported formats are>
30
+ # - :lograge - The custom tense lograge format
31
+ # - :logstash - JSON formatted as a Logstash Event.
32
+ mattr_accessor :log_format
33
+ self.log_format = :lograge
31
34
 
32
- %w{render_template render_partial render_collection}.each do |event|
33
- unsubscribe_from_event(:action_view, event)
35
+ def self.remove_existing_log_subscriptions
36
+ ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
37
+ case subscriber
38
+ when ActionView::LogSubscriber
39
+ unsubscribe(:action_view, subscriber)
40
+ when ActionController::LogSubscriber
41
+ unsubscribe(:action_controller, subscriber)
42
+ end
34
43
  end
35
44
  end
36
45
 
37
- def self.unsubscribe_from_event(component, event)
38
- delegate_type = component.to_s.classify
39
- ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
40
- if listener.inspect =~ /delegate[^a-z]+#{delegate_type}/
41
- ActiveSupport::Notifications.unsubscribe listener
46
+ def self.unsubscribe(component, subscriber)
47
+ events = subscriber.public_methods(false).reject{ |method| method.to_s == 'call' }
48
+ events.each do |event|
49
+ ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
50
+ if listener.instance_variable_get('@delegate') == subscriber
51
+ ActiveSupport::Notifications.unsubscribe listener
52
+ end
42
53
  end
43
54
  end
44
55
  end
@@ -49,6 +60,16 @@ module Lograge
49
60
  Lograge.remove_existing_log_subscriptions
50
61
  Lograge::RequestLogSubscriber.attach_to :action_controller
51
62
  Lograge.custom_options = app.config.lograge.custom_options
63
+ Lograge.log_format = app.config.lograge.log_format || :lograge
64
+ case Lograge.log_format.to_s
65
+ when "logstash"
66
+ begin
67
+ require "logstash-event"
68
+ rescue LoadError
69
+ puts "You need to install the logstash-event gem to use the logstash output."
70
+ raise
71
+ end
72
+ end
52
73
  end
53
74
  end
54
75
 
data/lograge.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.version = Lograge::VERSION
8
8
  s.authors = ["Mathias Meyer"]
9
9
  s.email = ["meyer@paperplanes.de"]
10
- s.homepage = "https://github.com/mattmatt/lograge"
10
+ s.homepage = "https://github.com/roidrage/lograge"
11
11
  s.summary = %q{Tame Rails' multi-line logging into a single line per request}
12
12
  s.description = %q{Tame Rails' multi-line logging into a single line per request}
13
13
 
@@ -35,7 +35,11 @@ describe Lograge::RequestLogSubscriber do
35
35
  )
36
36
  }
37
37
 
38
- describe "when processing an action" do
38
+ describe "when processing an action with lograge output" do
39
+ before do
40
+ Lograge::log_format = :lograge
41
+ end
42
+
39
43
  it "should include the URL in the log output" do
40
44
  subscriber.process_action(event)
41
45
  log_output.string.should include('/home')
@@ -43,7 +47,7 @@ describe Lograge::RequestLogSubscriber do
43
47
 
44
48
  it "should start the log line with the HTTP method" do
45
49
  subscriber.process_action(event)
46
- log_output.string.starts_with?('GET').should == true
50
+ log_output.string.starts_with?('method=GET').should == true
47
51
  end
48
52
 
49
53
  it "should include the status code" do
@@ -53,7 +57,7 @@ describe Lograge::RequestLogSubscriber do
53
57
 
54
58
  it "should include the controller and action" do
55
59
  subscriber.process_action(event)
56
- log_output.string.should include('action=home#index')
60
+ log_output.string.should include('controller=home action=index')
57
61
  end
58
62
 
59
63
  it "should include the duration" do
@@ -79,6 +83,13 @@ describe Lograge::RequestLogSubscriber do
79
83
  log_output.string.should =~ /error='AbstractController::ActionNotFound:Route not found'/
80
84
  end
81
85
 
86
+ it "should return an unknown status when no status or exception is found" do
87
+ event.payload[:status] = nil
88
+ event.payload[:exception] = nil
89
+ subscriber.process_action(event)
90
+ log_output.string.should =~ /status=0/
91
+ end
92
+
82
93
  describe "with a redirect" do
83
94
  before do
84
95
  Thread.current[:lograge_location] = "http://www.example.com"
@@ -101,7 +112,90 @@ describe Lograge::RequestLogSubscriber do
101
112
  end
102
113
  end
103
114
 
104
- describe "with custom_options configured" do
115
+ describe "when processing an action with logstash output" do
116
+ before do
117
+ require 'logstash-event'
118
+ Lograge::log_format = :logstash
119
+ end
120
+
121
+ it "should include the URL in the log output" do
122
+ subscriber.process_action(event)
123
+ log_output.string.should include('/home')
124
+ end
125
+
126
+ it "should start include the HTTP method" do
127
+ subscriber.process_action(event)
128
+ log_output.string.should include('"method":"GET"')
129
+ end
130
+
131
+ it "should include the status code" do
132
+ subscriber.process_action(event)
133
+ log_output.string.should include('"status":200')
134
+ end
135
+
136
+ it "should include the controller and action" do
137
+ subscriber.process_action(event)
138
+ log_output.string.should include('"controller":"home"')
139
+ log_output.string.should include('"action":"index"')
140
+ end
141
+
142
+ it "should include the duration" do
143
+ subscriber.process_action(event)
144
+ log_output.string.should =~ /"duration":\d+\.\d{0,2}/
145
+ end
146
+
147
+ it "should include the view rendering time" do
148
+ subscriber.process_action(event)
149
+ log_output.string.should =~ /"view":0.01/
150
+ end
151
+
152
+ it "should include the database rendering time" do
153
+ subscriber.process_action(event)
154
+ log_output.string.should =~ /"db":0.02/
155
+ end
156
+
157
+ it "should add a 500 status when an exception occurred" do
158
+ event.payload[:status] = nil
159
+ event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
160
+ subscriber.process_action(event)
161
+ log_output.string.should =~ /"status":500/
162
+ log_output.string.should =~ /"error":"AbstractController::ActionNotFound:Route not found"/
163
+ end
164
+
165
+ it "should return an unknown status when no status or exception is found" do
166
+ event.payload[:status] = nil
167
+ event.payload[:exception] = nil
168
+ subscriber.process_action(event)
169
+ log_output.string.should =~ /"status":0/
170
+ end
171
+
172
+ describe "with a redirect" do
173
+ before do
174
+ Thread.current[:lograge_location] = "http://www.example.com"
175
+ end
176
+
177
+ it "should add the location to the log line" do
178
+ subscriber.process_action(event)
179
+ log_output.string.should =~ %r{"location":"http://www.example.com"}
180
+ end
181
+
182
+ it "should remove the thread local variable" do
183
+ subscriber.process_action(event)
184
+ Thread.current[:lograge_location].should == nil
185
+ end
186
+ end
187
+
188
+ it "should not include a location by default" do
189
+ subscriber.process_action(event)
190
+ log_output.string.should_not =~ /"location":/
191
+ end
192
+ end
193
+
194
+ describe "with custom_options configured for lograge output" do
195
+ before do
196
+ Lograge::log_format = :lograge
197
+ end
198
+
105
199
  it "should combine the hash properly for the output" do
106
200
  Lograge.custom_options = {:data => "value"}
107
201
  subscriber.process_action(event)
@@ -119,6 +213,28 @@ describe Lograge::RequestLogSubscriber do
119
213
  end
120
214
  end
121
215
 
216
+ describe "with custom_options configured for logstash output" do
217
+ before do
218
+ Lograge::log_format = :logstash
219
+ end
220
+
221
+ it "should combine the hash properly for the output" do
222
+ Lograge.custom_options = {:data => "value"}
223
+ subscriber.process_action(event)
224
+ log_output.string.should =~ /"data":"value"/
225
+ end
226
+ it "should combine the output of a lambda properly" do
227
+ Lograge.custom_options = lambda {|event| {:data => "value"}}
228
+ subscriber.process_action(event)
229
+ log_output.string.should =~ /"data":"value"/
230
+ end
231
+ it "should work if the method returns nil" do
232
+ Lograge.custom_options = lambda {|event| nil}
233
+ subscriber.process_action(event)
234
+ log_output.string.should be_present
235
+ end
236
+ end
237
+
122
238
  describe "when processing a redirect" do
123
239
  it "should store the location in a thread local variable" do
124
240
  subscriber.redirect_to(redirect)
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,8 @@
4
4
  # loaded once.
5
5
  #
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ require 'action_pack'
8
+
7
9
  RSpec.configure do |config|
8
10
  config.treat_symbols_as_metadata_keys_with_true_values = true
9
11
  config.run_all_when_everything_filtered = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lograge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-16 00:00:00.000000000 Z
12
+ date: 2012-11-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70345743126700 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70345743126700
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: guard-rspec
27
- requirement: &70345743136340 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '0'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70345743136340
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: activesupport
38
- requirement: &70345743177800 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: '0'
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *70345743177800
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: actionpack
49
- requirement: &70345743191500 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,7 +69,12 @@ dependencies:
54
69
  version: '0'
55
70
  type: :runtime
56
71
  prerelease: false
57
- version_requirements: *70345743191500
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
58
78
  description: Tame Rails' multi-line logging into a single line per request
59
79
  email:
60
80
  - meyer@paperplanes.de
@@ -78,7 +98,7 @@ files:
78
98
  - spec/lograge_logsubscriber_spec.rb
79
99
  - spec/lograge_spec.rb
80
100
  - spec/spec_helper.rb
81
- homepage: https://github.com/mattmatt/lograge
101
+ homepage: https://github.com/roidrage/lograge
82
102
  licenses: []
83
103
  post_install_message:
84
104
  rdoc_options: []
@@ -98,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
118
  version: '0'
99
119
  requirements: []
100
120
  rubyforge_project: lograge
101
- rubygems_version: 1.8.11
121
+ rubygems_version: 1.8.23
102
122
  signing_key:
103
123
  specification_version: 3
104
124
  summary: Tame Rails' multi-line logging into a single line per request