lograge 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -24,6 +24,14 @@ module Lograge
24
24
  end
25
25
  end
26
26
 
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
34
+
27
35
  def self.remove_existing_log_subscriptions
28
36
  ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
29
37
  case subscriber
@@ -52,6 +60,16 @@ module Lograge
52
60
  Lograge.remove_existing_log_subscriptions
53
61
  Lograge::RequestLogSubscriber.attach_to :action_controller
54
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
55
73
  end
56
74
  end
57
75
 
@@ -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,41 +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}" }
28
76
  else
29
- " status=0"
77
+ { :status => 0 }
30
78
  end
31
79
  end
32
80
 
33
81
  def custom_options(event)
34
- message = ""
35
- (Lograge.custom_options(event) || {}).each do |name, value|
36
- message << " #{name}=#{value}"
37
- end
38
- message
82
+ Lograge.custom_options(event) || {}
39
83
  end
40
84
 
41
85
  def runtimes(event)
42
- message = ""
43
- {:duration => event.duration,
44
- :view => event.payload[:view_runtime],
45
- :db => event.payload[:db_runtime]}.each do |name, runtime|
46
- 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
47
93
  end
48
- message
49
94
  end
50
95
 
51
96
  def location(event)
52
97
  if location = Thread.current[:lograge_location]
53
98
  Thread.current[:lograge_location] = nil
54
- " location=#{location}"
99
+ { :location => location }
55
100
  else
56
- ""
101
+ {}
57
102
  end
58
103
  end
59
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.6"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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
@@ -108,7 +112,90 @@ describe Lograge::RequestLogSubscriber do
108
112
  end
109
113
  end
110
114
 
111
- 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
+
112
199
  it "should combine the hash properly for the output" do
113
200
  Lograge.custom_options = {:data => "value"}
114
201
  subscriber.process_action(event)
@@ -126,6 +213,28 @@ describe Lograge::RequestLogSubscriber do
126
213
  end
127
214
  end
128
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
+
129
238
  describe "when processing a redirect" do
130
239
  it "should store the location in a thread local variable" do
131
240
  subscriber.redirect_to(redirect)
@@ -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.6
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-07-24 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: &70325289194340 !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: *70325289194340
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: &70325289193820 !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: *70325289193820
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: &70325289193340 !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: *70325289193340
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: &70325289192680 !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: *70325289192680
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
@@ -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