rackstash 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +17 -9
- data/.travis.yml +26 -0
- data/Gemfile +31 -0
- data/LICENSE.txt +18 -17
- data/README.md +155 -6
- data/Rakefile +7 -7
- data/bin/rackstash +5 -0
- data/lib/rackstash.rb +85 -2
- data/lib/rackstash/buffered_logger.rb +269 -0
- data/lib/rackstash/framework/base.rb +13 -0
- data/lib/rackstash/framework/rack.rb +9 -0
- data/lib/rackstash/framework/rails2.rb +43 -0
- data/lib/rackstash/framework/rails3.rb +46 -0
- data/lib/rackstash/log_middleware.rb +27 -0
- data/lib/rackstash/log_severity.rb +9 -0
- data/lib/rackstash/log_subscriber.rb +108 -0
- data/lib/rackstash/rails_ext/action_controller.rb +101 -0
- data/lib/rackstash/rails_ext/initializer.rb +21 -0
- data/lib/rackstash/railtie.rb +19 -0
- data/lib/rackstash/runner.rb +36 -0
- data/lib/rackstash/version.rb +1 -1
- data/lib/tasks/rackstash.rb +9 -0
- data/rackstash.gemspec +26 -26
- data/test/buffered_logger_test.rb +191 -0
- data/test/log_subscriber_test.rb +89 -0
- data/test/rackstash_test.rb +104 -0
- data/test/runner_test.rb +82 -0
- data/test/test_helper.rb +13 -0
- metadata +105 -27
- data/CODE_OF_CONDUCT.md +0 -49
- data/bin/console +0 -14
- data/bin/setup +0 -8
data/lib/rackstash/version.rb
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
desc "Execute a sub-task in a rackstash log scope"
|
2
|
+
task :with_rackstash, [:task] do |t, args|
|
3
|
+
Rake::Task[:environment].invoke if Rake::Task[:environment]
|
4
|
+
|
5
|
+
Rackstash.tags |= ["rake", "rake::#{args[:task]}"]
|
6
|
+
Rackstash.with_log_buffer do
|
7
|
+
Rake::Task[args[:task]].invoke
|
8
|
+
end
|
9
|
+
end
|
data/rackstash.gemspec
CHANGED
@@ -1,35 +1,35 @@
|
|
1
|
-
#
|
2
|
-
# Copyright 2016 Holger Just
|
3
|
-
#
|
4
|
-
# This software may be modified and distributed under the terms
|
5
|
-
# of the MIT license. See the LICENSE.txt file for details.
|
6
|
-
|
1
|
+
# -*- encoding: utf-8 -*-
|
7
2
|
lib = File.expand_path('../lib', __FILE__)
|
8
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
9
4
|
require 'rackstash/version'
|
10
5
|
|
11
|
-
Gem::Specification.new do |
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "rackstash"
|
8
|
+
gem.version = Rackstash::VERSION
|
9
|
+
gem.authors = ["Holger Just, Planio GmbH"]
|
10
|
+
gem.email = ["holger@plan.io"]
|
11
|
+
gem.description = %q{Making Rack and Rails logs useful}
|
12
|
+
gem.summary = %q{Making Rack and Rails logs useful}
|
13
|
+
gem.homepage = "https://github.com/planio-gmbh/rackstash"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
https://github.com/meineerde/rackstash for any progress on releasing this
|
20
|
-
gem.
|
21
|
-
TXT
|
22
|
-
spec.homepage = 'https://github.com/meineerde/rackstash'
|
23
|
-
spec.license = 'MIT'
|
20
|
+
gem.add_runtime_dependency "logstash-event", "< 1.2.0"
|
21
|
+
gem.add_runtime_dependency "activesupport"
|
22
|
+
gem.add_runtime_dependency "thor"
|
24
23
|
|
25
|
-
|
24
|
+
gem.add_development_dependency "minitest"
|
25
|
+
gem.add_development_dependency "rake"
|
26
|
+
gem.add_development_dependency "json"
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
spec.bindir = 'exe'
|
30
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
-
spec.require_paths = ['lib']
|
28
|
+
if RbConfig::CONFIG["RUBY_INSTALL_NAME"] == "rbx"
|
29
|
+
gem.add_runtime_dependency "rubysl", "~> 2.0"
|
32
30
|
|
33
|
-
|
34
|
-
|
31
|
+
gem.add_development_dependency 'rubinius-coverage', '~> 2.0'
|
32
|
+
gem.add_development_dependency 'parser', '~> 2.1.0.pre1'
|
33
|
+
gem.add_development_dependency 'racc', '~> 1.4'
|
34
|
+
end
|
35
35
|
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rackstash/buffered_logger'
|
3
|
+
|
4
|
+
require 'stringio'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
describe Rackstash::BufferedLogger do
|
8
|
+
let(:log_output){ StringIO.new }
|
9
|
+
let(:base_logger){ Logger.new(log_output) }
|
10
|
+
|
11
|
+
def log_line
|
12
|
+
log_output.string.lines.to_a.last
|
13
|
+
end
|
14
|
+
def json
|
15
|
+
JSON.parse(log_line)
|
16
|
+
end
|
17
|
+
|
18
|
+
subject do
|
19
|
+
Rackstash::BufferedLogger.new(base_logger)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "must be properly initialized" do
|
23
|
+
subject.logger.formatter.must_be_instance_of Rackstash::BufferedLogger::SimpleFormatter
|
24
|
+
subject.buffering?.must_equal false
|
25
|
+
subject.fields.must_equal nil
|
26
|
+
subject.tags.must_equal nil
|
27
|
+
subject.source.must_equal nil
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when passing a logger" do
|
31
|
+
it "delegates only defined methods" do
|
32
|
+
# sanity
|
33
|
+
base_logger.wont_respond_to :flush
|
34
|
+
base_logger.wont_respond_to :auto_flushing
|
35
|
+
|
36
|
+
base_logger.instance_eval{ def flush; end }
|
37
|
+
|
38
|
+
base_logger.must_respond_to :flush
|
39
|
+
base_logger.wont_respond_to :auto_flushing
|
40
|
+
subject.must_respond_to :flush
|
41
|
+
subject.wont_respond_to :auto_flushing
|
42
|
+
end
|
43
|
+
|
44
|
+
it "delegates later methods too" do
|
45
|
+
base_logger.wont_respond_to :auto_flushing # sanity
|
46
|
+
base_logger.instance_eval{ def auto_flushing; end }
|
47
|
+
|
48
|
+
base_logger.must_respond_to :auto_flushing
|
49
|
+
subject.must_respond_to :auto_flushing
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "when using the Logger API" do
|
54
|
+
it "forwards base methods to the underlying logger" do
|
55
|
+
subject.logger.must_be_same_as base_logger
|
56
|
+
|
57
|
+
subject.level.must_equal base_logger.level
|
58
|
+
subject.progname.must_be_same_as base_logger.progname
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "when logging unbuffered" do
|
63
|
+
it "supports adding log messages" do
|
64
|
+
subject.add nil, "log_empty"
|
65
|
+
json["@message"].must_equal "[UNKNOWN] log_empty"
|
66
|
+
|
67
|
+
%w[debug info warn error fatal unknown].each do |severity|
|
68
|
+
subject.send severity, "log_#{severity}"
|
69
|
+
|
70
|
+
tag = "[#{severity.upcase}] ".rjust(10)
|
71
|
+
json["@message"].must_equal "#{tag}log_#{severity}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "supports the << method" do
|
76
|
+
subject << "Hello World"
|
77
|
+
log_output.string.must_equal "Hello World"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "ignores the instruction to not log a message" do
|
81
|
+
subject.do_not_log!.must_equal false
|
82
|
+
subject.info "Hello World"
|
83
|
+
json["@message"].must_equal " [INFO] Hello World"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "includes the default fields" do
|
87
|
+
subject.info "Foo Bar Baz"
|
88
|
+
|
89
|
+
json = self.json
|
90
|
+
json.keys.sort.must_equal %w[@fields @message @source @tags @timestamp]
|
91
|
+
json["@fields"].keys.sort.must_equal %w[log_id pid]
|
92
|
+
|
93
|
+
json["@fields"]["pid"].must_equal Process.pid
|
94
|
+
json["@fields"]["log_id"].must_match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/)
|
95
|
+
json["@message"].must_equal " [INFO] Foo Bar Baz"
|
96
|
+
json["@source"].must_be_nil
|
97
|
+
json["@tags"].must_equal []
|
98
|
+
# Timestamp is less than 2 seconds ago
|
99
|
+
timestamp_range = ((Time.now-2)..Time.now)
|
100
|
+
method = timestamp_range.respond_to?(:cover?) ? :cover? : :===
|
101
|
+
timestamp_range.must_be method, Time.parse(json["@timestamp"])
|
102
|
+
end
|
103
|
+
|
104
|
+
it "allows to log nil" do
|
105
|
+
subject.info nil
|
106
|
+
json["@message"].must_equal " [INFO] "
|
107
|
+
end
|
108
|
+
|
109
|
+
it "allows to log numerics" do
|
110
|
+
subject.info 12.123
|
111
|
+
json["@message"].must_equal " [INFO] 12.123"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "allows to set a source" do
|
115
|
+
subject.source = "BufferedLoggerTest"
|
116
|
+
subject.info nil
|
117
|
+
json["@source"].must_equal "BufferedLoggerTest"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "when using a buffer" do
|
122
|
+
it "should buffer logs" do
|
123
|
+
subject.with_buffer do
|
124
|
+
subject.info("Hello")
|
125
|
+
log_output.string.must_be_empty
|
126
|
+
subject.info("World")
|
127
|
+
end
|
128
|
+
|
129
|
+
json["@message"].must_equal " [INFO] Hello\n [INFO] World"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "can set additional tags" do
|
133
|
+
subject.with_buffer do
|
134
|
+
subject.tags << :foo
|
135
|
+
subject.info("Hello")
|
136
|
+
end
|
137
|
+
|
138
|
+
json["@tags"].must_equal ["foo"]
|
139
|
+
json["@message"].must_equal " [INFO] Hello"
|
140
|
+
end
|
141
|
+
|
142
|
+
it "can set additional fields" do
|
143
|
+
subject.with_buffer do
|
144
|
+
subject.fields[:foo] = :bar
|
145
|
+
subject.info("Hello")
|
146
|
+
end
|
147
|
+
|
148
|
+
json["@fields"]["foo"].must_equal "bar"
|
149
|
+
json["@message"].must_equal " [INFO] Hello"
|
150
|
+
end
|
151
|
+
|
152
|
+
it "can overwrite automatically filled fields" do
|
153
|
+
subject.with_buffer do
|
154
|
+
subject.fields[:pid] = "foobarbaz"
|
155
|
+
subject.info("Hello")
|
156
|
+
end
|
157
|
+
|
158
|
+
json["@fields"]["pid"].must_equal "foobarbaz"
|
159
|
+
json["@message"].must_equal " [INFO] Hello"
|
160
|
+
end
|
161
|
+
|
162
|
+
it "captures exceptions" do
|
163
|
+
exception = Class.new(StandardError) do
|
164
|
+
def self.name
|
165
|
+
"SomethingWrongError"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
proc do
|
170
|
+
subject.with_buffer do
|
171
|
+
raise exception, "Something is wrong"
|
172
|
+
end
|
173
|
+
end.must_raise(exception)
|
174
|
+
|
175
|
+
json["@message"].must_equal ""
|
176
|
+
json["@fields"]["error"].must_equal "SomethingWrongError"
|
177
|
+
json["@fields"]["error_message"].must_equal "Something is wrong"
|
178
|
+
json["@fields"]["error_backtrace"].must_match(/\A#{__FILE__}:\d+/)
|
179
|
+
json["@fields"]["error_backtrace"].must_match(/^#{File.expand_path("../../lib/rackstash/buffered_logger.rb", __FILE__)}:\d+:in `with_buffer'$/)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "doesn't log anything when using do_not_log!" do
|
183
|
+
subject.with_buffer do
|
184
|
+
subject.do_not_log!.must_equal true
|
185
|
+
subject.info "Hello World"
|
186
|
+
end
|
187
|
+
|
188
|
+
log_line.must_be :nil?
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rackstash'
|
3
|
+
|
4
|
+
if Rackstash.framework == "rails3"
|
5
|
+
require 'rackstash/log_subscriber'
|
6
|
+
require 'active_support/notifications'
|
7
|
+
|
8
|
+
describe Rackstash::LogSubscriber do
|
9
|
+
let(:log_output){ StringIO.new }
|
10
|
+
let(:base_logger){ Logger.new(log_output) }
|
11
|
+
let(:logger){ Rackstash::BufferedLogger.new(base_logger) }
|
12
|
+
|
13
|
+
let(:flush_and_pop_buffer){ logger.flush_and_pop_buffer }
|
14
|
+
let(:json) do
|
15
|
+
flush_and_pop_buffer
|
16
|
+
|
17
|
+
JSON.parse(log_output.string.lines.to_a.last)
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:subscriber){ Rackstash::LogSubscriber.new }
|
21
|
+
let(:event) do
|
22
|
+
now = Time.now
|
23
|
+
duration = 123 # milliseconds
|
24
|
+
ActiveSupport::Notifications::Event.new(
|
25
|
+
"process_action.action_controller", now, now + (duration.to_f / 1000), 2, {
|
26
|
+
:status => 200, :format => "application/json", :method => "GET", :path => "/home?foo=bar",
|
27
|
+
:params => {
|
28
|
+
"controller" => "home", "action" => "index", "foo" => "bar"
|
29
|
+
}, :db_runtime => 0.02, :view_runtime => 0.01
|
30
|
+
}
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:redirect) do
|
35
|
+
ActiveSupport::Notifications::Event.new(
|
36
|
+
'redirect_to.action_controller', Time.now, Time.now, 1, :location => 'http://example.com', :status => 302
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
before do
|
41
|
+
@original_rails_logger = Rails.logger
|
42
|
+
Rails.logger = logger
|
43
|
+
logger.push_buffer
|
44
|
+
end
|
45
|
+
after do
|
46
|
+
flush_and_pop_buffer
|
47
|
+
Rails.logger = @original_rails_logger
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#redirect_to" do
|
51
|
+
it "should store the location in a thread local variable" do
|
52
|
+
subscriber.redirect_to(redirect)
|
53
|
+
Thread.current[:rackstash_location].must_equal "http://example.com"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#process_action" do
|
58
|
+
it "includes the controller and action" do
|
59
|
+
subscriber.process_action(event)
|
60
|
+
json["@fields"]["controller"].must_equal "home"
|
61
|
+
json["@fields"]["action"].must_equal "index"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "includes the duration" do
|
65
|
+
subscriber.process_action(event)
|
66
|
+
json["@fields"]["duration"].must_be_within_epsilon 123
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should include the view rendering time" do
|
70
|
+
subscriber.process_action(event)
|
71
|
+
json["@fields"]["view"].must_be_within_epsilon 0.01
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should include the database rendering time" do
|
75
|
+
subscriber.process_action(event)
|
76
|
+
json["@fields"]["db"].must_be_within_epsilon 0.02
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should add a 500 status when an exception occurred" do
|
80
|
+
event.payload[:status] = nil
|
81
|
+
event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
|
82
|
+
subscriber.process_action(event)
|
83
|
+
|
84
|
+
json["@fields"]["error"].must_equal "AbstractController::ActionNotFound"
|
85
|
+
json["@fields"]["error_message"].must_equal "Route not found"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rackstash'
|
3
|
+
|
4
|
+
describe Rackstash do
|
5
|
+
let(:log_output){ StringIO.new }
|
6
|
+
let(:base_logger){ Logger.new(log_output) }
|
7
|
+
def json
|
8
|
+
JSON.parse(log_output.string.lines.to_a.last)
|
9
|
+
end
|
10
|
+
|
11
|
+
subject do
|
12
|
+
Rackstash::BufferedLogger.new(base_logger)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "fields" do
|
16
|
+
after do
|
17
|
+
Rackstash.fields = HashWithIndifferentAccess.new
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can be defined as a Hash" do
|
21
|
+
Rackstash.fields = {:foo => :bar}
|
22
|
+
Rackstash.fields.must_be_instance_of HashWithIndifferentAccess
|
23
|
+
|
24
|
+
subject.info("foo")
|
25
|
+
json["@fields"]["foo"].must_equal "bar"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can be defined as a proc" do
|
29
|
+
Rackstash.fields = proc do
|
30
|
+
{:foo => :baz}
|
31
|
+
end
|
32
|
+
|
33
|
+
subject.info("foo")
|
34
|
+
json["@fields"]["foo"].must_equal "baz"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can be defined as a lambda" do
|
38
|
+
Rackstash.fields = lambda do
|
39
|
+
{:foo => :baz}
|
40
|
+
end
|
41
|
+
|
42
|
+
subject.info("foo")
|
43
|
+
json["@fields"]["foo"].must_equal "baz"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "request_fields" do
|
48
|
+
after do
|
49
|
+
Rackstash.request_fields = HashWithIndifferentAccess.new
|
50
|
+
end
|
51
|
+
|
52
|
+
let(:controller) do
|
53
|
+
controller = Class.new(Object){attr_accessor :status}.new
|
54
|
+
controller.status = "running"
|
55
|
+
controller
|
56
|
+
end
|
57
|
+
|
58
|
+
it "won't be included in unbuffered mode" do
|
59
|
+
Rackstash.request_fields = {:foo => :bar}
|
60
|
+
|
61
|
+
subject.info("foo")
|
62
|
+
json["@fields"].keys.wont_include "foo"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "can be defined as a hash" do
|
66
|
+
Rackstash.request_fields = {:foo => :bar}
|
67
|
+
|
68
|
+
Rackstash.request_fields(controller).must_be_instance_of HashWithIndifferentAccess
|
69
|
+
Rackstash.request_fields(controller).must_equal({"foo" => :bar})
|
70
|
+
|
71
|
+
# TODO: fake a real request and ensure that the field gets set in the log output
|
72
|
+
end
|
73
|
+
|
74
|
+
it "can be defined as a proc" do
|
75
|
+
Rackstash.request_fields = proc do |controller|
|
76
|
+
{
|
77
|
+
:foo => :bar,
|
78
|
+
:status => @status,
|
79
|
+
:instance_status => controller.status
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
Rackstash.request_fields(controller).must_be_instance_of HashWithIndifferentAccess
|
84
|
+
Rackstash.request_fields(controller).must_equal({"foo" => :bar, "status" => "running", "instance_status" => "running"})
|
85
|
+
|
86
|
+
# TODO: fake a real request and ensure that the field gets set in the log output
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can be defined as a lambda" do
|
90
|
+
Rackstash.request_fields = lambda do |controller|
|
91
|
+
{
|
92
|
+
:foo => :bar,
|
93
|
+
:status => @status,
|
94
|
+
:instance_status => controller.status
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
Rackstash.request_fields(controller).must_be_instance_of HashWithIndifferentAccess
|
99
|
+
Rackstash.request_fields(controller).must_equal({"foo" => :bar, "status" => "running", "instance_status" => "running"})
|
100
|
+
|
101
|
+
# TODO: fake a real request and ensure that the field gets set in the log output
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|