logstasher 0.5.3 → 0.6.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 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