rackstash 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|