rackstash 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Rackstash
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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
@@ -1,35 +1,35 @@
1
- # coding: utf-8
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 |spec|
12
- spec.name = 'rackstash'
13
- spec.version = Rackstash::VERSION
14
- spec.authors = ['Holger Just']
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
- spec.summary = 'This is a placeholder for now'
17
- spec.description = <<-TXT.tap { |s| s.gsub!(/\s+|\n/, ' ').strip! }
18
- This is just a placeholder for now. Please have a look at
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
- spec.required_ruby_version = '>= 2.1.0'
24
+ gem.add_development_dependency "minitest"
25
+ gem.add_development_dependency "rake"
26
+ gem.add_development_dependency "json"
26
27
 
27
- files = `git ls-files -z`.split("\x0")
28
- spec.files = files.reject { |f| f.match(%r{^(test|spec|feature)/}) }
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
- spec.add_development_dependency 'bundler', '~> 1.11'
34
- spec.add_development_dependency 'rake', '~> 10.0'
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