paul_bunyan 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 39c2d1964ce640e53681be266ec3fd065ec310d5
4
- data.tar.gz: d3b1aad302c4b7ee5f2025f5e718595202985199
3
+ metadata.gz: 0b7d9fd1c7ac8e2b72e82b72171ea627c9597196
4
+ data.tar.gz: 270c98aa6aa254836aa244db96847b432204df92
5
5
  SHA512:
6
- metadata.gz: 2e408a9c88d81bf995d0982114728b8d261ad4bad9b1ac0151ebfa80d3ccf90bf85f732f468a928480ee7930e11e3517825b15af06315b6237e44a3856a48aae
7
- data.tar.gz: 1fe0abb219320d2fb0e05f0e04da4c1ac307e7e09ef9f3a5d7e72b1147acd889e21457cb05531e0664518a6f517a3f2b86c791e46b5616917258696f71504995
6
+ metadata.gz: 7cd3fb82d143d9b6fba0d2f4949ad5a15b52459a6b85fa2ff273a3d13f17754d3bf212a6c133a554a222691e2a6adc90a16a62dc5cfa325513a4e8099e869488
7
+ data.tar.gz: 19f8c7c07ac6027f48302882a814b7426dcd3dad228bf4be858075aeccb32d94db72feaddfcdb2cd3019991f775d8969447c1ceb57a6cb0f7485422577bae276
data/.dockerignore CHANGED
@@ -1 +1,2 @@
1
1
  Gemfile.lock
2
+ spec/gemfiles/*.lock
data/.gitignore CHANGED
@@ -23,3 +23,4 @@ spec/dummy/log/*.log
23
23
  spec/dummy/tmp/
24
24
  spec/dummy/.sass-cache
25
25
  spec/gemfiles/*.lock
26
+ docker-compose.override.yml
data/.travis.yml CHANGED
@@ -1,21 +1,17 @@
1
1
  language: ruby
2
- sudo: false
3
- cache: bundler
4
- bundler_args: --path ../../vendor/bundle
5
2
  rvm:
6
- # - ruby-head
7
- - 2.1
8
3
  - 2.2
9
- - 2.3.0
4
+ - 2.3
5
+ - 2.4
10
6
  matrix:
11
7
  fast_finish: true
12
8
  # WWTD doesn't support allow_failures... yet
13
9
  # allow_failures:
14
10
  # - rvm: ruby-head
15
11
  gemfile:
16
- - spec/gemfiles/40.gemfile
17
- - spec/gemfiles/41.gemfile
18
12
  - spec/gemfiles/42.gemfile
13
+ - spec/gemfiles/50.gemfile
14
+ - spec/gemfiles/51.gemfile
19
15
  script: bundle exec rspec
20
16
  notifications:
21
17
  email:
data/Dockerfile CHANGED
@@ -1,16 +1,19 @@
1
1
  FROM instructure/rvm
2
2
  MAINTAINER Instructure
3
3
 
4
- COPY Gemfile* *.gemspec /usr/src/app/
4
+ WORKDIR /usr/src/app
5
+ RUN /bin/bash -l -c "rvm use --default 2.4"
6
+
7
+ COPY paul_bunyan.gemspec Gemfile /usr/src/app/
5
8
  COPY lib/paul_bunyan/version.rb /usr/src/app/lib/paul_bunyan/
6
9
 
7
10
  USER root
8
11
  RUN chown -R docker:docker /usr/src/app
9
12
  USER docker
10
- RUN /bin/bash -l -c "cd /usr/src/app && bundle install"
13
+ RUN /bin/bash -l -c "bundle install"
11
14
 
12
15
  COPY . /usr/src/app
13
16
  USER root
14
17
  RUN chown -R docker:docker /usr/src/app/*
15
18
  USER docker
16
- CMD /bin/bash -l -c "cd /usr/src/app && bundle exec wwtd --parallel"
19
+ CMD /bin/bash -l -c "wwtd --parallel"
data/README.md CHANGED
@@ -19,8 +19,8 @@ end
19
19
 
20
20
  Also included is a Railtie that overrides the default rails logger to always
21
21
  print to STDOUT as well as format the messages to JSON for machine readable
22
- goodness. This has been tested with Rails 4.2 but should compatible with
23
- Rails 3 or newer since that's when Railties were introduced.
22
+ goodness. This has been tested with Rails 4.2 through 5.1, older versions of
23
+ Rails may work but are not guaranteed to and will not receive support.
24
24
 
25
25
  ## Installation
26
26
 
@@ -53,3 +53,12 @@ logger.warn "blah"
53
53
  ### Rails projects:
54
54
 
55
55
  Nothing after it's added to your Gemfile, the Railtie takes care of the rest.
56
+
57
+ ### Adding metadata to JSON logs
58
+ The default logger includes the ability to accept arbitrary metadata, the
59
+ primary use case for this functionality is to add context to log lines generated
60
+ in the course of processing a Rails request. There is an example for adding
61
+ the request host to the metadata in the examples directory. There are a few
62
+ keys that are used internally that will be overwritten when added to user
63
+ supplied metadata, this list can be found in the `#call` method of
64
+ `PaulBunyan::JSONFormatter`.
data/build.sh CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  set -e
4
4
 
5
- docker-compose build --pull
5
+ docker-compose build --pull test
6
6
  docker-compose run test
@@ -0,0 +1,10 @@
1
+ version: '2.0'
2
+ services:
3
+ test:
4
+ build: .
5
+ volumes:
6
+ - "./:/usr/src/app"
7
+ - "gems:/home/docker/.rvm/gems/"
8
+
9
+ volumes:
10
+ gems: {}
data/docker-compose.yml CHANGED
@@ -1,4 +1,9 @@
1
- test:
2
- build: .
3
- volumes:
4
- - "coverage:/app/coverage"
1
+ version: '2.0'
2
+ services:
3
+ test:
4
+ build: .
5
+ volumes:
6
+ - "coverage:/usr/src/app/coverage"
7
+
8
+ volumes:
9
+ coverage: {}
@@ -0,0 +1,20 @@
1
+ # An example Rack middleware to capture the host the request was made to
2
+ require 'rack/request'
3
+
4
+ class HostMiddleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ if PaulBunyan.logger.respond_to?(:with_metadata)
11
+ req = Rack::Request.new(env)
12
+
13
+ PaulBunyan.logger.with_metadata(request_host: req.host) do
14
+ @app.call(env)
15
+ end
16
+ else
17
+ @app.call(env)
18
+ end
19
+ end
20
+ end
data/lib/paul_bunyan.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  require 'logger'
2
2
 
3
+
3
4
  require_relative 'paul_bunyan/json_formatter'
4
5
  require_relative 'paul_bunyan/level'
5
6
  require_relative 'paul_bunyan/log_relayer'
7
+ require_relative 'paul_bunyan/metadata_logging'
8
+ require_relative 'paul_bunyan/tagged_logging'
6
9
  require_relative 'paul_bunyan/text_formatter'
7
10
  require_relative 'paul_bunyan/version'
8
11
 
@@ -23,7 +26,6 @@ module PaulBunyan
23
26
  class UnknownFormatterError < Error; end
24
27
  class UnknownLevelError < Error; end
25
28
 
26
- # The ONE method we care about.
27
29
  def logger
28
30
  PaulBunyan.logger
29
31
  end
@@ -49,6 +51,8 @@ module PaulBunyan
49
51
  def create_logger(logdev, shift_age = 0, shift_size = 1048576, formatter_type: PaulBunyan.default_formatter_type)
50
52
  logger = Logger.new(logdev, shift_age, shift_size)
51
53
  logger.formatter = default_formatter(formatter_type) unless formatter_type.nil?
54
+ logger.extend(TaggedLogging) if logger.formatter.respond_to?(:tagged)
55
+ logger.extend(MetadataLogging) if logger.formatter.respond_to?(:with_metadata)
52
56
  add_logger(logger)
53
57
  end
54
58
 
@@ -59,7 +63,7 @@ module PaulBunyan
59
63
  end
60
64
 
61
65
  def remove_logger(logger)
62
- @logger.remove_logger(logger)
66
+ @logger.remove_logger(logger) if @logger
63
67
  end
64
68
 
65
69
  private def default_formatter(formatter_type)
@@ -5,13 +5,17 @@ module PaulBunyan
5
5
  class JSONFormatter
6
6
  DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%3N'
7
7
 
8
+ def add_metadata(metadata)
9
+ current_metadata.merge!(metadata)
10
+ end
11
+
8
12
  def call(severity, time, progname, msg)
9
- metadata = {
13
+ metadata = current_metadata.merge({
10
14
  "ts" => time.utc.strftime(DATETIME_FORMAT),
11
15
  "unix_ts" => time.to_f,
12
16
  "severity" => severity,
13
17
  "pid" => $$,
14
- }
18
+ })
15
19
  metadata['program'] = progname if progname
16
20
  metadata['tags'] = current_tags unless current_tags.empty?
17
21
 
@@ -20,13 +24,24 @@ module PaulBunyan
20
24
  JSON::generate(merge_metadata_and_message(metadata, message_data)) + "\n"
21
25
  end
22
26
 
27
+ def clear_metadata!
28
+ current_metadata.clear
29
+ end
30
+
23
31
  def clear_tags!
24
32
  current_tags.clear
25
33
  end
26
34
 
35
+ def current_metadata
36
+ metadata_thread_key = @metadata_thread_key ||=
37
+ "paul_bunyan_logging_metadata:#{self.object_id}"
38
+ Thread.current[metadata_thread_key] ||= {}
39
+ end
40
+
27
41
  def current_tags
28
- thread_key = @thread_key ||= "logging_tagged_logging_tags:#{ Thread.current.object_id }"
29
- Thread.current[thread_key] ||= []
42
+ tags_thread_key = @tags_thread_key ||=
43
+ "paul_bunyan_logging_tags:#{self.object_id}"
44
+ Thread.current[tags_thread_key] ||= []
30
45
  end
31
46
 
32
47
  def datetime_format=(value)
@@ -48,6 +63,10 @@ module PaulBunyan
48
63
  end
49
64
  end
50
65
 
66
+ def remove_metadata(metadata)
67
+ metadata.each { |k, _| current_metadata.delete(k) }
68
+ end
69
+
51
70
  def tagged(*tags)
52
71
  clean_tags = push_tags(tags)
53
72
  yield
@@ -55,6 +74,13 @@ module PaulBunyan
55
74
  pop_tags(clean_tags.size)
56
75
  end
57
76
 
77
+ def with_metadata(consumer_metadata)
78
+ add_metadata(consumer_metadata)
79
+ yield
80
+ ensure
81
+ remove_metadata(consumer_metadata)
82
+ end
83
+
58
84
  private
59
85
 
60
86
  # TODO: extract all of this formatting/merging out into another class if it grows
@@ -108,43 +108,66 @@ module PaulBunyan
108
108
  silencer.call
109
109
  end
110
110
 
111
- module TaggedRelayer
112
- def current_tags
113
- tags = loggers.each_with_object(Set.new) do |logger, set|
114
- set.merge(logger.current_tags) if logger.respond_to?(:current_tags)
115
- end
116
- tags.to_a
111
+ def current_tags
112
+ tags = loggers.each_with_object(Set.new) do |logger, set|
113
+ set.merge(logger.current_tags) if logger.respond_to?(:current_tags)
117
114
  end
115
+ tags.to_a
116
+ end
118
117
 
119
- def push_tags(*tags)
120
- tags.flatten.reject(&:blank?).tap do |new_tags|
121
- loggers.each { |logger| logger.push_tags(*new_tags) if logger.respond_to?(:push_tags) }
122
- end
118
+ def push_tags(*tags)
119
+ tags.flatten.reject(&:blank?).tap do |new_tags|
120
+ messaging_loggers(:push_tags, *new_tags)
123
121
  end
122
+ end
124
123
 
125
- def pop_tags(size = 1)
126
- loggers.each { |logger| logger.pop_tags(size) if logger.respond_to?(:pop_tags) }
127
- nil
128
- end
124
+ def pop_tags(size = 1)
125
+ messaging_loggers(:pop_tags, size)
126
+ end
129
127
 
130
- def clear_tags!
131
- loggers.each { |logger| logger.clear_tags! if logger.respond_to?(:clear_tags!) }
132
- nil
133
- end
128
+ def clear_tags!
129
+ messaging_loggers(:clear_tags!)
130
+ end
134
131
 
135
- def flush
136
- loggers.each { |logger| logger.flush if logger.respond_to?(:flush) }
137
- nil
138
- end
132
+ def flush
133
+ messaging_loggers(:flush)
134
+ end
135
+
136
+ def tagged(*tags)
137
+ new_tags = push_tags(*tags)
138
+ yield self
139
+ ensure
140
+ pop_tags(new_tags.size)
141
+ end
139
142
 
140
- def tagged(*tags)
141
- new_tags = push_tags(*tags)
142
- yield self
143
- ensure
144
- pop_tags(new_tags.size)
143
+ def add_metadata(metadata)
144
+ messaging_loggers(:add_metadata, metadata)
145
+ end
146
+
147
+ def clear_metadata!
148
+ messaging_loggers(:clear_metadata!)
149
+ end
150
+
151
+ def current_metadata
152
+ loggers.inject({}) do |agg, logger|
153
+ if logger.respond_to?(:current_metadata)
154
+ agg.merge(logger.current_metadata)
155
+ else
156
+ agg
157
+ end
145
158
  end
146
159
  end
147
- include TaggedRelayer
160
+
161
+ def remove_metadata(metadata)
162
+ messaging_loggers(:remove_metadata, metadata)
163
+ end
164
+
165
+ def with_metadata(metadata)
166
+ add_metadata(metadata)
167
+ yield
168
+ ensure
169
+ remove_metadata(metadata)
170
+ end
148
171
 
149
172
  private
150
173
 
@@ -157,5 +180,10 @@ module PaulBunyan
157
180
  result = block.call
158
181
  end
159
182
  end
183
+
184
+ def messaging_loggers(required_method, *args)
185
+ loggers.each { |l| l.public_send(required_method, *args) if l.respond_to?(required_method) }
186
+ nil
187
+ end
160
188
  end
161
189
  end
@@ -0,0 +1,16 @@
1
+ module PaulBunyan
2
+ module MetadataLogging
3
+ def clear_metadata!
4
+ formatter.clear_metadata!
5
+ end
6
+
7
+ def with_metadata(metadata)
8
+ formatter.with_metadata(metadata) { yield self }
9
+ end
10
+
11
+ def flush
12
+ clear_metadata!
13
+ super if defined?(super)
14
+ end
15
+ end
16
+ end
@@ -31,6 +31,7 @@ module PaulBunyan
31
31
  new_logger.level = PaulBunyan::Level.coerce_level(ENV['LOG_LEVEL'] || ::Rails.application.config.log_level || 'INFO')
32
32
  new_logger.formatter = logging_config.formatter
33
33
  new_logger.extend(ActiveSupport::TaggedLogging) if logging_config.formatter.respond_to?(:tagged)
34
+ new_logger.extend(PaulBunyan::MetadataLogging) if logging_config.formatter.respond_to?(:with_metadata)
34
35
 
35
36
  if logging_config.handle_request_logging
36
37
  unsubscribe_default_log_subscribers
@@ -71,15 +71,6 @@ module PaulBunyan
71
71
  end
72
72
  end
73
73
 
74
- if Rails.version.start_with?('4.0')
75
- attr_reader :patterns
76
-
77
- def initialize
78
- super
79
- @patterns ||= []
80
- end
81
- end
82
-
83
74
  # Handle the start_processing event in the action_controller namespace
84
75
  #
85
76
  # We're only registering for this event so the default
@@ -0,0 +1,24 @@
1
+ module PaulBunyan
2
+ module TaggedLogging
3
+ def push_tags(*args)
4
+ formatter.push_tags(*args)
5
+ end
6
+
7
+ def pop_tags(count = 1)
8
+ formatter.pop_tags(count)
9
+ end
10
+
11
+ def clear_tags!
12
+ formatter.clear_tags!
13
+ end
14
+
15
+ def tagged(*tags)
16
+ formatter.tagged(*tags) { yield self }
17
+ end
18
+
19
+ def flush
20
+ clear_tags!
21
+ super if defined?(super)
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module PaulBunyan
2
- VERSION = '1.4.0'
2
+ VERSION = '1.5.0'
3
3
  end
data/paul_bunyan.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ['duane@instructure.com', 'kromney@instructure.com', 'markse@instructure.com', 'tpickett@instructure.com']
11
11
  spec.summary = 'Logging for all the things'
12
12
  spec.description = "Extensions and enhancements to Ruby's built in Logger class. Extensions include: multiple output streams, JSON formatting for easy aggregation, and a Railtie to set some sane(ish) defaults for production Rails environments."
13
- spec.homepage = ''
13
+ spec.homepage = 'https://github.com/instructure/paul_bunyan'
14
14
  spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
@@ -24,11 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'rake'
25
25
  spec.add_development_dependency 'rspec'
26
26
  spec.add_development_dependency 'wwtd'
27
- spec.add_development_dependency 'rails'
27
+ spec.add_development_dependency 'rails', '>= 4.2'
28
28
  spec.add_development_dependency 'sqlite3'
29
29
  spec.add_development_dependency 'byebug'
30
-
31
- if RUBY_VERSION < '2.2'
32
- spec.add_development_dependency 'rack', '< 2.0'
33
- end
34
30
  end
@@ -20,7 +20,7 @@ module Dummy
20
20
  # config.i18n.default_locale = :de
21
21
 
22
22
  # Do not swallow errors in after_commit/after_rollback callbacks.
23
- if Rails.version > '4.2'
23
+ if Rails.version > '4.2' && Rails.version < '5.0'
24
24
  config.active_record.raise_in_transactional_callbacks = true
25
25
  end
26
26
  end
@@ -13,8 +13,11 @@ Dummy::Application.configure do
13
13
  config.eager_load = false
14
14
 
15
15
  # Configure static file server for tests with Cache-Control for performance.
16
- config.serve_static_files = true
17
- config.static_cache_control = 'public, max-age=3600'
16
+ if Rails.version <= '5.0'
17
+ config.serve_static_files = true
18
+ config.static_cache_control = 'public, max-age=3600'
19
+ end
20
+
18
21
 
19
22
  # Show full error reports and disable caching.
20
23
  config.consider_all_requests_local = true
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec path: '../../'
4
4
 
5
- gem 'rails', '~> 4.0.0'
5
+ gem 'rails', '~> 5.0.0'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec path: '../../'
4
4
 
5
- gem 'rails', '~> 4.1.0'
5
+ gem 'rails', '~> 5.1.0'
@@ -56,6 +56,14 @@ module PaulBunyan
56
56
  end
57
57
  end
58
58
 
59
+ it 'must merge supplied metadata into the message' do
60
+ formatter.with_metadata(foo: 'bar') do
61
+ output = formatter.call('', time, '', 'This is my message, there are many like it.')
62
+ object = JSON.parse(output)
63
+ expect(object).to include 'foo' => 'bar'
64
+ end
65
+ end
66
+
59
67
  context 'when supplied a string as the message' do
60
68
  it 'must wrap the message in an object with a message key containing the key' do
61
69
  output = formatter.call('', time, '', 'This is my message, there are many like it.')
@@ -1,10 +1,6 @@
1
- require 'set'
2
1
  require 'spec_helper'
3
- require 'stringio'
4
2
 
5
3
  describe PaulBunyan::LogRelayer do
6
- let(:device) { StringIO.new }
7
-
8
4
  let(:empty_relayer) { PaulBunyan::LogRelayer.new }
9
5
 
10
6
  let(:primary) { double('primary logger') }
@@ -34,10 +30,6 @@ describe PaulBunyan::LogRelayer do
34
30
  end
35
31
  end
36
32
 
37
- describe '#loggers' do
38
- # tested by specs for #primary_logger and #secondary_loggers
39
- end
40
-
41
33
  describe '#primary_logger' do
42
34
  it 'matches the first value in #loggers' do
43
35
  expect(double_relayer.primary_logger).to eq(double_relayer.loggers.first)
@@ -66,7 +58,7 @@ describe PaulBunyan::LogRelayer do
66
58
  describe '#add_logger' do
67
59
  it 'adds loggers to the end of the #loggers array' do
68
60
  empty_relayer.add_logger(primary)
69
- expect(empty_relayer.loggers.last).to be(primary)
61
+ expect(empty_relayer.loggers).to contain_exactly(primary)
70
62
  end
71
63
 
72
64
  it 'sets #primary_logger when the first logger is added' do
@@ -256,93 +248,160 @@ describe PaulBunyan::LogRelayer do
256
248
  end
257
249
  end
258
250
 
259
- context 'tagging' do
260
- describe '#current_tags' do
261
- it 'aggregates the #current_tags of child loggers (skipping non-tagged loggers)' do
262
- expect(primary).to receive(:current_tags).and_return(%w(a b))
263
- expect(tertiary).to receive(:current_tags).and_return(%w(b c))
264
- expect(Set.new(triple_relayer.current_tags)).to be_superset(Set.new(%w(a b c)))
251
+ context 'silence' do
252
+ describe '#silence' do
253
+ it 'calls silence on all loggers' do
254
+ expect(primary).to receive(:silence).with(Logger::WARN) { |&b| b.call }
255
+ expect(secondary).to receive(:silence).with(Logger::WARN) { |&b| b.call }
256
+ expect { |b| double_relayer.silence(Logger::WARN, &b) }.to yield_control
265
257
  end
266
- end
267
258
 
268
- describe '#push_tags' do
269
- it 'delegates to all child loggers' do
270
- expect(primary).to receive(:push_tags).with(*%w(a b c))
271
- expect(tertiary).to receive(:push_tags).with(*%w(a b c))
272
- triple_relayer.push_tags(*%w(a b c))
259
+ it 'skips loggers without a #silence method' do
260
+ allow(secondary).to receive(:respond_to?) { false }
261
+ expect(primary).to receive(:silence).with(Logger::WARN) { |&b| b.call }
262
+ expect { |b| double_relayer.silence(Logger::WARN, &b) }.to yield_control
273
263
  end
264
+ end
265
+ end
274
266
 
275
- it 'flattens the tags list' do
276
- expect(primary).to receive(:push_tags).with(*%w(a b c d))
277
- single_relayer.push_tags(%w(a b), %w(c d))
278
- end
267
+ describe '#current_tags' do
268
+ it 'aggregates the #current_tags of child loggers (skipping non-tagged loggers)' do
269
+ expect(primary).to receive(:current_tags).and_return(%w(a b))
270
+ expect(tertiary).to receive(:current_tags).and_return(%w(b c))
271
+ expect(Set.new(triple_relayer.current_tags)).to be_superset(Set.new(%w(a b c)))
272
+ end
273
+ end
279
274
 
280
- it 'omits blank tags' do
281
- expect(primary).to receive(:push_tags).with(*%w(a b c d))
282
- single_relayer.push_tags(['a', 'b', '', nil, 'c', 'd'])
283
- end
275
+ describe '#push_tags' do
276
+ it 'delegates to all child loggers' do
277
+ expect(primary).to receive(:push_tags).with(*%w(a b c))
278
+ expect(tertiary).to receive(:push_tags).with(*%w(a b c))
279
+ triple_relayer.push_tags(*%w(a b c))
280
+ end
284
281
 
285
- it 'returns the flattened, non-blank list of tags' do
286
- allow(primary).to receive(:push_tags)
287
- expect(single_relayer.push_tags(%w(a b), nil, '', %w(c d))).to eq(%w(a b c d))
288
- end
282
+ it 'flattens the tags list' do
283
+ expect(primary).to receive(:push_tags).with(*%w(a b c d))
284
+ single_relayer.push_tags(%w(a b), %w(c d))
289
285
  end
290
286
 
291
- describe '#pop_tags' do
292
- it 'delegates to all child loggers' do
293
- expect(primary).to receive(:pop_tags).with(4)
294
- expect(tertiary).to receive(:pop_tags).with(4)
295
- triple_relayer.pop_tags(4)
287
+ it 'omits blank tags' do
288
+ expect(primary).to receive(:push_tags).with(*%w(a b c d))
289
+ single_relayer.push_tags(['a', 'b', '', nil, 'c', 'd'])
290
+ end
291
+
292
+ it 'returns the flattened, non-blank list of tags' do
293
+ allow(primary).to receive(:push_tags)
294
+ expect(single_relayer.push_tags(%w(a b), nil, '', %w(c d))).to eq(%w(a b c d))
295
+ end
296
+ end
297
+
298
+ describe '#pop_tags' do
299
+ it 'delegates to all child loggers' do
300
+ expect(primary).to receive(:pop_tags).with(4)
301
+ expect(tertiary).to receive(:pop_tags).with(4)
302
+ triple_relayer.pop_tags(4)
303
+ end
304
+ end
305
+
306
+ describe '#clear_tags!' do
307
+ it 'delegates to all child loggers' do
308
+ expect(primary).to receive(:clear_tags!)
309
+ expect(secondary).to receive(:clear_tags!)
310
+ triple_relayer.clear_tags!
311
+ end
312
+ end
313
+
314
+ describe '#flush' do
315
+ it 'delegates to all child loggers' do
316
+ expect(primary).to receive(:flush)
317
+ expect(secondary).to receive(:flush)
318
+ triple_relayer.flush
319
+ end
320
+ end
321
+
322
+ describe '#tagged' do
323
+ it 'calls #push_tags with the given tags' do
324
+ expect(single_relayer).to receive(:push_tags).with(*%w(a b c)).and_return(%w(a b c))
325
+ expect { |b| single_relayer.tagged(*%w(a b c), &b) }.to yield_control
326
+ end
327
+
328
+ it 'calls #pop_tags with the number of tags returned from #push_tags' do
329
+ expect(single_relayer).to receive(:push_tags).with('a', 'b', '', 'd').and_return(%w(a b c))
330
+ expect(single_relayer).to receive(:pop_tags).with(3)
331
+ single_relayer.tagged('a', 'b', '', 'd') {}
332
+ end
333
+
334
+ it 'calls #pop_tags when the block raises an exception' do
335
+ expect(single_relayer).to receive(:pop_tags)
336
+ expect { single_relayer.tagged('a') { fail StandardError, ':(' } }.to raise_error(StandardError)
337
+ end
338
+ end
339
+
340
+ context 'logging metadata' do
341
+ let(:my_metadata) { {foo: 'bar'} }
342
+
343
+ before do
344
+ allow(primary).to receive(:respond_to?).and_return(true)
345
+ allow(secondary).to receive(:respond_to?).and_return(false)
346
+ allow(tertiary).to receive(:respond_to?).and_return(true)
347
+ end
348
+
349
+ describe '#add_metadata' do
350
+ it 'delegates to all child loggers that support it' do
351
+ expect(primary).to receive(:add_metadata).with(my_metadata)
352
+ expect(secondary).to_not receive(:add_metadata)
353
+ double_relayer.add_metadata(my_metadata)
296
354
  end
297
355
  end
298
356
 
299
- describe '#clear_tags!' do
357
+ describe '#clear_metadata!' do
300
358
  it 'delegates to all child loggers' do
301
- expect(primary).to receive(:clear_tags!)
302
- expect(secondary).to receive(:clear_tags!)
303
- triple_relayer.clear_tags!
359
+ expect(primary).to receive(:clear_metadata!)
360
+ expect(secondary).to_not receive(:clear_metadata!)
361
+ double_relayer.clear_metadata!
304
362
  end
305
363
  end
306
364
 
307
- describe '#flush' do
308
- it 'delegates to all child loggers' do
309
- expect(primary).to receive(:flush)
310
- expect(secondary).to receive(:flush)
311
- triple_relayer.flush
365
+ describe '#current_metadata' do
366
+ it 'aggregates the #current_metadata of child loggers (skipping non-metadata loggers)' do
367
+ expect(primary).to receive(:current_metadata).and_return(foo: 'bar')
368
+ expect(secondary).to_not receive(:current_metadata)
369
+ expect(tertiary).to receive(:current_metadata).and_return(baz: 'qux')
370
+
371
+ expect(triple_relayer.current_metadata).to eq(foo: 'bar', baz: 'qux')
312
372
  end
313
373
  end
314
374
 
315
- describe '#tagged' do
316
- it 'calls #push_tags with the given tags' do
317
- expect(single_relayer).to receive(:push_tags).with(*%w(a b c)).and_return(%w(a b c))
318
- expect { |b| single_relayer.tagged(*%w(a b c), &b) }.to yield_control
375
+ describe '#remove_metadata' do
376
+ it 'delegates to all child loggers' do
377
+ expect(primary).to receive(:remove_metadata).with(my_metadata)
378
+ expect(secondary).to_not receive(:remove_metadata)
379
+ double_relayer.remove_metadata(my_metadata)
319
380
  end
381
+ end
320
382
 
321
- it 'calls #pop_tags with the number of tags returned from #push_tags' do
322
- expect(single_relayer).to receive(:push_tags).with('a', 'b', '', 'd').and_return(%w(a b c))
323
- expect(single_relayer).to receive(:pop_tags).with(3)
324
- single_relayer.tagged('a', 'b', '', 'd') {}
383
+ describe '#with_metadata' do
384
+ before do
385
+ allow(single_relayer).to receive(:add_metadata).with(my_metadata)
386
+ allow(single_relayer).to receive(:remove_metadata).with(my_metadata)
325
387
  end
326
388
 
327
- it 'calls #pop_tags when the block raises an exception' do
328
- expect(single_relayer).to receive(:pop_tags)
329
- expect { single_relayer.tagged('a') { fail StandardError, ':(' } }.to raise_error(StandardError)
389
+ it 'must call #add_metadata with the supplied hash' do
390
+ expect(single_relayer).to receive(:add_metadata).with(my_metadata)
391
+ expect { |b| single_relayer.with_metadata(my_metadata, &b) }.to yield_control
330
392
  end
331
- end
332
- end
333
393
 
334
- context 'silence' do
335
- describe '#silence' do
336
- it 'calls silence on all loggers' do
337
- expect(primary).to receive(:silence).with(Logger::WARN) { |&b| b.call }
338
- expect(secondary).to receive(:silence).with(Logger::WARN) { |&b| b.call }
339
- expect { |b| double_relayer.silence(Logger::WARN, &b) }.to yield_control
394
+ it 'must call #remove_metadata with the supplied hash' do
395
+ expect(single_relayer).to receive(:remove_metadata).with(my_metadata)
396
+ expect { |b| single_relayer.with_metadata(my_metadata, &b) }.to yield_control
340
397
  end
341
398
 
342
- it 'skips loggers without a #silence method' do
343
- allow(secondary).to receive(:respond_to?) { false }
344
- expect(primary).to receive(:silence).with(Logger::WARN) { |&b| b.call }
345
- expect { |b| double_relayer.silence(Logger::WARN, &b) }.to yield_control
399
+ it 'must call #remove_metadata when the block raises an exception' do
400
+ expect(single_relayer).to receive(:remove_metadata).with(my_metadata)
401
+ expect { single_relayer.with_metadata(my_metadata) do
402
+ fail StandardError, ':('
403
+ end
404
+ }.to raise_error(StandardError)
346
405
  end
347
406
  end
348
407
  end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe PaulBunyan::MetadataLogging do
4
+ subject { Object.new }
5
+ let(:formatter) { double('formatter') }
6
+
7
+ before do
8
+ subject.extend(PaulBunyan::MetadataLogging)
9
+ allow(subject).to receive(:formatter).and_return(formatter)
10
+ end
11
+
12
+ it 'must delegate clear_metadata! to the formatter' do
13
+ expect(formatter).to receive(:clear_metadata!)
14
+ subject.clear_metadata!
15
+ end
16
+
17
+ it 'must delegate with_metadata to the formatter' do
18
+ expect(formatter).to receive(:with_metadata).with(foo: 'bar').and_yield
19
+ subject.with_metadata(foo: 'bar') do |logger|
20
+ expect(subject).to eq logger
21
+ end
22
+ end
23
+
24
+ context '#flush' do
25
+ it 'clears metadata on the formatter' do
26
+ expect(formatter).to receive(:clear_metadata!)
27
+ subject.flush
28
+ end
29
+
30
+ it 'sends flush to a parent' do
31
+ klass = Class.new
32
+ klass.class_eval do
33
+ def flush
34
+ flush_behavior()
35
+ end
36
+ end
37
+
38
+ metadata_logger = klass.new
39
+ metadata_logger.extend(PaulBunyan::MetadataLogging)
40
+ allow(metadata_logger).to receive(:formatter).and_return(formatter)
41
+ allow(formatter).to receive(:clear_metadata!)
42
+
43
+ expect(metadata_logger).to receive(:flush_behavior)
44
+ metadata_logger.flush
45
+ end
46
+ end
47
+ end
@@ -18,7 +18,7 @@ RSpec.describe ActionController::Base do
18
18
  end
19
19
 
20
20
  before :each do
21
- subject.request = ActionDispatch::TestRequest.new({
21
+ subject.request = build_request({
22
22
  'action_dispatch.request_id' => request_id,
23
23
  'REMOTE_ADDR' => '127.0.0.1',
24
24
  })
@@ -74,7 +74,7 @@ RSpec.describe ActionController::Base do
74
74
 
75
75
  context 'with sensitive info in the query params' do
76
76
  before do
77
- subject.request = ActionDispatch::TestRequest.new({
77
+ subject.request = build_request({
78
78
  'action_dispatch.parameter_filter' => [:password, :baz],
79
79
  'PATH_INFO' => '/somewhere',
80
80
  'QUERY_STRING' => 'password=foo&bar=baz&baz=qux',
@@ -90,6 +90,11 @@ RSpec.describe ActionController::Base do
90
90
  end
91
91
  end
92
92
 
93
+ def build_request(attrs)
94
+ factory_method = (Rails.version >= '5.0' ? :create : :new)
95
+ ActionDispatch::TestRequest.send(factory_method, attrs)
96
+ end
97
+
93
98
  def calling_index(&block)
94
99
  with_subscription_to('process_action.action_controller', block) do
95
100
  subject.send(:process_action, :index)
@@ -4,10 +4,7 @@ module PaulBunyan
4
4
  RSpec.describe Railtie do
5
5
  describe 'initializer "initialize_logger.logging"' do
6
6
  it 'must extend the logger with ActiveSupport::TaggedLogging' do
7
- # Since we're extending an instance of a class it's hard to actually
8
- # check for the module in the ancestry chain. respond_to? should be a
9
- # good enough proxy for it though.
10
- expect(PaulBunyan.logger.primary_logger).to respond_to(:tagged)
7
+ expect(PaulBunyan.logger.primary_logger).to be_kind_of(ActiveSupport::TaggedLogging)
11
8
  end
12
9
  end
13
10
 
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe PaulBunyan::TaggedLogging do
4
+ subject { Object.new }
5
+ let(:formatter) { double('formatter') }
6
+
7
+ before do
8
+ subject.extend(PaulBunyan::TaggedLogging)
9
+ allow(subject).to receive(:formatter).and_return(formatter)
10
+ end
11
+
12
+ it 'delegates #push_tags to #formatter' do
13
+ expect(formatter).to receive(:push_tags).with('a','b','c')
14
+ subject.push_tags('a', 'b', 'c')
15
+ end
16
+
17
+ it 'delegates #pop_tags to #formatter' do
18
+ expect(formatter).to receive(:pop_tags).with(3)
19
+ subject.pop_tags(3)
20
+ end
21
+
22
+ it 'delegates #clear_tags! to #formatter' do
23
+ expect(formatter).to receive(:clear_tags!)
24
+ subject.clear_tags!
25
+ end
26
+
27
+ it 'delegates #tagged to #formatter' do
28
+ expect(formatter).to receive(:tagged).with('a','b','c').and_yield
29
+ subject.tagged('a', 'b', 'c') do |logger|
30
+ expect(subject).to eq logger
31
+ end
32
+ end
33
+
34
+ context '#flush' do
35
+ it 'clears tags on the formatter' do
36
+ expect(formatter).to receive(:clear_tags!)
37
+ subject.flush
38
+ end
39
+
40
+ it 'sends flush to a parent' do
41
+ klass = Class.new
42
+ klass.class_eval do
43
+ def flush
44
+ flush_behavior()
45
+ end
46
+ end
47
+
48
+ tagged_logger = klass.new
49
+ tagged_logger.extend(PaulBunyan::TaggedLogging)
50
+ allow(tagged_logger).to receive(:formatter).and_return(formatter)
51
+ allow(formatter).to receive(:clear_tags!)
52
+
53
+ expect(tagged_logger).to receive(:flush_behavior)
54
+ tagged_logger.flush
55
+ end
56
+ end
57
+ end
@@ -4,7 +4,7 @@ initial_default_formatter_type = PaulBunyan.default_formatter_type
4
4
 
5
5
  describe PaulBunyan do
6
6
  shared_examples 'respecting the ::default_formatter_type' do
7
- let(:logger) { double('logger') }
7
+ let(:logger) { double('logger', formatter: nil) }
8
8
 
9
9
  before do
10
10
  allow(Logger).to receive(:new).and_return(logger)
@@ -69,7 +69,7 @@ describe PaulBunyan do
69
69
 
70
70
  describe '::create_logger' do
71
71
  let(:device) { double('device') }
72
- let(:logger) { double('logger') }
72
+ let(:logger) { double('logger', formatter: nil) }
73
73
 
74
74
  before do
75
75
  allow(logger).to receive(:formatter=)
@@ -92,6 +92,32 @@ describe PaulBunyan do
92
92
  PaulBunyan.create_logger(device, formatter_type: :text)
93
93
  end
94
94
 
95
+ it 'creates a logger capable of tagging when using formatter that is capable of tagging' do
96
+ allow(logger).to receive(:formatter).and_return(double('formatter', tagged:nil))
97
+ allow(Logger).to receive(:new).and_return(logger)
98
+ PaulBunyan.create_logger(device)
99
+
100
+ expect(logger.formatter).to respond_to(:tagged)
101
+ expect(logger).to be_kind_of(PaulBunyan::TaggedLogging)
102
+ end
103
+
104
+ it 'must create a logger capable of handling metadata when using a formatter capable of handling metadata' do
105
+ allow(logger).to receive(:formatter).and_return(double('formatter', with_metadata:nil))
106
+ allow(Logger).to receive(:new).and_return(logger)
107
+ PaulBunyan.create_logger(device)
108
+
109
+ expect(logger.formatter).to respond_to(:with_metadata)
110
+ expect(logger).to be_kind_of(PaulBunyan::MetadataLogging)
111
+ end
112
+
113
+ it 'creates a regular logger when using formatter that is not capable of tagging' do
114
+ allow(Logger).to receive(:new).and_return(logger)
115
+ PaulBunyan.create_logger(device)
116
+
117
+ expect(logger.formatter).to_not respond_to(:tagged)
118
+ expect(logger).to_not be_kind_of(PaulBunyan::TaggedLogging)
119
+ end
120
+
95
121
  def default_formatter_type_call
96
122
  PaulBunyan.create_logger(device)
97
123
  end
@@ -129,6 +155,11 @@ describe PaulBunyan do
129
155
  end
130
156
 
131
157
  describe '::remove_logger' do
158
+ it 'must not blow up when the logger has not been set' do
159
+ PaulBunyan.instance_variable_set(:@logger, nil)
160
+ PaulBunyan.remove_logger('nxlogger')
161
+ end
162
+
132
163
  it 'removes the logger from the log relayer' do
133
164
  expect(PaulBunyan.logger).to receive(:remove_logger).with(PaulBunyan.logger.primary_logger)
134
165
  PaulBunyan.remove_logger(PaulBunyan.logger.primary_logger)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paul_bunyan
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Duane Johnson
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2016-11-28 00:00:00.000000000 Z
14
+ date: 2017-09-26 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: request_store
@@ -89,14 +89,14 @@ dependencies:
89
89
  requirements:
90
90
  - - ">="
91
91
  - !ruby/object:Gem::Version
92
- version: '0'
92
+ version: '4.2'
93
93
  type: :development
94
94
  prerelease: false
95
95
  version_requirements: !ruby/object:Gem::Requirement
96
96
  requirements:
97
97
  - - ">="
98
98
  - !ruby/object:Gem::Version
99
- version: '0'
99
+ version: '4.2'
100
100
  - !ruby/object:Gem::Dependency
101
101
  name: sqlite3
102
102
  requirement: !ruby/object:Gem::Requirement
@@ -146,21 +146,24 @@ files:
146
146
  - Guardfile
147
147
  - LICENSE.txt
148
148
  - README.md
149
- - README.rdoc
150
149
  - Rakefile
151
150
  - bin/logging_demo
152
151
  - build.sh
152
+ - docker-compose.override.yml.example
153
153
  - docker-compose.yml
154
+ - examples/host_middleware.rb
154
155
  - lib/paul_bunyan.rb
155
156
  - lib/paul_bunyan/json_formatter.rb
156
157
  - lib/paul_bunyan/level.rb
157
158
  - lib/paul_bunyan/log_relayer.rb
159
+ - lib/paul_bunyan/metadata_logging.rb
158
160
  - lib/paul_bunyan/rails_ext.rb
159
161
  - lib/paul_bunyan/rails_ext/active_support_logger.rb
160
162
  - lib/paul_bunyan/rails_ext/instrumentation.rb
161
163
  - lib/paul_bunyan/rails_ext/rack_logger.rb
162
164
  - lib/paul_bunyan/railtie.rb
163
165
  - lib/paul_bunyan/railtie/log_subscriber.rb
166
+ - lib/paul_bunyan/tagged_logging.rb
164
167
  - lib/paul_bunyan/text_formatter.rb
165
168
  - lib/paul_bunyan/version.rb
166
169
  - lib/tasks/paul_bunyan_tasks.rake
@@ -206,19 +209,21 @@ files:
206
209
  - spec/dummy/public/422.html
207
210
  - spec/dummy/public/500.html
208
211
  - spec/dummy/public/favicon.ico
209
- - spec/gemfiles/40.gemfile
210
- - spec/gemfiles/41.gemfile
211
212
  - spec/gemfiles/42.gemfile
213
+ - spec/gemfiles/50.gemfile
214
+ - spec/gemfiles/51.gemfile
212
215
  - spec/lib/paul_bunyan/json_formatter_spec.rb
213
216
  - spec/lib/paul_bunyan/level_spec.rb
214
217
  - spec/lib/paul_bunyan/log_relayer_spec.rb
218
+ - spec/lib/paul_bunyan/metadata_logging_spec.rb
215
219
  - spec/lib/paul_bunyan/rails_ext/instrumentation_spec.rb
216
220
  - spec/lib/paul_bunyan/railtie/log_subscriber_spec.rb
217
221
  - spec/lib/paul_bunyan/railtie_spec.rb
222
+ - spec/lib/paul_bunyan/tagged_logging_spec.rb
218
223
  - spec/lib/paul_bunyan_spec.rb
219
224
  - spec/spec_helper.rb
220
225
  - spec/support/notification_helpers.rb
221
- homepage: ''
226
+ homepage: https://github.com/instructure/paul_bunyan
222
227
  licenses:
223
228
  - MIT
224
229
  metadata: {}
@@ -238,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
243
  version: '0'
239
244
  requirements: []
240
245
  rubyforge_project:
241
- rubygems_version: 2.5.1
246
+ rubygems_version: 2.6.13
242
247
  signing_key:
243
248
  specification_version: 4
244
249
  summary: Logging for all the things
@@ -284,15 +289,17 @@ test_files:
284
289
  - spec/dummy/public/422.html
285
290
  - spec/dummy/public/500.html
286
291
  - spec/dummy/public/favicon.ico
287
- - spec/gemfiles/40.gemfile
288
- - spec/gemfiles/41.gemfile
289
292
  - spec/gemfiles/42.gemfile
293
+ - spec/gemfiles/50.gemfile
294
+ - spec/gemfiles/51.gemfile
290
295
  - spec/lib/paul_bunyan/json_formatter_spec.rb
291
296
  - spec/lib/paul_bunyan/level_spec.rb
292
297
  - spec/lib/paul_bunyan/log_relayer_spec.rb
298
+ - spec/lib/paul_bunyan/metadata_logging_spec.rb
293
299
  - spec/lib/paul_bunyan/rails_ext/instrumentation_spec.rb
294
300
  - spec/lib/paul_bunyan/railtie/log_subscriber_spec.rb
295
301
  - spec/lib/paul_bunyan/railtie_spec.rb
302
+ - spec/lib/paul_bunyan/tagged_logging_spec.rb
296
303
  - spec/lib/paul_bunyan_spec.rb
297
304
  - spec/spec_helper.rb
298
305
  - spec/support/notification_helpers.rb
data/README.rdoc DELETED
@@ -1,3 +0,0 @@
1
- = PaulBunyan
2
-
3
- This project rocks and uses MIT-LICENSE.