logstasher 0.1.1 → 0.2.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/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ .idea
data/.travis.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.0
3
4
  - 1.9.3
4
5
  - 1.9.2
5
6
  script: bundle exec rspec
data/Gemfile CHANGED
@@ -1,9 +1,14 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in logstasher.gemspec
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem 'actionpack'
8
- gem 'logstash-event'
7
+ gem 'rb-fsevent', '~> 0.9'
8
+ gem 'guard'
9
+ gem 'guard-rspec'
10
+ gem 'growl'
11
+ gem 'simplecov', :platforms => :mri_19, :require => false
12
+ gem 'rcov', :platforms => :mri_18
13
+ gem 'rails', '~> 3.2.0'
9
14
  end
data/Guardfile CHANGED
@@ -1,5 +1,6 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
+ interactor :simple
3
4
 
4
5
  guard 'rspec', :version => 2 do
5
6
  watch(%r{^spec/.+_spec\.rb$})
data/README.md CHANGED
@@ -1,3 +1,49 @@
1
- Logstasher - Awesome rails logs
2
- =======
1
+ # Logstasher - Awesome Logging for Rails [![Build Status](https://secure.travis-ci.org/shadabahmed/logstasher.png)](https://secure.travis-ci.org/shadabahmed/logstasher)
3
2
 
3
+ This gem is heavily inspired from [lograge](https://github.com/roidrage/lograge) but it's focused on one thing and one thing only; making your logs awesome.
4
+
5
+ How do I do that ?
6
+
7
+ Using these two awesome tools:
8
+ * [logstash](http://logstash.net) - Store and index your logs
9
+ * [Kibana](http://kibana.org/) - for awesome visualization. This is optional though, and you can use any other visualizer
10
+
11
+ ## Installation
12
+
13
+ In your Gemfile:
14
+
15
+ gem 'logstasher'
16
+
17
+ ### Configure your \<environment\>.rb e.g. development.rb
18
+
19
+ config.logstasher.enabled = true
20
+
21
+ # This line is optional if you do not want to supress app logs in your <environment>.log
22
+ config.logstasher.supress_app_log = false
23
+
24
+ ## Adding custom fields to the log
25
+
26
+ Since some fields are very specific to your application for e.g. *user_name*, it is left upto you to add them. Here's how to add those to the logs:
27
+
28
+ # In config/initializers/logstasher.rb
29
+
30
+ if LogStasher.enabled
31
+ LogStasher.add_custom_fields do |fields|
32
+ fields[:user] = current_user && current_user.mail
33
+ fields[:site] = request.path =~ /^\/api/ ? 'api' : 'user'
34
+
35
+ # If you are using custom instrumentation, just add those to logstasher custom fields
36
+ LogStasher.custom_fields << :myapi_runtime
37
+ end
38
+ end
39
+
40
+ ## Versions
41
+ All versions require Rails 3.0.x and higher and Ruby 1.9.2+
42
+
43
+ ## Development
44
+ - Run tests - `rake`
45
+ - Generate test coverage report - `rake coverage`. Coverage report path - coverage/index.html
46
+
47
+ ## Copyright
48
+
49
+ Copyright (c) 2013 Shadab Ahmed, released under the MIT license
data/Rakefile CHANGED
@@ -1 +1,42 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ # Add default task. When you type just rake command this would run. Travis CI runs this. Making this run spec
5
+ desc 'Default: run specs.'
6
+ task :default => :spec
7
+
8
+ # Defining spec task for running spec
9
+ desc "Run specs"
10
+ RSpec::Core::RakeTask.new('spec') do |spec|
11
+ # Pattern filr for spec files to run. This is default btw.
12
+ spec.pattern = "./spec/**/*_spec.rb"
13
+ end
14
+
15
+ # Run the rdoc task to generate rdocs for this gem
16
+ require 'rdoc/task'
17
+ RDoc::Task.new do |rdoc|
18
+ require "logstasher/version"
19
+ version = LogStasher::VERSION
20
+
21
+ rdoc.rdoc_dir = 'rdoc'
22
+ rdoc.title = "logstasher #{version}"
23
+ rdoc.rdoc_files.include('README*')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ # Code coverage tasks. Different for Ruby 1.8 vs 1.9
28
+ if RUBY_VERSION =~ /^1\.8/
29
+ # Ruby 1.8 uses rcov for code coverage
30
+ RSpec::Core::RakeTask.new(:coverage) do |spec|
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ spec.rcov_opts = %w{--exclude pkg\/,spec\/,features\/}
34
+ end
35
+ else
36
+ # Ruby 1.9+ using simplecov. Note: Simplecov config defined in spec_helper
37
+ desc "Code coverage detail"
38
+ task :coverage do
39
+ ENV['COVERAGE'] = "true"
40
+ Rake::Task['spec'].execute
41
+ end
42
+ end
data/lib/logstasher.rb CHANGED
@@ -4,24 +4,17 @@ require 'active_support/core_ext/module/attribute_accessors'
4
4
  require 'active_support/core_ext/string/inflections'
5
5
  require 'active_support/ordered_options'
6
6
 
7
- module Logstasher
7
+ module LogStasher
8
8
  # Logger for the logstash logs
9
9
  mattr_accessor :logger, :enabled
10
10
 
11
- # Set the options for the adding cutom data to payload
12
- mattr_accessor :payload_appender
13
-
14
- def self.append_payload(&block)
15
- self.payload_appender = block
16
- end
17
-
18
11
  def self.remove_existing_log_subscriptions
19
12
  ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
20
13
  case subscriber
21
- when ActionView::LogSubscriber
22
- unsubscribe(:action_view, subscriber)
23
- when ActionController::LogSubscriber
24
- unsubscribe(:action_controller, subscriber)
14
+ when ActionView::LogSubscriber
15
+ unsubscribe(:action_view, subscriber)
16
+ when ActionController::LogSubscriber
17
+ unsubscribe(:action_controller, subscriber)
25
18
  end
26
19
  end
27
20
  end
@@ -37,15 +30,62 @@ module Logstasher
37
30
  end
38
31
  end
39
32
 
33
+ def self.add_default_fields_to_payload(payload, request)
34
+ payload[:ip] = request.remote_ip
35
+ payload[:route] = "#{request.params[:controller]}##{request.params[:action]}"
36
+ payload[:parameters] = payload[:params].except(*ActionController::LogSubscriber::INTERNAL_PARAMS).inject(""){|s,(k,v)|
37
+ s+="#{k}=#{v}\n"}
38
+ self.custom_fields += [:ip, :route, :parameters]
39
+ end
40
+
41
+ def self.add_custom_fields(&block)
42
+ ActionController::Base.send(:define_method, :logtasher_add_custom_fields_to_payload, &block)
43
+ end
44
+
40
45
  def self.setup(app)
41
- Logstasher.enabled = true
42
46
  app.config.action_dispatch.rack_cache[:verbose] = false if app.config.action_dispatch.rack_cache
43
- require 'logstasher/rails_ext/rack/logger'
47
+ # Path instrumentation class to insert our hook
44
48
  require 'logstasher/rails_ext/action_controller/metal/instrumentation'
45
49
  require 'logstash/event'
46
- Logstasher.remove_existing_log_subscriptions
47
- Logstasher::RequestLogSubscriber.attach_to :action_controller
48
- self.logger = app.config.logstasher.logger || Logger.new("#{Rails.root}/log/logstash.log")
50
+ self.suppress_app_logs(app)
51
+ LogStasher::RequestLogSubscriber.attach_to :action_controller
52
+ self.logger = app.config.logstasher.logger || Logger.new("#{Rails.root}/log/logstash_#{Rails.env}.log")
53
+ self.logger.level = app.config.logstasher.log_level || Logger::WARN
54
+ self.enabled = true
55
+ self.custom_fields = []
56
+ end
57
+
58
+ def self.suppress_app_logs(app)
59
+ if app.config.logstasher.supress_app_log.nil? || app.config.logstasher.supress_app_log
60
+ require 'logstasher/rails_ext/rack/logger'
61
+ LogStasher.remove_existing_log_subscriptions
62
+ end
63
+ end
64
+
65
+ def self.custom_fields
66
+ Thread.current[:logstasher_custom_fields]
67
+ end
68
+
69
+ def self.custom_fields=(val)
70
+ Thread.current[:logstasher_custom_fields] = val
71
+ end
72
+
73
+
74
+ def self.log(severity, msg)
75
+ if self.logger && self.logger.send("#{severity}?")
76
+ event = LogStash::Event.new('@fields' => {:message => msg, :level => severity},'@tags' => ['log'])
77
+ self.logger.send severity, event.to_json
78
+ end
79
+ end
80
+
81
+ class << self
82
+ %w( fatal error warn info debug unknown ).each do |severity|
83
+ eval <<-EOM, nil, __FILE__, __LINE__ + 1
84
+ def #{severity}(msg)
85
+ self.log(:#{severity}, msg)
86
+ end
87
+ EOM
88
+ end
49
89
  end
50
90
  end
51
91
 
@@ -1,7 +1,7 @@
1
1
  require 'active_support/core_ext/class/attribute'
2
2
  require 'active_support/log_subscriber'
3
3
 
4
- module Logstasher
4
+ module LogStasher
5
5
  class RequestLogSubscriber < ActiveSupport::LogSubscriber
6
6
  def process_action(event)
7
7
  payload = event.payload
@@ -11,10 +11,11 @@ module Logstasher
11
11
  data.merge! runtimes(event)
12
12
  data.merge! location(event)
13
13
  data.merge! extract_exception(payload)
14
- data.merge! extract_appended_params(payload)
14
+ data.merge! extract_custom_fields(payload)
15
15
 
16
- event = LogStash::Event.new("@fields" => data)
17
- Logstasher.logger.info event.to_json
16
+ event = LogStash::Event.new('@fields' => data, '@tags' => ['request'])
17
+ event.tags << 'exception' if payload[:exception]
18
+ LogStasher.logger.unknown event.to_json
18
19
  end
19
20
 
20
21
  def redirect_to(event)
@@ -28,8 +29,8 @@ module Logstasher
28
29
  :method => payload[:method],
29
30
  :path => extract_path(payload),
30
31
  :format => extract_format(payload),
31
- :controller => payload[:controller],
32
- :action => payload[:action]
32
+ :controller => payload[:params]['controller'],
33
+ :action => payload[:params]['action']
33
34
  }
34
35
  end
35
36
 
@@ -49,7 +50,7 @@ module Logstasher
49
50
  if payload[:status]
50
51
  { :status => payload[:status].to_i }
51
52
  else
52
- {}
53
+ { :status => 0 }
53
54
  end
54
55
  end
55
56
 
@@ -77,16 +78,17 @@ module Logstasher
77
78
  def extract_exception(payload)
78
79
  if payload[:exception]
79
80
  exception, message = payload[:exception]
80
- message = "#{exception} : #{message}\n #{($!.backtrace.join("\n"))}"
81
+ message = "#{exception}\n#{message}\n#{($!.backtrace.join("\n"))}"
81
82
  { :status => 500, :error => message }
82
83
  else
83
84
  {}
84
85
  end
85
86
  end
86
87
 
87
- def extract_appended_params(payload)
88
- appended_keys = payload.delete(:log_stasher_appended_param_keys)
89
- (appended_keys && payload.extract!(*appended_keys)) || {}
88
+ def extract_custom_fields(payload)
89
+ custom_fields = (!LogStasher.custom_fields.empty? && payload.extract!(*LogStasher.custom_fields)) || {}
90
+ LogStasher.custom_fields.clear
91
+ custom_fields
90
92
  end
91
93
  end
92
94
  end
@@ -10,13 +10,13 @@ module ActionController
10
10
  :path => (request.fullpath rescue "unknown")
11
11
  }
12
12
 
13
- if Logstasher.payload_appender
13
+ LogStasher.add_default_fields_to_payload(raw_payload, request)
14
+ if self.respond_to?(:logtasher_add_custom_fields_to_payload)
14
15
  before_keys = raw_payload.keys.clone
15
- # Execue the payload appened in current context
16
- self.instance_exec raw_payload, &Logstasher.payload_appender
16
+ logtasher_add_custom_fields_to_payload(raw_payload)
17
17
  after_keys = raw_payload.keys
18
- # Add to payload all extra keys added to payload hash
19
- raw_payload[:log_stasher_appended_param_keys] = after_keys - before_keys
18
+ # Store all extra keys added to payload hash in payload itself. This is a thread safe way
19
+ LogStasher.custom_fields += after_keys - before_keys
20
20
  end
21
21
 
22
22
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
@@ -2,13 +2,13 @@ require 'rails/railtie'
2
2
  require 'action_view/log_subscriber'
3
3
  require 'action_controller/log_subscriber'
4
4
 
5
- module Logstasher
5
+ module LogStasher
6
6
  class Railtie < Rails::Railtie
7
7
  config.logstasher = ActiveSupport::OrderedOptions.new
8
8
  config.logstasher.enabled = false
9
9
 
10
10
  initializer :logstasher do |app|
11
- Logstasher.setup(app) if app.config.logstasher.enabled
11
+ LogStasher.setup(app) if app.config.logstasher.enabled
12
12
  end
13
13
  end
14
14
  end
@@ -1,3 +1,3 @@
1
- module Logstasher
2
- VERSION = "0.1.1"
1
+ module LogStasher
2
+ VERSION = "0.2.0"
3
3
  end
data/logstasher.gemspec CHANGED
@@ -4,7 +4,7 @@ require "logstasher/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "logstasher"
7
- s.version = Logstasher::VERSION
7
+ s.version = LogStasher::VERSION
8
8
  s.authors = ["Shadab Ahmed"]
9
9
  s.email = ["shadab.ansari@gmail.com"]
10
10
  s.homepage = "https://github.com/shadabahmed/logstasher"
@@ -21,7 +21,6 @@ Gem::Specification.new do |s|
21
21
 
22
22
  # specify any dependencies here; for example:
23
23
  s.add_development_dependency "rspec"
24
- s.add_development_dependency "guard-rspec"
25
- s.add_runtime_dependency "activesupport"
26
- s.add_runtime_dependency "actionpack"
24
+ s.add_development_dependency("bundler", [">= 1.0.0"])
25
+ s.add_development_dependency("rails", [">= 3.0"])
27
26
  end
@@ -1,30 +1,29 @@
1
1
  require 'spec_helper'
2
- require 'logstasher'
3
- require 'logstasher/log_subscriber'
4
- require 'active_support/notifications'
5
- require 'active_support/core_ext/string'
6
- require 'logger'
7
2
 
8
- describe Logstasher::RequestLogSubscriber do
3
+ describe LogStasher::RequestLogSubscriber do
9
4
  let(:log_output) {StringIO.new}
10
5
  let(:logger) {
11
6
  logger = Logger.new(log_output)
12
7
  logger.formatter = ->(_, _, _, msg) {
13
8
  msg
14
9
  }
10
+ def log_output.json
11
+ JSON.parse! self.string
12
+ end
15
13
  logger
16
14
  }
17
15
  before do
18
- Logstasher::RequestLogSubscriber.logger = logger
16
+ LogStasher.logger = logger
17
+ LogStasher.custom_fields = []
19
18
  end
20
19
 
21
- let(:subscriber) {Logstasher::RequestLogSubscriber.new}
20
+ let(:subscriber) {LogStasher::RequestLogSubscriber.new}
22
21
  let(:event) {
23
22
  ActiveSupport::Notifications::Event.new(
24
23
  'process_action.action_controller', Time.now, Time.now, 2, {
25
24
  status: 200, format: 'application/json', method: 'GET', path: '/home?foo=bar', params: {
26
- 'controller' => 'home', 'action' => 'index', 'foo' => 'bar'
27
- }, db_runtime: 0.02, view_runtime: 0.01
25
+ :controller => 'home', :action => 'index', 'foo' => 'bar'
26
+ }.with_indifferent_access, db_runtime: 0.02, view_runtime: 0.01
28
27
  }
29
28
  )
30
29
  }
@@ -35,220 +34,73 @@ describe Logstasher::RequestLogSubscriber do
35
34
  )
36
35
  }
37
36
 
38
- describe "when processing an action with logstasher output" do
39
- before do
40
- Logstasher::log_format = :logstasher
41
- end
42
-
43
- it "should include the URL in the log output" do
44
- subscriber.process_action(event)
45
- log_output.string.should include('/home')
46
- end
47
-
48
- it "should not include the query string in the url" do
49
- subscriber.process_action(event)
50
- log_output.string.should_not include('?foo=bar')
51
- end
52
-
53
- it "should start the log line with the HTTP method" do
54
- subscriber.process_action(event)
55
- log_output.string.starts_with?('method=GET ').should == true
56
- end
57
-
58
- it "should include the status code" do
59
- subscriber.process_action(event)
60
- log_output.string.should include('status=200 ')
61
- end
62
-
63
- it "should include the controller and action" do
64
- subscriber.process_action(event)
65
- log_output.string.should include('controller=home action=index')
66
- end
67
-
68
- it "should include the duration" do
69
- subscriber.process_action(event)
70
- log_output.string.should =~ /duration=[\.0-9]{4,4} /
71
- end
37
+ describe 'logstasher output' do
72
38
 
73
- it "should include the view rendering time" do
39
+ it "should contain request tag" do
74
40
  subscriber.process_action(event)
75
- log_output.string.should =~ /view=0.01 /
41
+ log_output.json['@tags'].should include 'request'
76
42
  end
77
43
 
78
- it "should include the database rendering time" do
44
+ it "should contain HTTP method" do
79
45
  subscriber.process_action(event)
80
- log_output.string.should =~ /db=0.02/
46
+ log_output.json['@fields']['method'].should == 'GET'
81
47
  end
82
48
 
83
- it "should add a 500 status when an exception occurred" do
84
- event.payload[:status] = nil
85
- event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
49
+ it "should include the path in the log output" do
86
50
  subscriber.process_action(event)
87
- log_output.string.should =~ /status=500 /
88
- log_output.string.should =~ /error='AbstractController::ActionNotFound:Route not found' /
51
+ log_output.json['@fields']['path'].should == '/home'
89
52
  end
90
53
 
91
- it "should return an unknown status when no status or exception is found" do
92
- event.payload[:status] = nil
93
- event.payload[:exception] = nil
54
+ it "should include the format in the log output" do
94
55
  subscriber.process_action(event)
95
- log_output.string.should =~ /status=0 /
96
- end
97
-
98
- describe "with a redirect" do
99
- before do
100
- Thread.current[:logstasher_location] = "http://www.example.com"
101
- end
102
-
103
- it "should add the location to the log line" do
104
- subscriber.process_action(event)
105
- log_output.string.should =~ %r{ location=http://www.example.com}
106
- end
107
-
108
- it "should remove the thread local variable" do
109
- subscriber.process_action(event)
110
- Thread.current[:logstasher_location].should == nil
111
- end
112
- end
113
-
114
- it "should not include a location by default" do
115
- subscriber.process_action(event)
116
- log_output.string.should_not =~ /location=/
117
- end
118
- end
119
-
120
- describe "when processing an action with logstash output" do
121
- before do
122
- require 'logstash-event'
123
- Logstasher::log_format = :logstash
124
- end
125
-
126
- it "should include the URL in the log output" do
127
- subscriber.process_action(event)
128
- log_output.string.should include('/home')
129
- end
130
-
131
- it "should start include the HTTP method" do
132
- subscriber.process_action(event)
133
- log_output.string.should include('"method":"GET"')
56
+ log_output.json['@fields']['format'].should == 'application/json'
134
57
  end
135
58
 
136
59
  it "should include the status code" do
137
60
  subscriber.process_action(event)
138
- log_output.string.should include('"status":200')
61
+ log_output.json['@fields']['status'].should == 200
139
62
  end
140
63
 
141
- it "should include the controller and action" do
64
+ it "should include the controller" do
142
65
  subscriber.process_action(event)
143
- log_output.string.should include('"controller":"home"')
144
- log_output.string.should include('"action":"index"')
66
+ log_output.json['@fields']['controller'].should == 'home'
145
67
  end
146
68
 
147
- it "should include the duration" do
69
+ it "should include the action" do
148
70
  subscriber.process_action(event)
149
- log_output.string.should =~ /"duration":\d+\.\d{0,2}/
71
+ log_output.json['@fields']['action'].should == 'index'
150
72
  end
151
73
 
152
74
  it "should include the view rendering time" do
153
75
  subscriber.process_action(event)
154
- log_output.string.should =~ /"view":0.01/
76
+ log_output.json['@fields']['view'].should == 0.01
155
77
  end
156
78
 
157
79
  it "should include the database rendering time" do
158
80
  subscriber.process_action(event)
159
- log_output.string.should =~ /"db":0.02/
81
+ log_output.json['@fields']['db'].should == 0.02
160
82
  end
161
83
 
162
84
  it "should add a 500 status when an exception occurred" do
163
- event.payload[:status] = nil
164
- event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
165
- subscriber.process_action(event)
166
- log_output.string.should =~ /"status":500/
167
- log_output.string.should =~ /"error":"AbstractController::ActionNotFound:Route not found"/
168
- end
169
-
170
- it "should return an unknown status when no status or exception is found" do
171
- event.payload[:status] = nil
172
- event.payload[:exception] = nil
173
- subscriber.process_action(event)
174
- log_output.string.should =~ /"status":0/
175
- end
176
-
177
- describe "with a redirect" do
178
- before do
179
- Thread.current[:logstasher_location] = "http://www.example.com"
180
- end
181
-
182
- it "should add the location to the log line" do
85
+ begin
86
+ raise AbstractController::ActionNotFound.new('Could not find an action')
87
+ # working this in rescue to get access to $! variable
88
+ rescue
89
+ event.payload[:status] = nil
90
+ event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
183
91
  subscriber.process_action(event)
184
- log_output.string.should =~ %r{"location":"http://www.example.com"}
92
+ log_output.json['@fields']['status'].should == 500
93
+ log_output.json['@fields']['error'].should =~ /AbstractController::ActionNotFound.*Route not found.*logstasher\/spec\/logstasher_logsubscriber_spec\.rb/m
94
+ log_output.json['@tags'].should include 'request'
95
+ log_output.json['@tags'].should include 'exception'
185
96
  end
186
-
187
- it "should remove the thread local variable" do
188
- subscriber.process_action(event)
189
- Thread.current[:logstasher_location].should == nil
190
- end
191
- end
192
-
193
- it "should not include a location by default" do
194
- subscriber.process_action(event)
195
- log_output.string.should_not =~ /"location":/
196
- end
197
- end
198
-
199
- describe "when processing an action with graylog2 output" do
200
- before do
201
- Logstasher::log_format = :graylog2
202
- end
203
-
204
- it "should include the URL in the log output" do
205
- subscriber.process_action(event)
206
- log_output.string.should include(':_path=>"/home"')
207
- end
208
-
209
- it "should start include the HTTP method" do
210
- subscriber.process_action(event)
211
- log_output.string.should include(':_method=>"GET"')
212
- end
213
-
214
- it "should include the status code" do
215
- subscriber.process_action(event)
216
- log_output.string.should include(':_status=>200') end
217
-
218
- it "should include the controller and action" do
219
- subscriber.process_action(event)
220
- log_output.string.should include(':_controller=>"home"')
221
- log_output.string.should include(':_action=>"index"')
222
- end
223
-
224
- it "should include the duration" do
225
- subscriber.process_action(event)
226
- log_output.string.should =~ /:_duration=>\d+\.\d{0,2}/
227
- end
228
-
229
- it "should include the view rendering time" do
230
- subscriber.process_action(event)
231
- log_output.string.should include(':_view=>0.01')
232
- end
233
-
234
- it "should include the database rendering time" do
235
- subscriber.process_action(event)
236
- log_output.string.should include(':_db=>0.02')
237
- end
238
-
239
- it "should add a 500 status when an exception occurred" do
240
- event.payload[:status] = nil
241
- event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
242
- subscriber.process_action(event)
243
- log_output.string.should include(':_status=>500')
244
- log_output.string.should include(':_error=>"AbstractController::ActionNotFound:Route not found"')
245
97
  end
246
98
 
247
99
  it "should return an unknown status when no status or exception is found" do
248
100
  event.payload[:status] = nil
249
101
  event.payload[:exception] = nil
250
102
  subscriber.process_action(event)
251
- log_output.string.should include(':_status=>0')
103
+ log_output.json['@fields']['status'].should == 0
252
104
  end
253
105
 
254
106
  describe "with a redirect" do
@@ -258,7 +110,7 @@ describe Logstasher::RequestLogSubscriber do
258
110
 
259
111
  it "should add the location to the log line" do
260
112
  subscriber.process_action(event)
261
- log_output.string.should include(':_location=>"http://www.example.com"')
113
+ log_output.json['@fields']['location'].should == 'http://www.example.com'
262
114
  end
263
115
 
264
116
  it "should remove the thread local variable" do
@@ -269,75 +121,37 @@ describe Logstasher::RequestLogSubscriber do
269
121
 
270
122
  it "should not include a location by default" do
271
123
  subscriber.process_action(event)
272
- log_output.string.should_not =~ /"location":/
124
+ log_output.json['@fields']['location'].should be_nil
273
125
  end
274
126
  end
275
127
 
276
- describe "with custom_options configured for graylog2 output" do
277
- before do
278
- Logstasher::log_format = :graylog2
279
- end
280
-
281
- it "should combine the hash properly for the output" do
282
- Logstasher.custom_options = {:data => "value"}
283
- subscriber.process_action(event)
284
- log_output.string.should include(':_data=>"value"')
285
- end
286
-
287
- it "should combine the output of a lambda properly" do
288
- Logstasher.custom_options = lambda {|event| {:data => "value"}}
289
- subscriber.process_action(event)
290
- log_output.string.should include(':_data=>"value"')
291
- end
292
-
293
- it "should work if the method returns nil" do
294
- Logstasher.custom_options = lambda {|event| nil}
128
+ describe "with append_custom_params block specified" do
129
+ let(:request) { mock(:remote_ip => '10.0.0.1')}
130
+ it "should add default custom data to the output" do
131
+ request.stub(:params => event.payload[:params])
132
+ LogStasher.add_default_fields_to_payload(event.payload, request)
295
133
  subscriber.process_action(event)
296
- log_output.string.should be_present
134
+ log_output.json['@fields']['ip'].should == '10.0.0.1'
135
+ log_output.json['@fields']['route'].should == 'home#index'
136
+ log_output.json['@fields']['parameters'].should == "foo=bar\n"
297
137
  end
298
138
  end
299
139
 
300
- describe "with custom_options configured for logstasher output" do
140
+ describe "with append_custom_params block specified" do
301
141
  before do
302
- Logstasher::log_format = :logstasher
303
- end
304
-
305
- it "should combine the hash properly for the output" do
306
- Logstasher.custom_options = {:data => "value"}
307
- subscriber.process_action(event)
308
- log_output.string.should =~ / data=value/
309
- end
310
- it "should combine the output of a lambda properly" do
311
- Logstasher.custom_options = lambda {|event| {:data => "value"}}
312
- subscriber.process_action(event)
313
- log_output.string.should =~ / data=value/
314
- end
315
- it "should work if the method returns nil" do
316
- Logstasher.custom_options = lambda {|event| nil}
317
- subscriber.process_action(event)
318
- log_output.string.should be_present
319
- end
320
- end
321
-
322
- describe "with custom_options configured for logstash output" do
323
- before do
324
- Logstasher::log_format = :logstash
142
+ LogStasher.stub(:add_custom_fields) do |&block|
143
+ @block = block
144
+ end
145
+ LogStasher.add_custom_fields do |payload|
146
+ payload[:user] = 'user'
147
+ end
148
+ LogStasher.custom_fields += [:user]
325
149
  end
326
150
 
327
- it "should combine the hash properly for the output" do
328
- Logstasher.custom_options = {:data => "value"}
329
- subscriber.process_action(event)
330
- log_output.string.should =~ /"data":"value"/
331
- end
332
- it "should combine the output of a lambda properly" do
333
- Logstasher.custom_options = lambda {|event| {:data => "value"}}
334
- subscriber.process_action(event)
335
- log_output.string.should =~ /"data":"value"/
336
- end
337
- it "should work if the method returns nil" do
338
- Logstasher.custom_options = lambda {|event| nil}
151
+ it "should add the custom data to the output" do
152
+ @block.call(event.payload)
339
153
  subscriber.process_action(event)
340
- log_output.string.should be_present
154
+ log_output.json['@fields']['user'].should == 'user'
341
155
  end
342
156
  end
343
157
 
@@ -1,12 +1,6 @@
1
1
  require 'spec_helper'
2
- require 'logstasher'
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 Logstasher do
2
+
3
+ describe LogStasher do
10
4
  describe "when removing Rails' log subscribers" do
11
5
  after do
12
6
  ActionController::LogSubscriber.attach_to :action_controller
@@ -15,7 +9,7 @@ describe Logstasher do
15
9
 
16
10
  it "should remove subscribers for controller events" do
17
11
  expect {
18
- Logstasher.remove_existing_log_subscriptions
12
+ LogStasher.remove_existing_log_subscriptions
19
13
  }.to change {
20
14
  ActiveSupport::Notifications.notifier.listeners_for('process_action.action_controller')
21
15
  }
@@ -23,7 +17,7 @@ describe Logstasher do
23
17
 
24
18
  it "should remove subscribers for all events" do
25
19
  expect {
26
- Logstasher.remove_existing_log_subscriptions
20
+ LogStasher.remove_existing_log_subscriptions
27
21
  }.to change {
28
22
  ActiveSupport::Notifications.notifier.listeners_for('render_template.action_view')
29
23
  }
@@ -32,9 +26,101 @@ describe Logstasher do
32
26
  it "shouldn't remove subscribers that aren't from Rails" do
33
27
  blk = -> {}
34
28
  ActiveSupport::Notifications.subscribe("process_action.action_controller", &blk)
35
- Logstasher.remove_existing_log_subscriptions
29
+ LogStasher.remove_existing_log_subscriptions
36
30
  listeners = ActiveSupport::Notifications.notifier.listeners_for('process_action.action_controller')
37
31
  listeners.size.should > 0
38
32
  end
39
33
  end
34
+
35
+ describe '.appened_default_info_to_payload' do
36
+ let(:params) { {'a' => '1', 'b' => 2, 'action' => 'action', 'controller' => 'test'}.with_indifferent_access }
37
+ let(:payload) { {:params => params} }
38
+ let(:request) { mock(:params => params, :remote_ip => '10.0.0.1')}
39
+ after do
40
+ LogStasher.custom_fields = []
41
+ end
42
+ it 'appends default parameters to payload' do
43
+ LogStasher.custom_fields = []
44
+ LogStasher.add_default_fields_to_payload(payload, request)
45
+ payload[:ip].should == '10.0.0.1'
46
+ payload[:route].should == 'test#action'
47
+ payload[:parameters].should == "a=1\nb=2\n"
48
+ LogStasher.custom_fields.should == [:ip, :route, :parameters]
49
+ end
50
+ end
51
+
52
+ describe '.append_custom_params' do
53
+ let(:block) { ->{} }
54
+ it 'defines a method in ActionController::Base' do
55
+ ActionController::Base.should_receive(:send).with(:define_method, :logtasher_add_custom_fields_to_payload, &block)
56
+ LogStasher.add_custom_fields(&block)
57
+ end
58
+ end
59
+
60
+ describe '.setup' do
61
+ let(:logger) { mock }
62
+ let(:logstasher_config) { mock(:logger => logger,:log_level => 'warn') }
63
+ let(:config) { mock(:logstasher => logstasher_config) }
64
+ let(:app) { mock(:config => config) }
65
+ before do
66
+ config.stub(:action_dispatch => mock(:rack_cache => false))
67
+ end
68
+ it 'defines a method in ActionController::Base' do
69
+ LogStasher.should_receive(:require).with('logstasher/rails_ext/action_controller/metal/instrumentation')
70
+ LogStasher.should_receive(:require).with('logstash/event')
71
+ LogStasher.should_receive(:suppress_app_logs).with(app)
72
+ LogStasher::RequestLogSubscriber.should_receive(:attach_to).with(:action_controller)
73
+ logger.should_receive(:level=).with('warn')
74
+ LogStasher.setup(app)
75
+ LogStasher.enabled.should be_true
76
+ LogStasher.custom_fields.should == []
77
+ end
78
+ end
79
+
80
+ describe '.supress_app_logs' do
81
+ let(:logstasher_config){ mock(:logstasher => mock(:supress_app_log => true))}
82
+ let(:app){ mock(:config => logstasher_config)}
83
+ it 'removes existing subscription if enabled' do
84
+ LogStasher.should_receive(:require).with('logstasher/rails_ext/rack/logger')
85
+ LogStasher.should_receive(:remove_existing_log_subscriptions)
86
+ LogStasher.suppress_app_logs(app)
87
+ end
88
+ end
89
+
90
+ describe '.appended_params' do
91
+ it 'returns the stored var in current thread' do
92
+ Thread.current[:logstasher_custom_fields] = :test
93
+ LogStasher.custom_fields.should == :test
94
+ end
95
+ end
96
+
97
+ describe '.appended_params=' do
98
+ it 'returns the stored var in current thread' do
99
+ LogStasher.custom_fields = :test
100
+ Thread.current[:logstasher_custom_fields].should == :test
101
+ end
102
+ end
103
+
104
+ describe '.log' do
105
+ let(:logger) { mock() }
106
+ before do
107
+ LogStasher.logger = logger
108
+ LogStash::Time.stub(:now => 'timestamp')
109
+ end
110
+ it 'adds to log with specified level' do
111
+ logger.should_receive(:send).with('warn?').and_return(true)
112
+ logger.should_receive(:send).with('warn',"{\"@source\":\"unknown\",\"@tags\":[\"log\"],\"@fields\":{\"message\":\"WARNING\",\"level\":\"warn\"},\"@timestamp\":\"timestamp\"}")
113
+ LogStasher.log('warn', 'WARNING')
114
+ end
115
+ end
116
+
117
+ %w( fatal error warn info debug unknown ).each do |severity|
118
+ describe ".#{severity}" do
119
+ let(:message) { "This is a #{severity} message" }
120
+ it 'should log with specified level' do
121
+ LogStasher.should_receive(:log).with(severity.to_sym, message)
122
+ LogStasher.send(severity, message )
123
+ end
124
+ end
125
+ end
40
126
  end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,47 @@
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
1
+ # Notice there is a .rspec file in the root folder. It defines rspec arguments
2
+
3
+ # Ruby 1.9 uses simplecov. The ENV['COVERAGE'] is set when rake coverage is run in ruby 1.9
4
+ if ENV['COVERAGE']
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ # Remove the spec folder from coverage. By default all code files are included. For more config options see
8
+ # https://github.com/colszowka/simplecov
9
+ add_filter File.expand_path('../../spec', __FILE__)
10
+ end
11
+ end
12
+
13
+ # Modify load path so you can require 'multi_config' directly.
14
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
15
+
16
+ require 'rubygems'
17
+ # Loads bundler setup tasks. Now if I run spec without installing gems then it would say gem not installed and
18
+ # do bundle install instead of ugly load error on require.
19
+ require 'bundler/setup'
20
+
21
+ # This will require me all the gems automatically for the groups. If I do only .setup then I will have to require gems
22
+ # manually. Note that you have still have to require some gems if they are part of bigger gem like ActiveRecord which is
23
+ # part of Rails. You can say :require => false in gemfile to always use explicit requiring
24
+ Bundler.require(:default, :test)
25
+
26
+ # Set Rails environment as test
27
+ ENV['RAILS_ENV'] = 'test'
28
+
7
29
  require 'action_pack'
30
+ require 'action_controller'
31
+ require 'logstasher'
32
+ require 'active_support/notifications'
33
+ require 'active_support/core_ext/string'
34
+ require 'active_support/log_subscriber'
35
+ require 'action_controller/log_subscriber'
36
+ require 'action_view/log_subscriber'
37
+ require 'active_support/core_ext/hash/except'
38
+ require 'active_support/core_ext/hash/indifferent_access'
39
+ require 'active_support/core_ext/hash/slice'
40
+ require 'active_support/core_ext/string'
41
+ require 'active_support/core_ext/time/zones'
42
+ require 'abstract_controller/base'
43
+ require 'logger'
44
+ require 'logstash-event'
8
45
 
9
46
  RSpec.configure do |config|
10
47
  config.treat_symbols_as_metadata_keys_with_true_values = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstasher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-06 00:00:00.000000000 Z
12
+ date: 2013-04-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: logstash-event
@@ -44,13 +44,13 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: guard-rspec
47
+ name: bundler
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
51
  - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 1.0.0
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,39 +58,23 @@ dependencies:
58
58
  requirements:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 1.0.0
62
62
  - !ruby/object:Gem::Dependency
63
- name: activesupport
63
+ name: rails
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
67
  - - ! '>='
68
68
  - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :runtime
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: '0'
78
- - !ruby/object:Gem::Dependency
79
- name: actionpack
80
- requirement: !ruby/object:Gem::Requirement
81
- none: false
82
- requirements:
83
- - - ! '>='
84
- - !ruby/object:Gem::Version
85
- version: '0'
86
- type: :runtime
69
+ version: '3.0'
70
+ type: :development
87
71
  prerelease: false
88
72
  version_requirements: !ruby/object:Gem::Requirement
89
73
  none: false
90
74
  requirements:
91
75
  - - ! '>='
92
76
  - !ruby/object:Gem::Version
93
- version: '0'
77
+ version: '3.0'
94
78
  description: Awesome rails logs
95
79
  email:
96
80
  - shadab.ansari@gmail.com
@@ -127,12 +111,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
111
  - - ! '>='
128
112
  - !ruby/object:Gem::Version
129
113
  version: '0'
114
+ segments:
115
+ - 0
116
+ hash: -4048643813953369805
130
117
  required_rubygems_version: !ruby/object:Gem::Requirement
131
118
  none: false
132
119
  requirements:
133
120
  - - ! '>='
134
121
  - !ruby/object:Gem::Version
135
122
  version: '0'
123
+ segments:
124
+ - 0
125
+ hash: -4048643813953369805
136
126
  requirements: []
137
127
  rubyforge_project: logstasher
138
128
  rubygems_version: 1.8.25