stasher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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