stasher 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.
@@ -0,0 +1,40 @@
1
+ require 'logger'
2
+
3
+ module Stasher
4
+ class Logger < ::Logger
5
+ def initialize(device = nil)
6
+ super(device)
7
+ end
8
+
9
+ def add(severity, message = nil, progname = nil, &block)
10
+ severity ||= UNKNOWN
11
+ if severity < @level
12
+ return true
13
+ end
14
+
15
+ progname ||= @progname
16
+ if message.nil?
17
+ if block_given?
18
+ message = yield
19
+ else
20
+ message = progname
21
+ progname = @progname
22
+ end
23
+ end
24
+
25
+ if message.is_a? String
26
+ message = format_message(severity, Time.now, progname, message).chomp
27
+ end
28
+
29
+ severity = format_severity(severity)
30
+
31
+ Stasher.log severity, message
32
+
33
+ true
34
+ end
35
+
36
+ private
37
+
38
+
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ module ActionController
2
+ module Instrumentation
3
+ def process_action(*args)
4
+ raw_payload = {
5
+ :controller => self.class.name,
6
+ :action => self.action_name,
7
+ :params => request.filtered_parameters,
8
+ :ip => request.remote_ip,
9
+ :format => request.format.try(:ref),
10
+ :method => request.method,
11
+ :path => (request.fullpath rescue "unknown")
12
+ }
13
+
14
+ Stasher.add_default_fields_to_scope(Stasher::CurrentScope.fields, request)
15
+
16
+ if self.respond_to?(:stasher_add_custom_fields_to_scope)
17
+ stasher_add_custom_fields_to_scope(Stasher::CurrentScope.fields)
18
+ end
19
+
20
+ ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
21
+
22
+ ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
23
+ result = super
24
+ payload[:status] = response.status
25
+ append_info_to_payload(payload)
26
+ result
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require 'rails/rack/logger'
2
+
3
+ module Rails
4
+ module Rack
5
+ # Overwrites defaults of Rails::Rack::Logger that cause
6
+ # unnecessary logging.
7
+ # This effectively removes the log lines from the log
8
+ # that say:
9
+ # Started GET / for 192.168.2.1...
10
+ class Logger
11
+ # Overwrites Rails 3.2 code that logs new requests
12
+ def call_app(*args)
13
+ env = args.last
14
+ @app.call(env)
15
+ ensure
16
+ ActiveSupport::LogSubscriber.flush_all!
17
+ end
18
+
19
+ # Overwrites Rails 3.0/3.1 code that logs new requests
20
+ def before_dispatch(env)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require 'rails/railtie'
2
+ require 'action_view/log_subscriber'
3
+ require 'action_controller/log_subscriber'
4
+
5
+ module Stasher
6
+ class Railtie < Rails::Railtie
7
+ config.stasher = ActiveSupport::OrderedOptions.new
8
+ config.stasher.enabled = false
9
+ config.stasher.suppress_app_log = true
10
+ config.stasher.redirect_logger = false
11
+ config.stasher.attach_to = [ :action_controller, :active_record]
12
+
13
+ initializer 'stasher' do |app|
14
+ Stasher.setup(app) if app.config.stasher.enabled
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Stasher
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ FactoryGirl.define do
2
+ factory :actioncontroller_payload, class: Hash do
3
+ skip_create
4
+
5
+ status 200
6
+ format 'application/json'
7
+ method 'GET'
8
+ path '/home?foo=bar'
9
+ ip "127.0.0.1"
10
+ params { {:controller => 'home', :action => 'index', 'foo' => 'bar' }.with_indifferent_access }
11
+ db_runtime 0.02
12
+ view_runtime 0.01
13
+
14
+ initialize_with { attributes }
15
+ end
16
+
17
+ factory :activerecord_sql_payload, class: Hash do
18
+ skip_create
19
+
20
+ sql "SELECT * FROM `users` WHERE `users`.`id` = 5"
21
+ name 'User Load'
22
+
23
+ initialize_with { attributes }
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stasher::CurrentScope do
4
+ describe '.clear!' do
5
+ before :each do
6
+ Stasher::CurrentScope.fields[:foo] = 'bar'
7
+ Stasher::CurrentScope.fields[:baz] = { :bat => "man" }
8
+ end
9
+
10
+ it "removes all existing fields" do
11
+ Stasher::CurrentScope.clear!
12
+
13
+ Stasher::CurrentScope.fields.should == {}
14
+ end
15
+ end
16
+
17
+ describe ".fields" do
18
+ before :each do
19
+ Stasher::CurrentScope.fields = { :foo => "bar" }
20
+ end
21
+
22
+ it "can retrive a value" do
23
+ Stasher::CurrentScope.fields[:foo].should == "bar"
24
+ end
25
+ end
26
+
27
+ describe ".fields=" do
28
+ it "can assign all fields at once" do
29
+ Stasher::CurrentScope.fields = { :foo => "bar" }
30
+
31
+ Stasher::CurrentScope.fields[:foo].should == "bar"
32
+ end
33
+
34
+ it "overwrites exisitng fields" do
35
+ Stasher::CurrentScope.fields[:baz] = { :bat => "man" }
36
+
37
+ Stasher::CurrentScope.fields = { :foo => "bar" }
38
+
39
+ Stasher::CurrentScope.fields.should == { :foo => "bar" }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,214 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stasher::LogSubscriber do
4
+ let(:logger) { MockLogger.new }
5
+
6
+ before :each do
7
+ Stasher.logger = logger
8
+ Stasher.stub(:source).and_return("source")
9
+ LogStash::Time.stub(:now => 'timestamp')
10
+ end
11
+
12
+ subject(:subscriber) { Stasher::LogSubscriber.new }
13
+
14
+ describe '#start_processing' do
15
+ let(:payload) { FactoryGirl.create(:actioncontroller_payload) }
16
+
17
+ let(:event) {
18
+ ActiveSupport::Notifications::Event.new(
19
+ 'process_action.action_controller', Time.now, Time.now, 2, payload
20
+ )
21
+ }
22
+
23
+ let(:json) {
24
+ '{"@source":"source","@tags":["request"],"@fields":{"method":"GET","ip":"127.0.0.1","params":{"foo":"bar"},' +
25
+ '"path":"/home","format":"application/json","controller":"home","action":"index"},"@timestamp":"timestamp"}' + "\n"
26
+ }
27
+
28
+ it 'calls all extractors and outputs the json' do
29
+ subscriber.should_receive(:extract_request).with(payload).and_return({:request => true})
30
+ subscriber.should_receive(:extract_current_scope).with(no_args).and_return({:custom => true})
31
+ subscriber.start_processing(event)
32
+ end
33
+
34
+ it "logs the event" do
35
+ subscriber.start_processing(event)
36
+
37
+ logger.messages.first.should == json
38
+ end
39
+ end
40
+
41
+ describe '#sql' do
42
+ let(:event) {
43
+ ActiveSupport::Notifications::Event.new(
44
+ 'sql.active_record', Time.now, Time.now, 2, payload
45
+ )
46
+ }
47
+
48
+ context "for SCHEMA events" do
49
+ let(:payload) { FactoryGirl.create(:activerecord_sql_payload, name: 'SCHEMA') }
50
+
51
+ it "does not log anything" do
52
+ subscriber.sql(event)
53
+
54
+ logger.messages.should be_empty
55
+ end
56
+ end
57
+
58
+ context "for unnamed events" do
59
+ let(:payload) { FactoryGirl.create(:activerecord_sql_payload, name: '') }
60
+
61
+ it "does not log anything" do
62
+ subscriber.sql(event)
63
+
64
+ logger.messages.should be_empty
65
+ end
66
+ end
67
+
68
+ context "for session events" do
69
+ let(:payload) { FactoryGirl.create(:activerecord_sql_payload, name: 'ActiveRecord::SessionStore') }
70
+
71
+ it "does not log anything" do
72
+ subscriber.sql(event)
73
+
74
+ logger.messages.should be_empty
75
+ end
76
+ end
77
+
78
+ context "for any other events" do
79
+ let(:payload) { FactoryGirl.create(:activerecord_sql_payload) }
80
+
81
+ let(:json) {
82
+ '{"@source":"source","@tags":["sql"],"@fields":{"name":"User Load","sql":"' +
83
+ payload[:sql] + '","duration":0.0},"@timestamp":"timestamp"}' + "\n"
84
+ }
85
+
86
+ it 'calls all extractors and outputs the json' do
87
+ subscriber.should_receive(:extract_sql).with(payload).and_return({:sql => true})
88
+ subscriber.should_receive(:extract_current_scope).with(no_args).and_return({:custom => true})
89
+ subscriber.sql(event)
90
+ end
91
+
92
+ it "logs the event" do
93
+ subscriber.sql(event)
94
+
95
+ logger.messages.first.should == json
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#process_action' do
101
+ let(:payload) { FactoryGirl.create(:actioncontroller_payload) }
102
+
103
+ let(:event) {
104
+ ActiveSupport::Notifications::Event.new(
105
+ 'process_action.action_controller', Time.now, Time.now, 2, payload
106
+ )
107
+ }
108
+
109
+ let(:json) {
110
+ '{"@source":"source","@tags":["response"],"@fields":{"method":"GET","ip":"127.0.0.1","params":{"foo":"bar"},' +
111
+ '"path":"/home","format":"application/json","controller":"home","action":"index","status":200,' +
112
+ '"duration":0.0,"view":0.01,"db":0.02},"@timestamp":"timestamp"}' + "\n"
113
+ }
114
+
115
+ it 'calls all extractors and outputs the json' do
116
+ subscriber.should_receive(:extract_request).with(payload).and_return({:request => true})
117
+ subscriber.should_receive(:extract_status).with(payload).and_return({:status => true})
118
+ subscriber.should_receive(:runtimes).with(event).and_return({:runtimes => true})
119
+ subscriber.should_receive(:extract_exception).with(payload).and_return({:exception => true})
120
+ subscriber.should_receive(:extract_current_scope).with(no_args).and_return({:custom => true})
121
+ subscriber.process_action(event)
122
+ end
123
+
124
+ it "logs the event" do
125
+ subscriber.process_action(event)
126
+
127
+ logger.messages.first.should == json
128
+ end
129
+
130
+ context "when the payload includes an exception" do
131
+ before :each do
132
+ payload[:exception] = [ 'Exception', 'message' ]
133
+ subscriber.stub(:extract_exception).and_return({})
134
+ end
135
+
136
+ it "adds the 'exception' tag" do
137
+ subscriber.process_action(event)
138
+
139
+ logger.messages.first.should match %r|"@tags":\["response","exception"\]|
140
+ end
141
+ end
142
+
143
+ it "clears the scoped parameters" do
144
+ Stasher::CurrentScope.should_receive(:clear!)
145
+
146
+ subscriber.process_action(event)
147
+ end
148
+
149
+ context "with a redirect" do
150
+ before do
151
+ Stasher::CurrentScope.fields[:location] = "http://www.example.com"
152
+ end
153
+
154
+ it "adds the location to the log line" do
155
+ subscriber.process_action(event)
156
+ logger.messages.first.should match %r|"@fields":{.*?"location":"http://www\.example\.com".*?}|
157
+ end
158
+ end
159
+ end
160
+
161
+ describe '#log_event' do
162
+ it "sets the type as a @tag" do
163
+ subscriber.send :log_event, 'tag', {}
164
+
165
+ logger.messages.first.should match %r|"@tags":\["tag"\]|
166
+ end
167
+
168
+ it "renders the data in the @fields" do
169
+ subscriber.send :log_event, 'tag', { "foo" => "bar", :baz => 'bot' }
170
+
171
+ logger.messages.first.should match %r|"@fields":{"foo":"bar","baz":"bot"}|
172
+ end
173
+
174
+ it "sets the @source" do
175
+ subscriber.send :log_event, 'tag', {}
176
+
177
+ logger.messages.first.should match %r|"@source":"source"|
178
+ end
179
+
180
+ context "with a block" do
181
+ it "calls the block with the new event" do
182
+ yielded = []
183
+ subscriber.send :log_event, 'tag', {} do |args|
184
+ yielded << args
185
+ end
186
+
187
+ yielded.size.should == 1
188
+ yielded.first.should be_a(LogStash::Event)
189
+ end
190
+
191
+ it "logs the modified event" do
192
+ subscriber.send :log_event, 'tag', {} do |event|
193
+ event.tags << "extra"
194
+ end
195
+
196
+ logger.messages.first.should match %r|"@tags":\["tag","extra"\]|
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#redirect_to' do
202
+ let(:event) {
203
+ ActiveSupport::Notifications::Event.new(
204
+ 'redirect_to.action_controller', Time.now, Time.now, 1, :location => 'http://example.com', :status => 302
205
+ )
206
+ }
207
+
208
+ it "stores the payload location in the current scope" do
209
+ subscriber.redirect_to(event)
210
+
211
+ Stasher::CurrentScope.fields[:location].should == "http://example.com"
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stasher::Logger do
4
+ subject (:logger) { Stasher::Logger.new }
5
+
6
+ before :each do
7
+ logger.level = ::Logger::WARN
8
+ end
9
+
10
+ it "logs messages that are at the configured level" do
11
+ Stasher.should_receive(:log).with('WARN', 'message')
12
+
13
+ logger.warn 'message'
14
+ end
15
+
16
+ it "logs messages that are above the configured level" do
17
+ Stasher.should_receive(:log).with('ERROR', 'message')
18
+
19
+ logger.error 'message'
20
+ end
21
+
22
+ it "does not log messages that are below the configured level" do
23
+ Stasher.should_not_receive(:log)
24
+
25
+ logger.info 'message'
26
+ end
27
+
28
+ it "formats the severity" do
29
+ Stasher.should_receive(:log).with('WARN', 'message')
30
+
31
+ logger.add ::Logger::WARN, "message"
32
+ end
33
+
34
+ it "returns true" do
35
+ Stasher.stub(:log)
36
+
37
+ logger.add( ::Logger::WARN, "message" ).should be_true
38
+ end
39
+
40
+ context "when there is a block given" do
41
+ it "yields to the block" do
42
+ Stasher.stub(:log)
43
+
44
+ expect { |b|
45
+ logger.add ::Logger::WARN, &b
46
+ }.to yield_with_no_args
47
+ end
48
+
49
+ it "logs the returned message" do
50
+ Stasher.should_receive(:log).with('WARN', 'message')
51
+
52
+ logger.add ::Logger::WARN do
53
+ "message"
54
+ end
55
+ end
56
+ end
57
+
58
+ context "when the message is a string" do
59
+ it "formats the message" do
60
+ logger.should_receive(:format_message).with(::Logger::WARN, an_instance_of(Time), nil, "message").and_return("formatted")
61
+ Stasher.stub(:log)
62
+
63
+ logger.warn 'message'
64
+ end
65
+
66
+ it "renders the formatted message" do
67
+ logger.stub(:format_message).and_return("formatted")
68
+ Stasher.should_receive(:log).with('WARN', 'formatted')
69
+
70
+ logger.warn 'message'
71
+ end
72
+ end
73
+
74
+ context "when the message is an object" do
75
+ let (:message) { Object.new }
76
+
77
+ it "does not format the message" do
78
+ logger.should_not_receive(:format_message)
79
+ Stasher.stub(:log)
80
+
81
+ logger.warn message
82
+ end
83
+
84
+ it "logs the raw message object" do
85
+ Stasher.should_receive(:log).with('WARN', message)
86
+
87
+ logger.warn message
88
+ end
89
+ end
90
+ end