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 +1 -0
- data/.travis.yml +1 -0
- data/Gemfile +8 -3
- data/Guardfile +1 -0
- data/README.md +48 -2
- data/Rakefile +41 -0
- data/lib/logstasher.rb +57 -17
- data/lib/logstasher/log_subscriber.rb +13 -11
- data/lib/logstasher/rails_ext/action_controller/metal/instrumentation.rb +5 -5
- data/lib/logstasher/railtie.rb +2 -2
- data/lib/logstasher/version.rb +2 -2
- data/logstasher.gemspec +3 -4
- data/spec/logstasher_logsubscriber_spec.rb +57 -243
- data/spec/logstasher_spec.rb +97 -11
- data/spec/spec_helper.rb +43 -6
- metadata +15 -25
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
-
source "
|
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 '
|
8
|
-
gem '
|
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
data/README.md
CHANGED
@@ -1,3 +1,49 @@
|
|
1
|
-
Logstasher - Awesome
|
2
|
-
=======
|
1
|
+
# Logstasher - Awesome Logging for Rails [](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
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
47
|
+
# Path instrumentation class to insert our hook
|
44
48
|
require 'logstasher/rails_ext/action_controller/metal/instrumentation'
|
45
49
|
require 'logstash/event'
|
46
|
-
|
47
|
-
|
48
|
-
self.logger = app.config.logstasher.logger || Logger.new("#{Rails.root}/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
|
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!
|
14
|
+
data.merge! extract_custom_fields(payload)
|
15
15
|
|
16
|
-
event = LogStash::Event.new(
|
17
|
-
|
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}
|
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
|
88
|
-
|
89
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
19
|
-
|
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)
|
data/lib/logstasher/railtie.rb
CHANGED
@@ -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
|
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
|
-
|
11
|
+
LogStasher.setup(app) if app.config.logstasher.enabled
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
data/lib/logstasher/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = "0.
|
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 =
|
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 "
|
25
|
-
s.
|
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
|
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
|
-
|
16
|
+
LogStasher.logger = logger
|
17
|
+
LogStasher.custom_fields = []
|
19
18
|
end
|
20
19
|
|
21
|
-
let(:subscriber) {
|
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
|
-
|
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
|
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
|
39
|
+
it "should contain request tag" do
|
74
40
|
subscriber.process_action(event)
|
75
|
-
log_output.
|
41
|
+
log_output.json['@tags'].should include 'request'
|
76
42
|
end
|
77
43
|
|
78
|
-
it "should
|
44
|
+
it "should contain HTTP method" do
|
79
45
|
subscriber.process_action(event)
|
80
|
-
log_output.
|
46
|
+
log_output.json['@fields']['method'].should == 'GET'
|
81
47
|
end
|
82
48
|
|
83
|
-
it "should
|
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.
|
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
|
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.
|
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.
|
61
|
+
log_output.json['@fields']['status'].should == 200
|
139
62
|
end
|
140
63
|
|
141
|
-
it "should include the controller
|
64
|
+
it "should include the controller" do
|
142
65
|
subscriber.process_action(event)
|
143
|
-
log_output.
|
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
|
69
|
+
it "should include the action" do
|
148
70
|
subscriber.process_action(event)
|
149
|
-
log_output.
|
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.
|
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.
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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.
|
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.
|
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.
|
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.
|
124
|
+
log_output.json['@fields']['location'].should be_nil
|
273
125
|
end
|
274
126
|
end
|
275
127
|
|
276
|
-
describe "with
|
277
|
-
|
278
|
-
|
279
|
-
|
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.
|
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
|
140
|
+
describe "with append_custom_params block specified" do
|
301
141
|
before do
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
328
|
-
|
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.
|
154
|
+
log_output.json['@fields']['user'].should == 'user'
|
341
155
|
end
|
342
156
|
end
|
343
157
|
|
data/spec/logstasher_spec.rb
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
2
|
-
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
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.
|
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-
|
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:
|
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:
|
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:
|
61
|
+
version: 1.0.0
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
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: :
|
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
|