paul_bunyan 1.4.0 → 1.5.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,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.