logstasher 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZDVhMmFhNjI3MDBmNmI4N2RlNWEzZTAyODNiMjk2ZGRkMzNlNzA5Yw==
4
+ MmUyOWU0M2UwMGY1YzRjZDU0ZDRmNGExM2E2YTZjZDVhMGEyYWRjMQ==
5
5
  data.tar.gz: !binary |-
6
- ZWZmMTdmOTdmODFkZDkyYTUzYWQ1Mzk1MjgzODNlMTlkZDM5ZTgwNg==
6
+ NGI2M2M3NGNjYjc2OGZjMWI0NGU3MTM3ZjljMDY5ZGNhYTk5OTYxNQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDcxZmFkNGUxZjI5Mzk5NDg2ZDhjMjZlN2U0OTY4MzdhOWNmZmIyYTNmY2E1
10
- NzkxYWQ2OWYyMTMxNGMwNjdlMTViNWVjNzExNzQ4NjEyZjFjN2RlMjVmYThk
11
- MjBjYzNlZjdhN2M4ZWRkNWJmN2FmZTBkNmQ2MjM2MzlkYzQyYjM=
9
+ YjM5YzhmMGU0YTcyYTAxMWMxYzAxZWE5NTgyN2RmYTM3MWM0ZDE0MTY1YWQ2
10
+ YzIzOWQ4MjFkODA0NDMwNDk3YWY4MmNkYzY1M2ViNmU4NjZmYzdjMDZlMGU2
11
+ ZGM2MGY0ZmNjYzk3NTYwODRiMTc3ZDg1OTYzZWFiMTgzNmEyZjA=
12
12
  data.tar.gz: !binary |-
13
- MmM5Y2JjZDY5OTU0ZWMzNTc1YTljM2Y1OTM2M2JmZDIwYTYwN2Q3ZTJmYzI3
14
- Yjg5NDgwYTEzMmZiMDk2OGNhNzQyZjlmM2E3NTQ4MTM1OTY3YzA5NzJhOWNk
15
- ZDA3MzUxMzFlZGI0ZTJkZDIxOWE0NGE2NjU1ZTJjODA3OGFmNzE=
13
+ NTk2MDFmNGM4MDFlOGE2OGI1NjE3MDMwZmZmYjRlZTM0NDQwMTU1OGZiMzBl
14
+ N2VlYzViNzQ2MDFjODk4OWYyYThhZGRjNzUyMDRhNzBiZjU3YTdhM2E0ZjJj
15
+ ZTRlYTA2Y2UyM2UzN2M0YTY5MDFkY2IyNzk1NmY5MzM5MjJlOGQ=
data/README.md CHANGED
@@ -94,6 +94,15 @@ Since some fields are very specific to your application for e.g. *user_name*, so
94
94
  end
95
95
  end
96
96
 
97
+ ## Logging ActionMailer events
98
+
99
+ Logstasher can easily log messages from `ActionMailer`, such as incoming/outgoing e-mails and e-mail content generation (Rails >= 4.1).
100
+ This functionality is automatically enabled. Since the relationship between a concrete HTTP request and a mailer invocation is lost
101
+ once in an `ActionMailer` instance method, global (per-request) state is kept to correlate HTTP requests and events from other parts
102
+ of rails, such as `ActionMailer`. Every time a request is invoked, a `request_id` key is added which is present on every `ActionMailer` event.
103
+
104
+ Note: Since mailers are executed within the lifetime of a request, they will show up in logs prior to the actual request.
105
+
97
106
  ## Listening to `ActiveSupport::Notifications` events
98
107
 
99
108
  It is possible to listen to any `ActiveSupport::Notifications` events and store arbitrary data to be included in the final JSON log entry:
@@ -135,7 +144,7 @@ You can easily share the same store between different types of notifications, by
135
144
  java -jar logstash-1.3.3-flatjar.jar agent -f quickstart.conf -- web
136
145
  ```
137
146
  * Visit http://localhost:9292/ to see the Kibana interface and your parsed logs
138
- * For advanced options see the latest logstash documentation at [logstash.net](http://www.logstash.net/) or visit my blog at (shadabahmed.com)[http://shadabahmed.com/blog/2013/04/30/logstasher-for-awesome-rails-logging] (slightly outdated but will sure give you ideas for distributed setup etc.)
147
+ * For advanced options see the latest logstash documentation at [logstash.net](http://www.logstash.net/) or visit my blog at [shadabahmed.com](http://shadabahmed.com/blog/2013/04/30/logstasher-for-awesome-rails-logging) (slightly outdated but will sure give you ideas for distributed setup etc.)
139
148
 
140
149
  ## Versions
141
150
  All versions require Rails 3.0.x and higher and Ruby 1.9.2+. Tested on Rails 4 and Ruby 2.0
@@ -8,8 +8,9 @@ require 'active_support/ordered_options'
8
8
  module LogStasher
9
9
  extend self
10
10
  STORE_KEY = :logstasher_data
11
+ REQUEST_CONTEXT_KEY = :logstasher_request_context
11
12
 
12
- attr_accessor :logger, :enabled, :log_controller_parameters, :source
13
+ attr_accessor :logger, :logger_path, :enabled, :log_controller_parameters, :source
13
14
  # Setting the default to 'unknown' to define the default behaviour
14
15
  @source = 'unknown'
15
16
 
@@ -20,6 +21,8 @@ module LogStasher
20
21
  unsubscribe(:action_view, subscriber)
21
22
  when ActionController::LogSubscriber
22
23
  unsubscribe(:action_controller, subscriber)
24
+ when ActionMailer::LogSubscriber
25
+ unsubscribe(:action_mailer, subscriber)
23
26
  end
24
27
  end
25
28
  end
@@ -38,7 +41,8 @@ module LogStasher
38
41
  def add_default_fields_to_payload(payload, request)
39
42
  payload[:ip] = request.remote_ip
40
43
  payload[:route] = "#{request.params[:controller]}##{request.params[:action]}"
41
- self.custom_fields += [:ip, :route]
44
+ payload[:request_id] = request.env['action_dispatch.request_id']
45
+ self.custom_fields += [:ip, :route, :request_id]
42
46
  if self.log_controller_parameters
43
47
  payload[:parameters] = payload[:params].except(*ActionController::LogSubscriber::INTERNAL_PARAMS)
44
48
  self.custom_fields += [:parameters]
@@ -54,6 +58,23 @@ module LogStasher
54
58
  ActionController::Base.send(:define_method, :logtasher_add_custom_fields_to_payload, &wrapped_block)
55
59
  end
56
60
 
61
+ def add_custom_fields_to_request_context(&block)
62
+ wrapped_block = Proc.new do |fields|
63
+ instance_exec(fields, &block)
64
+ LogStasher.custom_fields.concat(fields.keys)
65
+ end
66
+ ActionController::Metal.send(:define_method, :logstasher_add_custom_fields_to_request_context, &wrapped_block)
67
+ ActionController::Base.send(:define_method, :logstasher_add_custom_fields_to_request_context, &wrapped_block)
68
+ end
69
+
70
+ def add_default_fields_to_request_context(request)
71
+ request_context[:request_id] = request.env['action_dispatch.request_id']
72
+ end
73
+
74
+ def clear_request_context
75
+ request_context.clear
76
+ end
77
+
57
78
  def setup(app)
58
79
  app.config.action_dispatch.rack_cache[:verbose] = false if app.config.action_dispatch.rack_cache
59
80
  # Path instrumentation class to insert our hook
@@ -61,7 +82,9 @@ module LogStasher
61
82
  require 'logstash-event'
62
83
  self.suppress_app_logs(app)
63
84
  LogStasher::RequestLogSubscriber.attach_to :action_controller
64
- self.logger = app.config.logstasher.logger || new_logger("#{Rails.root}/log/logstash_#{Rails.env}.log")
85
+ LogStasher::MailerLogSubscriber.attach_to :action_mailer
86
+ self.logger_path = app.config.logstasher.logger_path || "#{Rails.root}/log/logstash_#{Rails.env}.log"
87
+ self.logger = app.config.logstasher.logger || new_logger(self.logger_path)
65
88
  self.logger.level = app.config.logstasher.log_level || Logger::WARN
66
89
  self.source = app.config.logstasher.source unless app.config.logstasher.source.nil?
67
90
  self.enabled = true
@@ -103,6 +126,10 @@ module LogStasher
103
126
  RequestStore.store[STORE_KEY]
104
127
  end
105
128
 
129
+ def request_context
130
+ RequestStore.store[REQUEST_CONTEXT_KEY] ||= {}
131
+ end
132
+
106
133
  def watch(event, opts = {}, &block)
107
134
  event_group = opts[:event_group] || event
108
135
  ActiveSupport::Notifications.subscribe(event) do |*args|
@@ -119,6 +146,10 @@ module LogStasher
119
146
  EOM
120
147
  end
121
148
 
149
+ def enabled?
150
+ self.enabled
151
+ end
152
+
122
153
  private
123
154
 
124
155
  def new_logger(path)
@@ -93,4 +93,35 @@ module LogStasher
93
93
  custom_fields
94
94
  end
95
95
  end
96
+
97
+ class MailerLogSubscriber < ActiveSupport::LogSubscriber
98
+ MAILER_FIELDS = [:mailer, :action, :message_id, :from, :to]
99
+
100
+ def deliver(event)
101
+ process_event(event, ['mailer', 'deliver'])
102
+ end
103
+
104
+ def receive(event)
105
+ process_event(event, ['mailer', 'receive'])
106
+ end
107
+
108
+ def process(event)
109
+ process_event(event, ['mailer', 'process'])
110
+ end
111
+
112
+ private
113
+ def process_event(event, tags)
114
+ data = LogStasher.request_context.merge(extract_metadata(event.payload))
115
+ event = LogStash::Event.new('@source' => LogStasher.source, '@fields' => data, '@tags' => tags)
116
+ logger << event.to_json + "\n"
117
+ end
118
+
119
+ def extract_metadata(payload)
120
+ payload.slice(*MAILER_FIELDS)
121
+ end
122
+
123
+ def logger
124
+ LogStasher.logger
125
+ end
126
+ end
96
127
  end
@@ -12,9 +12,16 @@ module ActionController
12
12
 
13
13
  LogStasher.add_default_fields_to_payload(raw_payload, request)
14
14
 
15
+ LogStasher.clear_request_context
16
+ LogStasher.add_default_fields_to_request_context(request)
17
+
15
18
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
16
19
 
17
20
  ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
21
+ if self.respond_to?(:logstasher_add_custom_fields_to_request_context)
22
+ logstasher_add_custom_fields_to_request_context(LogStasher.request_context)
23
+ end
24
+
18
25
  result = super
19
26
 
20
27
  if self.respond_to?(:logtasher_add_custom_fields_to_payload)
@@ -30,6 +37,10 @@ module ActionController
30
37
  LogStasher.store.each do |key, value|
31
38
  payload[key] = value
32
39
  end
40
+
41
+ LogStasher.request_context.each do |key, value|
42
+ payload[key] = value
43
+ end
33
44
  result
34
45
  end
35
46
  end
@@ -1,3 +1,3 @@
1
1
  module LogStasher
2
- VERSION = "0.5.3"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_runtime_dependency "request_store"
22
22
 
23
23
  # specify any dependencies here; for example:
24
- s.add_development_dependency "rspec"
24
+ s.add_development_dependency "rspec", [">= 2.14"]
25
25
  s.add_development_dependency("bundler", [">= 1.0.0"])
26
26
  s.add_development_dependency("rails", [">= 3.0"])
27
27
  end
@@ -13,28 +13,28 @@ describe LogStasher::Device::Redis do
13
13
 
14
14
  it 'has default options' do
15
15
  device = LogStasher::Device::Redis.new
16
- device.options.should eq(default_options)
16
+ expect(device.options).to eq(default_options)
17
17
  end
18
18
 
19
19
  it 'creates a redis instance' do
20
- ::Redis.should_receive(:new).with({})
20
+ expect(::Redis).to receive(:new).with({})
21
21
  LogStasher::Device::Redis.new()
22
22
  end
23
23
 
24
24
  it 'assumes unknown options are for redis' do
25
- ::Redis.should_receive(:new).with(hash_including(db: '0'))
25
+ expect(::Redis).to receive(:new).with(hash_including(db: '0'))
26
26
  device = LogStasher::Device::Redis.new(db: '0')
27
- device.redis_options.should eq(db: '0')
27
+ expect(device.redis_options).to eq(db: '0')
28
28
  end
29
29
 
30
30
  it 'has a key' do
31
31
  device = LogStasher::Device::Redis.new(key: 'the_key')
32
- device.key.should eq 'the_key'
32
+ expect(device.key).to eq('the_key')
33
33
  end
34
34
 
35
35
  it 'has a data_type' do
36
36
  device = LogStasher::Device::Redis.new(data_type: 'channel')
37
- device.data_type.should eq 'channel'
37
+ expect(device.data_type).to eq('channel')
38
38
  end
39
39
 
40
40
  it 'does not allow unsupported data types' do
@@ -45,13 +45,13 @@ describe LogStasher::Device::Redis do
45
45
 
46
46
  it 'quits the redis connection on #close' do
47
47
  device = LogStasher::Device::Redis.new
48
- device.redis.should_receive(:quit)
48
+ expect(device.redis).to receive(:quit)
49
49
  device.close
50
50
  end
51
51
 
52
52
  it 'works as a logger device' do
53
53
  device = LogStasher::Device::Redis.new
54
- device.should_receive(:write).with('blargh')
54
+ expect(device).to receive(:write).with('blargh')
55
55
  logger = Logger.new(device)
56
56
  logger << 'blargh'
57
57
  end
@@ -59,19 +59,19 @@ describe LogStasher::Device::Redis do
59
59
  describe '#write' do
60
60
  it "rpushes logs onto a list" do
61
61
  device = LogStasher::Device::Redis.new(data_type: 'list')
62
- device.redis.should_receive(:rpush).with('logstash', 'the log')
62
+ expect(device.redis).to receive(:rpush).with('logstash', 'the log')
63
63
  device.write('the log')
64
64
  end
65
65
 
66
66
  it "rpushes logs onto a custom key" do
67
67
  device = LogStasher::Device::Redis.new(data_type: 'list', key: 'custom')
68
- device.redis.should_receive(:rpush).with('custom', 'the log')
68
+ expect(device.redis).to receive(:rpush).with('custom', 'the log')
69
69
  device.write('the log')
70
70
  end
71
71
 
72
72
  it "publishes logs onto a channel" do
73
73
  device = LogStasher::Device::Redis.new(data_type: 'channel', key: 'custom')
74
- device.redis.should_receive(:publish).with('custom', 'the log')
74
+ expect(device.redis).to receive(:publish).with('custom', 'the log')
75
75
  device.write('the log')
76
76
  end
77
77
  end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'logstasher/rails_ext/action_controller/metal/instrumentation'
3
+
4
+ describe ActionController::Base do
5
+ before :each do
6
+ subject.request = ActionDispatch::TestRequest.new
7
+ subject.response = ActionDispatch::TestResponse.new
8
+
9
+ def subject.index(*args)
10
+ render text: 'OK'
11
+ end
12
+ end
13
+
14
+ describe ".process_action" do
15
+ it "adds default fields to payload" do
16
+ expect(LogStasher).to receive(:add_default_fields_to_payload).once
17
+ expect(LogStasher).to receive(:add_default_fields_to_request_context).once
18
+ subject.process_action(:index)
19
+ end
20
+
21
+ it "creates the request context before processing" do
22
+ LogStasher.request_context[:some_key] = 'value'
23
+ expect(LogStasher).to receive(:clear_request_context).once.and_call_original
24
+ expect {
25
+ subject.process_action(:index)
26
+ }.to change { LogStasher.request_context }
27
+ end
28
+
29
+ it "notifies rails of a request coming in" do
30
+ expect(ActiveSupport::Notifications).to receive(:instrument).with("start_processing.action_controller", anything).once
31
+ expect(ActiveSupport::Notifications).to receive(:instrument).with("process_action.action_controller", anything).once
32
+ subject.process_action(:index)
33
+ end
34
+
35
+ context "request context has custom fields defined" do
36
+ before :each do
37
+ LogStasher.add_custom_fields_to_request_context do |fields|
38
+ fields[:some_field] = 'value'
39
+ end
40
+
41
+ ActiveSupport::Notifications.subscribe('process_action.action_controller') do |_, _, _, _, payload|
42
+ @payload = payload
43
+ end
44
+ end
45
+
46
+ it "should retain the value in the request context" do
47
+ subject.process_action(:index)
48
+ end
49
+
50
+ after :each do
51
+ expect(@payload[:some_field]).to eq('value')
52
+
53
+ ActionController::Metal.class_eval do
54
+ undef logstasher_add_custom_fields_to_request_context
55
+ end
56
+ ActionController::Base.class_eval do
57
+ undef logstasher_add_custom_fields_to_request_context
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -45,17 +45,17 @@ describe LogStasher::RequestLogSubscriber do
45
45
  let(:logger) { double }
46
46
  let(:json) { "{\"@source\":\"unknown\",\"@tags\":[\"request\"],\"@fields\":{\"request\":true,\"status\":true,\"runtimes\":true,\"location\":true,\"exception\":true,\"custom\":true},\"@timestamp\":\"timestamp\"}\n" }
47
47
  before do
48
- LogStasher.stub(:logger => logger)
49
- LogStash::Time.stub(:now => 'timestamp')
48
+ allow(LogStasher).to receive(:logger).and_return(logger)
49
+ allow(LogStash::Time).to receive(:now).and_return('timestamp')
50
50
  end
51
51
  it 'calls all extractors and outputs the json' do
52
- request_subscriber.should_receive(:extract_request).with(payload).and_return({:request => true})
53
- request_subscriber.should_receive(:extract_status).with(payload).and_return({:status => true})
54
- request_subscriber.should_receive(:runtimes).with(event).and_return({:runtimes => true})
55
- request_subscriber.should_receive(:location).with(event).and_return({:location => true})
56
- request_subscriber.should_receive(:extract_exception).with(payload).and_return({:exception => true})
57
- request_subscriber.should_receive(:extract_custom_fields).with(payload).and_return({:custom => true})
58
- LogStasher.logger.should_receive(:<<).with(json)
52
+ expect(request_subscriber).to receive(:extract_request).with(payload).and_return({:request => true})
53
+ expect(request_subscriber).to receive(:extract_status).with(payload).and_return({:status => true})
54
+ expect(request_subscriber).to receive(:runtimes).with(event).and_return({:runtimes => true})
55
+ expect(request_subscriber).to receive(:location).with(event).and_return({:location => true})
56
+ expect(request_subscriber).to receive(:extract_exception).with(payload).and_return({:exception => true})
57
+ expect(request_subscriber).to receive(:extract_custom_fields).with(payload).and_return({:custom => true})
58
+ expect(LogStasher.logger).to receive(:<<).with(json)
59
59
  request_subscriber.process_action(event)
60
60
  end
61
61
  end
@@ -64,47 +64,47 @@ describe LogStasher::RequestLogSubscriber do
64
64
 
65
65
  it "should contain request tag" do
66
66
  subscriber.process_action(event)
67
- log_output.json['@tags'].should include 'request'
67
+ expect(log_output.json['@tags']).to include 'request'
68
68
  end
69
69
 
70
70
  it "should contain HTTP method" do
71
71
  subscriber.process_action(event)
72
- log_output.json['@fields']['method'].should == 'GET'
72
+ expect(log_output.json['@fields']['method']).to eq 'GET'
73
73
  end
74
74
 
75
75
  it "should include the path in the log output" do
76
76
  subscriber.process_action(event)
77
- log_output.json['@fields']['path'].should == '/home'
77
+ expect(log_output.json['@fields']['path']).to eq '/home'
78
78
  end
79
79
 
80
80
  it "should include the format in the log output" do
81
81
  subscriber.process_action(event)
82
- log_output.json['@fields']['format'].should == 'application/json'
82
+ expect(log_output.json['@fields']['format']).to eq 'application/json'
83
83
  end
84
84
 
85
85
  it "should include the status code" do
86
86
  subscriber.process_action(event)
87
- log_output.json['@fields']['status'].should == 200
87
+ expect(log_output.json['@fields']['status']).to eq 200
88
88
  end
89
89
 
90
90
  it "should include the controller" do
91
91
  subscriber.process_action(event)
92
- log_output.json['@fields']['controller'].should == 'home'
92
+ expect(log_output.json['@fields']['controller']).to eq 'home'
93
93
  end
94
94
 
95
95
  it "should include the action" do
96
96
  subscriber.process_action(event)
97
- log_output.json['@fields']['action'].should == 'index'
97
+ expect(log_output.json['@fields']['action']).to eq 'index'
98
98
  end
99
99
 
100
100
  it "should include the view rendering time" do
101
101
  subscriber.process_action(event)
102
- log_output.json['@fields']['view'].should == 0.01
102
+ expect(log_output.json['@fields']['view']).to eq 0.01
103
103
  end
104
104
 
105
105
  it "should include the database rendering time" do
106
106
  subscriber.process_action(event)
107
- log_output.json['@fields']['db'].should == 0.02
107
+ expect(log_output.json['@fields']['db']).to eq 0.02
108
108
  end
109
109
 
110
110
  it "should add a valid status when an exception occurred" do
@@ -115,10 +115,10 @@ describe LogStasher::RequestLogSubscriber do
115
115
  event.payload[:status] = nil
116
116
  event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found']
117
117
  subscriber.process_action(event)
118
- log_output.json['@fields']['status'].should >= 400
119
- log_output.json['@fields']['error'].should =~ /AbstractController::ActionNotFound.*Route not found.*logstasher\/spec\/lib\/logstasher\/log_subscriber_spec\.rb/m
120
- log_output.json['@tags'].should include 'request'
121
- log_output.json['@tags'].should include 'exception'
118
+ expect(log_output.json['@fields']['status']).to be >= 400
119
+ expect(log_output.json['@fields']['error']).to be =~ /AbstractController::ActionNotFound.*Route not found.*logstasher\/spec\/lib\/logstasher\/log_subscriber_spec\.rb/m
120
+ expect(log_output.json['@tags']).to include 'request'
121
+ expect(log_output.json['@tags']).to include 'exception'
122
122
  end
123
123
  end
124
124
 
@@ -126,7 +126,7 @@ describe LogStasher::RequestLogSubscriber do
126
126
  event.payload[:status] = nil
127
127
  event.payload[:exception] = nil
128
128
  subscriber.process_action(event)
129
- log_output.json['@fields']['status'].should == 0
129
+ expect(log_output.json['@fields']['status']).to eq 0
130
130
  end
131
131
 
132
132
  describe "with a redirect" do
@@ -136,36 +136,36 @@ describe LogStasher::RequestLogSubscriber do
136
136
 
137
137
  it "should add the location to the log line" do
138
138
  subscriber.process_action(event)
139
- log_output.json['@fields']['location'].should == 'http://www.example.com'
139
+ expect(log_output.json['@fields']['location']).to eq 'http://www.example.com'
140
140
  end
141
141
 
142
142
  it "should remove the thread local variable" do
143
143
  subscriber.process_action(event)
144
- Thread.current[:logstasher_location].should == nil
144
+ expect(Thread.current[:logstasher_location]).to be_nil
145
145
  end
146
146
  end
147
147
 
148
148
  it "should not include a location by default" do
149
149
  subscriber.process_action(event)
150
- log_output.json['@fields']['location'].should be_nil
150
+ expect(log_output.json['@fields']['location']).to be_nil
151
151
  end
152
152
  end
153
153
 
154
154
  describe "with append_custom_params block specified" do
155
- let(:request) { double(:remote_ip => '10.0.0.1')}
155
+ let(:request) { double(:remote_ip => '10.0.0.1', :env => {})}
156
156
  it "should add default custom data to the output" do
157
- request.stub(:params => event.payload[:params])
157
+ allow(request).to receive_messages(:params => event.payload[:params])
158
158
  LogStasher.add_default_fields_to_payload(event.payload, request)
159
159
  subscriber.process_action(event)
160
- log_output.json['@fields']['ip'].should == '10.0.0.1'
161
- log_output.json['@fields']['route'].should == 'home#index'
162
- log_output.json['@fields']['parameters'].should == {'foo' => 'bar'}
160
+ expect(log_output.json['@fields']['ip']).to eq '10.0.0.1'
161
+ expect(log_output.json['@fields']['route']).to eq'home#index'
162
+ expect(log_output.json['@fields']['parameters']).to eq 'foo' => 'bar'
163
163
  end
164
164
  end
165
165
 
166
166
  describe "with append_custom_params block specified" do
167
167
  before do
168
- LogStasher.stub(:add_custom_fields) do |&block|
168
+ allow(LogStasher).to receive(:add_custom_fields) do |&block|
169
169
  @block = block
170
170
  end
171
171
  LogStasher.add_custom_fields do |payload|
@@ -177,14 +177,89 @@ describe LogStasher::RequestLogSubscriber do
177
177
  it "should add the custom data to the output" do
178
178
  @block.call(event.payload)
179
179
  subscriber.process_action(event)
180
- log_output.json['@fields']['user'].should == 'user'
180
+ expect(log_output.json['@fields']['user']).to eq 'user'
181
181
  end
182
182
  end
183
183
 
184
184
  describe "when processing a redirect" do
185
185
  it "should store the location in a thread local variable" do
186
186
  subscriber.redirect_to(redirect)
187
- Thread.current[:logstasher_location].should == "http://example.com"
187
+ expect(Thread.current[:logstasher_location]).to eq "http://example.com"
188
+ end
189
+ end
190
+ end
191
+
192
+ describe LogStasher::MailerLogSubscriber do
193
+ let(:log_output) {StringIO.new}
194
+ let(:logger) {
195
+ logger = Logger.new(log_output)
196
+ logger.formatter = ->(_, _, _, msg) {
197
+ msg
198
+ }
199
+ def log_output.json
200
+ JSON.parse!(self.string.split("\n").last)
201
+ end
202
+ logger
203
+ }
204
+
205
+ before :all do
206
+ SampleMailer.delivery_method = :test
207
+ LogStasher::MailerLogSubscriber.attach_to(:action_mailer)
208
+ end
209
+
210
+ before do
211
+ LogStasher.logger = logger
212
+ expect(LogStasher.request_context).to receive(:merge).at_most(2).times.and_call_original
213
+ end
214
+
215
+ let :message do
216
+ Mail.new do
217
+ from 'some-dude@example.com'
218
+ to 'some-other-dude@example.com'
219
+ subject 'Goodbye'
220
+ body 'LOL'
221
+ end
222
+ end
223
+
224
+ it 'receive an e-mail' do
225
+ SampleMailer.receive(message.encoded)
226
+ log_output.json.tap do |json|
227
+ expect(json['@source']).to eq(LogStasher.source)
228
+ expect(json['@tags']).to eq(['mailer', 'receive'])
229
+ json['@fields'].tap do |fields|
230
+ expect(fields['mailer']).to eq('SampleMailer')
231
+ expect(fields['from']).to eq(['some-dude@example.com'])
232
+ expect(fields['to']).to eq(['some-other-dude@example.com'])
233
+ expect(fields['message_id']).to eq(message.message_id)
234
+ end
235
+ end
236
+ end
237
+
238
+ it 'deliver an outgoing e-mail' do
239
+ email = SampleMailer.welcome
240
+
241
+ if version = ENV['RAILS_VERSION'] and version >= '4.1'
242
+ log_output.json.tap do |json|
243
+ expect(json['@source']).to eq(LogStasher.source)
244
+ expect(json['@tags']).to eq(['mailer', 'process'])
245
+ json['@fields'].tap do |fields|
246
+ expect(fields['mailer']).to eq('SampleMailer')
247
+ expect(fields['action']).to eq('welcome')
248
+ end
249
+ end
250
+ end
251
+
252
+ email.deliver
253
+ log_output.json.tap do |json|
254
+ expect(json['@source']).to eq(LogStasher.source)
255
+ expect(json['@tags']).to eq(['mailer', 'deliver'])
256
+ json['@fields'].tap do |fields|
257
+ expect(fields['mailer']).to eq('SampleMailer')
258
+ expect(fields['from']).to eq(['some-dude@example.com'])
259
+ expect(fields['to']).to eq(['some-other-dude@example.com'])
260
+ # Message-Id appears not to be yet available at this point in time.
261
+ expect(fields['message_id']).to be_nil
262
+ end
188
263
  end
189
264
  end
190
265
  end
@@ -5,6 +5,7 @@ describe LogStasher do
5
5
  after do
6
6
  ActionController::LogSubscriber.attach_to :action_controller
7
7
  ActionView::LogSubscriber.attach_to :action_view
8
+ ActionMailer::LogSubscriber.attach_to :action_mailer
8
9
  end
9
10
 
10
11
  it "should remove subscribers for controller events" do
@@ -23,19 +24,27 @@ describe LogStasher do
23
24
  }
24
25
  end
25
26
 
27
+ it "should remove subscribsers for mailer events" do
28
+ expect {
29
+ LogStasher.remove_existing_log_subscriptions
30
+ }.to change {
31
+ ActiveSupport::Notifications.notifier.listeners_for('deliver.action_mailer')
32
+ }
33
+ end
34
+
26
35
  it "shouldn't remove subscribers that aren't from Rails" do
27
36
  blk = -> {}
28
37
  ActiveSupport::Notifications.subscribe("process_action.action_controller", &blk)
29
38
  LogStasher.remove_existing_log_subscriptions
30
39
  listeners = ActiveSupport::Notifications.notifier.listeners_for('process_action.action_controller')
31
- listeners.size.should > 0
40
+ expect(listeners).to_not be_empty
32
41
  end
33
42
  end
34
43
 
35
44
  describe '.appened_default_info_to_payload' do
36
45
  let(:params) { {'a' => '1', 'b' => 2, 'action' => 'action', 'controller' => 'test'}.with_indifferent_access }
37
46
  let(:payload) { {:params => params} }
38
- let(:request) { double(:params => params, :remote_ip => '10.0.0.1')}
47
+ let(:request) { double(:params => params, :remote_ip => '10.0.0.1', :env => {})}
39
48
  after do
40
49
  LogStasher.custom_fields = []
41
50
  LogStasher.log_controller_parameters = false
@@ -44,53 +53,75 @@ describe LogStasher do
44
53
  LogStasher.log_controller_parameters = true
45
54
  LogStasher.custom_fields = []
46
55
  LogStasher.add_default_fields_to_payload(payload, request)
47
- payload[:ip].should == '10.0.0.1'
48
- payload[:route].should == 'test#action'
49
- payload[:parameters].should == {'a' => '1', 'b' => 2}
50
- LogStasher.custom_fields.should == [:ip, :route, :parameters]
56
+ expect(payload[:ip]).to eq '10.0.0.1'
57
+ expect(payload[:route]).to eq 'test#action'
58
+ expect(payload[:parameters]).to eq 'a' => '1', 'b' => 2
59
+ expect(LogStasher.custom_fields).to eq [:ip, :route, :request_id, :parameters]
51
60
  end
52
61
 
53
62
  it 'does not include parameters when not configured to' do
54
63
  LogStasher.custom_fields = []
55
64
  LogStasher.add_default_fields_to_payload(payload, request)
56
- payload.should_not have_key(:parameters)
57
- LogStasher.custom_fields.should == [:ip, :route]
65
+ expect(payload).to_not have_key(:parameters)
66
+ expect(LogStasher.custom_fields).to eq [:ip, :route, :request_id]
58
67
  end
59
68
  end
60
69
 
61
70
  describe '.append_custom_params' do
62
- let(:block) { ->{} }
71
+ let(:block) { ->(_, _){} }
63
72
  it 'defines a method in ActionController::Base' do
64
- ActionController::Base.should_receive(:send).with(:define_method, :logtasher_add_custom_fields_to_payload, &block)
73
+ expect(ActionController::Base).to receive(:send).with(:define_method, :logtasher_add_custom_fields_to_payload, &block)
65
74
  LogStasher.add_custom_fields(&block)
66
75
  end
67
76
  end
68
77
 
78
+ describe '.add_custom_fields_to_request_context' do
79
+ let(:block) { ->(_, _){} }
80
+ it 'defines a method in ActionController::Base' do
81
+ expect(ActionController::Base).to receive(:send).with(:define_method, :logstasher_add_custom_fields_to_request_context, &block)
82
+ expect(ActionController::Metal).to receive(:send).with(:define_method, :logstasher_add_custom_fields_to_request_context, &block)
83
+ LogStasher.add_custom_fields_to_request_context(&block)
84
+ end
85
+ end
86
+
87
+ describe '.add_default_fields_to_request_context' do
88
+ it 'adds a request_id to the request context' do
89
+ LogStasher.clear_request_context
90
+ LogStasher.add_default_fields_to_request_context(double(env: {'action_dispatch.request_id' => 'lol'}))
91
+ expect(LogStasher.request_context).to eq({ request_id: 'lol' })
92
+ end
93
+ end
94
+
69
95
  shared_examples 'setup' do
70
- let(:logger) { double }
71
- let(:logstasher_config) { double(:logger => logger, :log_level => 'warn', :log_controller_parameters => nil, :source => logstasher_source) }
96
+ let(:logstasher_source) { nil }
97
+ let(:logstasher_config) { double(:logger => logger, :log_level => 'warn', :log_controller_parameters => nil, :source => logstasher_source, :logger_path => logger_path) }
72
98
  let(:config) { double(:logstasher => logstasher_config) }
73
99
  let(:app) { double(:config => config) }
74
100
  before do
75
101
  @previous_source = LogStasher.source
76
- config.stub(:action_dispatch => double(:rack_cache => false))
102
+ allow(config).to receive_messages(:action_dispatch => double(:rack_cache => false))
103
+ allow_message_expectations_on_nil
77
104
  end
78
105
  after { LogStasher.source = @previous_source } # Need to restore old source for specs
79
106
  it 'defines a method in ActionController::Base' do
80
- LogStasher.should_receive(:require).with('logstasher/rails_ext/action_controller/metal/instrumentation')
81
- LogStasher.should_receive(:require).with('logstash-event')
82
- LogStasher.should_receive(:suppress_app_logs).with(app)
83
- LogStasher::RequestLogSubscriber.should_receive(:attach_to).with(:action_controller)
84
- logger.should_receive(:level=).with('warn')
107
+ expect(LogStasher).to receive(:require).with('logstasher/rails_ext/action_controller/metal/instrumentation')
108
+ expect(LogStasher).to receive(:require).with('logstash-event')
109
+ expect(LogStasher).to receive(:suppress_app_logs).with(app)
110
+ expect(LogStasher::RequestLogSubscriber).to receive(:attach_to).with(:action_controller)
111
+ expect(LogStasher::MailerLogSubscriber).to receive(:attach_to).with(:action_mailer)
112
+ expect(logger).to receive(:level=).with('warn')
85
113
  LogStasher.setup(app)
86
- LogStasher.source.should == (logstasher_source || 'unknown')
87
- LogStasher.enabled.should be_true
88
- LogStasher.custom_fields.should == []
89
- LogStasher.log_controller_parameters.should == false
114
+ expect(LogStasher.source).to eq (logstasher_source || 'unknown')
115
+ expect(LogStasher).to be_enabled
116
+ expect(LogStasher.custom_fields).to be_empty
117
+ expect(LogStasher.log_controller_parameters).to eq false
90
118
  end
91
119
  end
92
120
 
93
121
  describe '.setup' do
122
+ let(:logger) { double }
123
+ let(:logger_path) { nil }
124
+
94
125
  describe 'with source set' do
95
126
  let(:logstasher_source) { 'foo' }
96
127
  it_behaves_like 'setup'
@@ -100,21 +131,37 @@ describe LogStasher do
100
131
  let(:logstasher_source) { nil }
101
132
  it_behaves_like 'setup'
102
133
  end
134
+
135
+ describe 'with customized logging' do
136
+ let(:logger) { nil }
137
+
138
+ context 'with no logger passed in' do
139
+ before { expect(LogStasher).to receive(:new_logger).with('/log/logstash_test.log') }
140
+ it_behaves_like 'setup'
141
+ end
142
+
143
+ context 'with custom logger path passed in' do
144
+ let(:logger_path) { double }
145
+
146
+ before { expect(LogStasher).to receive(:new_logger).with(logger_path) }
147
+ it_behaves_like 'setup'
148
+ end
149
+ end
103
150
  end
104
151
 
105
152
  describe '.suppress_app_logs' do
106
153
  let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => true))}
107
154
  let(:app){ double(:config => logstasher_config)}
108
155
  it 'removes existing subscription if enabled' do
109
- LogStasher.should_receive(:require).with('logstasher/rails_ext/rack/logger')
110
- LogStasher.should_receive(:remove_existing_log_subscriptions)
156
+ expect(LogStasher).to receive(:require).with('logstasher/rails_ext/rack/logger')
157
+ expect(LogStasher).to receive(:remove_existing_log_subscriptions)
111
158
  LogStasher.suppress_app_logs(app)
112
159
  end
113
160
 
114
161
  context 'when disabled' do
115
162
  let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => false)) }
116
163
  it 'does not remove existing subscription' do
117
- LogStasher.should_not_receive(:remove_existing_log_subscriptions)
164
+ expect(LogStasher).to_not receive(:remove_existing_log_subscriptions)
118
165
  LogStasher.suppress_app_logs(app)
119
166
  end
120
167
 
@@ -122,7 +169,7 @@ describe LogStasher do
122
169
  context 'with spelling "supress_app_log"' do
123
170
  let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => nil, :supress_app_log => false)) }
124
171
  it 'does not remove existing subscription' do
125
- LogStasher.should_not_receive(:remove_existing_log_subscriptions)
172
+ expect(LogStasher).to_not receive(:remove_existing_log_subscriptions)
126
173
  LogStasher.suppress_app_logs(app)
127
174
  end
128
175
  end
@@ -133,14 +180,14 @@ describe LogStasher do
133
180
  describe '.appended_params' do
134
181
  it 'returns the stored var in current thread' do
135
182
  Thread.current[:logstasher_custom_fields] = :test
136
- LogStasher.custom_fields.should == :test
183
+ expect(LogStasher.custom_fields).to eq :test
137
184
  end
138
185
  end
139
186
 
140
187
  describe '.appended_params=' do
141
188
  it 'returns the stored var in current thread' do
142
189
  LogStasher.custom_fields = :test
143
- Thread.current[:logstasher_custom_fields].should == :test
190
+ expect(Thread.current[:logstasher_custom_fields]).to eq :test
144
191
  end
145
192
  end
146
193
 
@@ -148,11 +195,12 @@ describe LogStasher do
148
195
  let(:logger) { double() }
149
196
  before do
150
197
  LogStasher.logger = logger
151
- LogStash::Time.stub(:now => 'timestamp')
198
+ allow(LogStash::Time).to receive_messages(:now => 'timestamp')
199
+ allow_message_expectations_on_nil
152
200
  end
153
201
  it 'adds to log with specified level' do
154
- logger.should_receive(:send).with('warn?').and_return(true)
155
- logger.should_receive(:send).with('warn',"{\"@source\":\"unknown\",\"@tags\":[\"log\"],\"@fields\":{\"message\":\"WARNING\",\"level\":\"warn\"},\"@timestamp\":\"timestamp\"}")
202
+ expect(logger).to receive(:send).with('warn?').and_return(true)
203
+ expect(logger).to receive(:send).with('warn',"{\"@source\":\"unknown\",\"@tags\":[\"log\"],\"@fields\":{\"message\":\"WARNING\",\"level\":\"warn\"},\"@timestamp\":\"timestamp\"}")
156
204
  LogStasher.log('warn', 'WARNING')
157
205
  end
158
206
  context 'with a source specified' do
@@ -160,8 +208,8 @@ describe LogStasher do
160
208
  LogStasher.source = 'foo'
161
209
  end
162
210
  it 'sets the correct source' do
163
- logger.should_receive(:send).with('warn?').and_return(true)
164
- logger.should_receive(:send).with('warn',"{\"@source\":\"foo\",\"@tags\":[\"log\"],\"@fields\":{\"message\":\"WARNING\",\"level\":\"warn\"},\"@timestamp\":\"timestamp\"}")
211
+ expect(logger).to receive(:send).with('warn?').and_return(true)
212
+ expect(logger).to receive(:send).with('warn',"{\"@source\":\"foo\",\"@tags\":[\"log\"],\"@fields\":{\"message\":\"WARNING\",\"level\":\"warn\"},\"@timestamp\":\"timestamp\"}")
165
213
  LogStasher.log('warn', 'WARNING')
166
214
  end
167
215
  end
@@ -171,7 +219,7 @@ describe LogStasher do
171
219
  describe ".#{severity}" do
172
220
  let(:message) { "This is a #{severity} message" }
173
221
  it 'should log with specified level' do
174
- LogStasher.should_receive(:log).with(severity.to_sym, message)
222
+ expect(LogStasher).to receive(:log).with(severity.to_sym, message)
175
223
  LogStasher.send(severity, message )
176
224
  end
177
225
  end
@@ -192,7 +240,7 @@ describe LogStasher do
192
240
  before(:each) { LogStasher.custom_fields = [] }
193
241
 
194
242
  it "subscribes to the required event" do
195
- ActiveSupport::Notifications.should_receive(:subscribe).with('event_name')
243
+ expect(ActiveSupport::Notifications).to receive(:subscribe).with('event_name')
196
244
  LogStasher.watch('event_name')
197
245
  end
198
246
 
@@ -208,7 +256,7 @@ describe LogStasher do
208
256
  probe = lambda { |*args, store| store[:foo] = :bar }
209
257
  LogStasher.watch('custom.event.bar', &probe)
210
258
  ActiveSupport::Notifications.instrument('custom.event.bar', {})
211
- LogStasher.store['custom.event.bar'].should == {:foo => :bar}
259
+ expect(LogStasher.store['custom.event.bar']).to eq :foo => :bar
212
260
  end
213
261
  end
214
262
  end
@@ -23,6 +23,8 @@ require 'bundler/setup'
23
23
  # part of Rails. You can say :require => false in gemfile to always use explicit requiring
24
24
  Bundler.require(:default, :test)
25
25
 
26
+ Dir[File.join("./spec/support/**/*.rb")].each { |f| require f }
27
+
26
28
  # Set Rails environment as test
27
29
  ENV['RAILS_ENV'] = 'test'
28
30
 
@@ -40,11 +42,11 @@ require 'active_support/core_ext/hash/slice'
40
42
  require 'active_support/core_ext/string'
41
43
  require 'active_support/core_ext/time/zones'
42
44
  require 'abstract_controller/base'
45
+ require 'action_mailer'
43
46
  require 'logger'
44
47
  require 'logstash-event'
45
48
 
46
49
  RSpec.configure do |config|
47
- config.treat_symbols_as_metadata_keys_with_true_values = true
48
50
  config.run_all_when_everything_filtered = true
49
51
  config.filter_run :focus
50
52
  end
@@ -0,0 +1,16 @@
1
+ require 'action_mailer'
2
+
3
+ class SampleMailer < ActionMailer::Base
4
+ def welcome
5
+ mail(from: 'some-dude@example.com', to: 'some-other-dude@example.com', subject: 'Hello, there') do |format|
6
+ format.text { render plain: 'OK' }
7
+ end
8
+ end
9
+
10
+ def receive(email)
11
+ end
12
+
13
+ def _render_template(_)
14
+ ""
15
+ end
16
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstasher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shadab Ahmed
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-06 00:00:00.000000000 Z
11
+ date: 2014-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logstash-event
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ! '>='
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.14'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ! '>='
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.14'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -104,9 +104,11 @@ files:
104
104
  - logstasher.gemspec
105
105
  - sample_logstash_configurations/quickstart.conf
106
106
  - spec/lib/logstasher/device/redis_spec.rb
107
+ - spec/lib/logstasher/instrumentation_spec.rb
107
108
  - spec/lib/logstasher/log_subscriber_spec.rb
108
109
  - spec/lib/logstasher_spec.rb
109
110
  - spec/spec_helper.rb
111
+ - spec/support/sample_mailer.rb
110
112
  homepage: https://github.com/shadabahmed/logstasher
111
113
  licenses: []
112
114
  metadata: {}
@@ -126,12 +128,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
128
  version: '0'
127
129
  requirements: []
128
130
  rubyforge_project: logstasher
129
- rubygems_version: 2.1.4
131
+ rubygems_version: 2.2.2
130
132
  signing_key:
131
133
  specification_version: 4
132
134
  summary: Awesome rails logs
133
135
  test_files:
134
136
  - spec/lib/logstasher/device/redis_spec.rb
137
+ - spec/lib/logstasher/instrumentation_spec.rb
135
138
  - spec/lib/logstasher/log_subscriber_spec.rb
136
139
  - spec/lib/logstasher_spec.rb
137
140
  - spec/spec_helper.rb
141
+ - spec/support/sample_mailer.rb