berkeley_library-logging 0.2.1 → 0.2.5

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
  SHA256:
3
- metadata.gz: f88d32f15e7f95d27b1ec0ba158068a7d2950a22e89ae2b169fccc4aa385c3f6
4
- data.tar.gz: '07193aef296c9b347993c02304ff506b9789ec45e6362b86180ab9da00c84519'
3
+ metadata.gz: cafd11d3f9d11029ae88c9ed475e08d9c5227d56f2924085145e6363fe76bfc1
4
+ data.tar.gz: 42eb37cd205ee71cbe710d86ddafd0a342ca31b3f54b40d988b16d5786ef6335
5
5
  SHA512:
6
- metadata.gz: a1d6d0b7d9692ef52ea6b266d2cdf782d90bea3caa1cacc785191445b21b1fdf0daf6126ebdf0f45278635c3d9abed4f5b239dc4d013aab91410a2cb949165d5
7
- data.tar.gz: 8eba0dc24f2d163827d415ccbbe780d0f3b23c3d5b6e583573b55c15a48d11e1c1ab6098c33cdc18b98461945f9ca19fad8c7158b572c9c8ec2825e35fd52a69
6
+ metadata.gz: bc1a877b7dd123c92aa2cbfdcb8f55676bb6ef2257857f4af395e19c2d27fb950e3fcbf69813a0a639dd372f50a711e3b5cc84dfd593aa67dd5035534447a339
7
+ data.tar.gz: 2fc6bbf11a1d51aa90a15d0841df1c49a38aad8add0a824e1dfcab0bfe4d3489bb6a40d8afe56e3da255d6caac62d68535acfd726f4673604fb1548f5c71d0d0
@@ -6,7 +6,7 @@ jobs:
6
6
  fail-fast: false
7
7
  matrix:
8
8
  os: [ ubuntu-latest, macos-latest ]
9
- ruby-version: [ '2.7', '3.0' ]
9
+ ruby: [ '2.7', '3.0' ]
10
10
  runs-on: ${{ matrix.os }}
11
11
 
12
12
  steps:
@@ -7,6 +7,7 @@
7
7
  <inspection_tool class="Rubocop" enabled="false" level="WARNING" enabled_by_default="false" />
8
8
  <inspection_tool class="RubyCaseWithoutElseBlockInspection" enabled="false" level="WARNING" enabled_by_default="false" />
9
9
  <inspection_tool class="RubyNilAnalysis" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
10
+ <inspection_tool class="RubyParameterNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
10
11
  <inspection_tool class="RubyStringKeysInHashInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
11
12
  <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
12
13
  <option name="processCode" value="true" />
@@ -14,4 +15,4 @@
14
15
  <option name="processComments" value="true" />
15
16
  </inspection_tool>
16
17
  </profile>
17
- </component>
18
+ </component>
data/.idea/logging.iml CHANGED
@@ -4,32 +4,28 @@
4
4
  <shared />
5
5
  </component>
6
6
  <component name="NewModuleRootManager">
7
- <content url="file://$MODULE_DIR$">
8
- <sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
9
- <sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
10
- <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
11
- </content>
7
+ <content url="file://$MODULE_DIR$" />
12
8
  <orderEntry type="jdk" jdkName="RVM: ruby-2.7.4" jdkType="RUBY_SDK" />
13
9
  <orderEntry type="sourceFolder" forTests="false" />
14
- <orderEntry type="library" scope="PROVIDED" name="actioncable (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
15
- <orderEntry type="library" scope="PROVIDED" name="actionmailbox (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
16
- <orderEntry type="library" scope="PROVIDED" name="actionmailer (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
17
- <orderEntry type="library" scope="PROVIDED" name="actionpack (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
18
- <orderEntry type="library" scope="PROVIDED" name="actiontext (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
19
- <orderEntry type="library" scope="PROVIDED" name="actionview (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
20
- <orderEntry type="library" scope="PROVIDED" name="activejob (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
21
- <orderEntry type="library" scope="PROVIDED" name="activemodel (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
22
- <orderEntry type="library" scope="PROVIDED" name="activerecord (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
23
- <orderEntry type="library" scope="PROVIDED" name="activestorage (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
24
- <orderEntry type="library" scope="PROVIDED" name="activesupport (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
25
- <orderEntry type="library" scope="PROVIDED" name="amazing_print (v1.3.0, RVM: ruby-2.7.4) [gem]" level="application" />
10
+ <orderEntry type="library" scope="PROVIDED" name="actioncable (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
11
+ <orderEntry type="library" scope="PROVIDED" name="actionmailbox (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
12
+ <orderEntry type="library" scope="PROVIDED" name="actionmailer (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
13
+ <orderEntry type="library" scope="PROVIDED" name="actionpack (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
14
+ <orderEntry type="library" scope="PROVIDED" name="actiontext (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="actionview (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
16
+ <orderEntry type="library" scope="PROVIDED" name="activejob (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="activemodel (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="activerecord (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="activestorage (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
20
+ <orderEntry type="library" scope="PROVIDED" name="activesupport (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="amazing_print (v1.4.0, RVM: ruby-2.7.4) [gem]" level="application" />
26
22
  <orderEntry type="library" scope="PROVIDED" name="ansi (v1.5.0, RVM: ruby-2.7.4) [gem]" level="application" />
27
23
  <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
28
24
  <orderEntry type="library" scope="PROVIDED" name="brakeman (v4.10.1, RVM: ruby-2.7.4) [gem]" level="application" />
29
25
  <orderEntry type="library" scope="PROVIDED" name="builder (v3.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
30
26
  <orderEntry type="library" scope="PROVIDED" name="bundle-audit (v0.1.0, RVM: ruby-2.7.4) [gem]" level="application" />
31
27
  <orderEntry type="library" scope="PROVIDED" name="bundler (v2.2.14, RVM: ruby-2.7.4) [gem]" level="application" />
32
- <orderEntry type="library" scope="PROVIDED" name="bundler-audit (v0.8.0, RVM: ruby-2.7.4) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="bundler-audit (v0.9.0.1, RVM: ruby-2.7.4) [gem]" level="application" />
33
29
  <orderEntry type="library" scope="PROVIDED" name="ci_reporter (v2.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
34
30
  <orderEntry type="library" scope="PROVIDED" name="ci_reporter_rspec (v1.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
35
31
  <orderEntry type="library" scope="PROVIDED" name="colorize (v0.8.1, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -39,7 +35,7 @@
39
35
  <orderEntry type="library" scope="PROVIDED" name="docile (v1.4.0, RVM: ruby-2.7.4) [gem]" level="application" />
40
36
  <orderEntry type="library" scope="PROVIDED" name="dotenv (v2.7.6, RVM: ruby-2.7.4) [gem]" level="application" />
41
37
  <orderEntry type="library" scope="PROVIDED" name="erubi (v1.10.0, RVM: ruby-2.7.4) [gem]" level="application" />
42
- <orderEntry type="library" scope="PROVIDED" name="ffi (v1.15.3, RVM: ruby-2.7.4) [gem]" level="application" />
38
+ <orderEntry type="library" scope="PROVIDED" name="ffi (v1.15.4, RVM: ruby-2.7.4) [gem]" level="application" />
43
39
  <orderEntry type="library" scope="PROVIDED" name="globalid (v0.5.2, RVM: ruby-2.7.4) [gem]" level="application" />
44
40
  <orderEntry type="library" scope="PROVIDED" name="i18n (v1.8.10, RVM: ruby-2.7.4) [gem]" level="application" />
45
41
  <orderEntry type="library" scope="PROVIDED" name="io-console (v0.5.9, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -48,23 +44,23 @@
48
44
  <orderEntry type="library" scope="PROVIDED" name="lograge (v0.11.2, RVM: ruby-2.7.4) [gem]" level="application" />
49
45
  <orderEntry type="library" scope="PROVIDED" name="loofah (v2.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
50
46
  <orderEntry type="library" scope="PROVIDED" name="mail (v2.7.1, RVM: ruby-2.7.4) [gem]" level="application" />
51
- <orderEntry type="library" scope="PROVIDED" name="marcel (v1.0.1, RVM: ruby-2.7.4) [gem]" level="application" />
47
+ <orderEntry type="library" scope="PROVIDED" name="marcel (v1.0.2, RVM: ruby-2.7.4) [gem]" level="application" />
52
48
  <orderEntry type="library" scope="PROVIDED" name="method_source (v1.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
53
- <orderEntry type="library" scope="PROVIDED" name="mini_mime (v1.1.0, RVM: ruby-2.7.4) [gem]" level="application" />
49
+ <orderEntry type="library" scope="PROVIDED" name="mini_mime (v1.1.1, RVM: ruby-2.7.4) [gem]" level="application" />
54
50
  <orderEntry type="library" scope="PROVIDED" name="minitest (v5.14.4, RVM: ruby-2.7.4) [gem]" level="application" />
55
51
  <orderEntry type="library" scope="PROVIDED" name="nio4r (v2.5.8, RVM: ruby-2.7.4) [gem]" level="application" />
56
- <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.12.3, RVM: ruby-2.7.4) [gem]" level="application" />
57
- <orderEntry type="library" scope="PROVIDED" name="oj (v3.13.2, RVM: ruby-2.7.4) [gem]" level="application" />
52
+ <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.12.4, RVM: ruby-2.7.4) [gem]" level="application" />
53
+ <orderEntry type="library" scope="PROVIDED" name="oj (v3.13.9, RVM: ruby-2.7.4) [gem]" level="application" />
58
54
  <orderEntry type="library" scope="PROVIDED" name="ougai (v1.9.1, RVM: ruby-2.7.4) [gem]" level="application" />
59
- <orderEntry type="library" scope="PROVIDED" name="parallel (v1.20.1, RVM: ruby-2.7.4) [gem]" level="application" />
55
+ <orderEntry type="library" scope="PROVIDED" name="parallel (v1.21.0, RVM: ruby-2.7.4) [gem]" level="application" />
60
56
  <orderEntry type="library" scope="PROVIDED" name="parser (v3.0.2.0, RVM: ruby-2.7.4) [gem]" level="application" />
61
57
  <orderEntry type="library" scope="PROVIDED" name="racc (v1.5.2, RVM: ruby-2.7.4) [gem]" level="application" />
62
58
  <orderEntry type="library" scope="PROVIDED" name="rack (v2.2.3, RVM: ruby-2.7.4) [gem]" level="application" />
63
59
  <orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.7.4) [gem]" level="application" />
64
- <orderEntry type="library" scope="PROVIDED" name="rails (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
60
+ <orderEntry type="library" scope="PROVIDED" name="rails (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
65
61
  <orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
66
- <orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
67
- <orderEntry type="library" scope="PROVIDED" name="railties (v6.1.4, RVM: ruby-2.7.4) [gem]" level="application" />
62
+ <orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
63
+ <orderEntry type="library" scope="PROVIDED" name="railties (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
68
64
  <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
69
65
  <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.4) [gem]" level="application" />
70
66
  <orderEntry type="library" scope="PROVIDED" name="rb-fsevent (v0.11.0, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -89,14 +85,18 @@
89
85
  <orderEntry type="library" scope="PROVIDED" name="simplecov_json_formatter (v0.1.3, RVM: ruby-2.7.4) [gem]" level="application" />
90
86
  <orderEntry type="library" scope="PROVIDED" name="sprockets (v4.0.2, RVM: ruby-2.7.4) [gem]" level="application" />
91
87
  <orderEntry type="library" scope="PROVIDED" name="sprockets-rails (v3.2.2, RVM: ruby-2.7.4) [gem]" level="application" />
92
- <orderEntry type="library" scope="PROVIDED" name="terminal-table (v3.0.1, RVM: ruby-2.7.4) [gem]" level="application" />
88
+ <orderEntry type="library" scope="PROVIDED" name="terminal-table (v3.0.2, RVM: ruby-2.7.4) [gem]" level="application" />
93
89
  <orderEntry type="library" scope="PROVIDED" name="thor (v1.1.0, RVM: ruby-2.7.4) [gem]" level="application" />
94
90
  <orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.4, RVM: ruby-2.7.4) [gem]" level="application" />
95
- <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v1.7.0, RVM: ruby-2.7.4) [gem]" level="application" />
91
+ <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v1.8.0, RVM: ruby-2.7.4) [gem]" level="application" />
96
92
  <orderEntry type="library" scope="PROVIDED" name="websocket-driver (v0.7.5, RVM: ruby-2.7.4) [gem]" level="application" />
97
93
  <orderEntry type="library" scope="PROVIDED" name="websocket-extensions (v0.1.5, RVM: ruby-2.7.4) [gem]" level="application" />
98
94
  <orderEntry type="library" scope="PROVIDED" name="zeitwerk (v2.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
99
95
  </component>
96
+ <component name="RModuleSettingsStorage">
97
+ <LOAD_PATH number="2" string0="$MODULE_DIR$/lib" string1="$MODULE_DIR$/spec" />
98
+ <I18N_FOLDERS number="0" />
99
+ </component>
100
100
  <component name="RakeTasksCache">
101
101
  <option name="myRootTask">
102
102
  <RakeTaskImpl id="rake">
@@ -110,7 +110,7 @@
110
110
  <RakeTaskImpl description="Remove artifacts directory" fullCommand="clean" id="clean" />
111
111
  <RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
112
112
  <RakeTaskImpl description="Clean, check, build gem" fullCommand="default" id="default" />
113
- <RakeTaskImpl description="Build berkeley_library-logging.gemspec as berkeley_library-logging-0.2.0.gem" fullCommand="gem" id="gem" />
113
+ <RakeTaskImpl description="Build berkeley_library-logging.gemspec as berkeley_library-logging-0.2.4.gem" fullCommand="gem" id="gem" />
114
114
  <RakeTaskImpl description="Run RuboCop" fullCommand="rubocop" id="rubocop" />
115
115
  <RakeTaskImpl id="rubocop">
116
116
  <subtasks>
data/.simplecov CHANGED
@@ -1,4 +1,3 @@
1
1
  SimpleCov.start 'rails' do
2
2
  add_filter 'module_info.rb'
3
- formatter SimpleCov::Formatter::SimpleFormatter
4
3
  end
data/CHANGES.md CHANGED
@@ -1,3 +1,28 @@
1
+ # 0.2.5 (2021-11-04)
2
+
3
+ - Rails event logs now include a subset of session attributes (`session_id` and `_csrf_token`).
4
+ Set `Rails.config.lograge.verbose_session_logging` to log the full session hash.
5
+
6
+ # 0.2.4 (2021-11-02)
7
+
8
+ - Rails event logs now include the following, in addition to the
9
+ headers already logged:
10
+
11
+ - `request.origin`
12
+ - `request.base_url`
13
+ - `request.x_csrf_token`
14
+ - `params[:authenticity_token]`
15
+
16
+ # 0.2.3 (2021-09-02)
17
+
18
+ - JSON formatter now strips all ANSI 7-bit C1 escapes from strings
19
+ (fixes [#1](https://github.com/BerkeleyLibrary/logging/issues/1) properly)
20
+
21
+ # 0.2.2 (2021-09-02)
22
+
23
+ - JSON formatter now strips ANSI color escapes from strings
24
+ (fixes [#1](https://github.com/BerkeleyLibrary/logging/issues/1))
25
+
1
26
  # 0.2.1 (2021-08-19)
2
27
 
3
28
  - Recursively log error cause
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # BerkeleyLibrary::Logging
2
2
 
3
3
  [![Build Status](https://github.com/BerkeleyLibrary/logging/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/BerkeleyLibrary/logging/actions/workflows/build.yml)
4
- [![Gem Version](https://img.shields.io/gem/v/berkeley_library-logging.svg)](https://github.com/BerkeleyLibrary/logging/releases)
4
+ [![Gem Version](https://img.shields.io/gem/v/berkeley_library-logging.svg)](https://rubygems.org/gems/berkeley_library-logging/)
5
5
 
6
6
  Opinionated logging for UCB Library IT Rails applications.
7
7
 
data/Rakefile CHANGED
@@ -19,9 +19,11 @@ end
19
19
  # ------------------------------------------------------------
20
20
  # Custom tasks
21
21
 
22
- desc 'Remove artifacts directory'
22
+ desc 'Remove artifacts directory, except for .keep file'
23
23
  task :clean do
24
24
  FileUtils.rm_rf('artifacts')
25
+ FileUtils.mkdir('artifacts')
26
+ FileUtils.touch(File.join('artifacts', '.keep'))
25
27
  end
26
28
 
27
29
  desc 'Check test coverage, check code style, check gems for vulnerabilities'
data/artifacts/.keep ADDED
File without changes
@@ -20,20 +20,20 @@ Gem::Specification.new do |spec|
20
20
  spec.homepage = BerkeleyLibrary::Logging::ModuleInfo::HOMEPAGE
21
21
 
22
22
  spec.files = `git ls-files -z`.split("\x0")
23
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features|artifacts)/})
24
24
  spec.require_paths = ['lib']
25
25
 
26
26
  spec.required_ruby_version = ">= #{ruby_version}"
27
27
 
28
28
  spec.add_dependency 'activesupport', '~> 6.0'
29
29
  spec.add_dependency 'amazing_print', '~> 1.1'
30
+ spec.add_dependency 'colorize', '~> 0.8.1'
30
31
  spec.add_dependency 'lograge', '~> 0.11'
31
32
  spec.add_dependency 'ougai', '~> 1.8'
32
33
 
33
34
  spec.add_development_dependency 'brakeman', '~> 4.9'
34
35
  spec.add_development_dependency 'bundle-audit', '~> 0.1'
35
36
  spec.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
36
- spec.add_development_dependency 'colorize', '~> 0.8'
37
37
  spec.add_development_dependency 'dotenv', '~> 2.7'
38
38
  spec.add_development_dependency 'irb', '~> 1.2' # workaroundfor https://github.com/bundler/bundler/issues/6929
39
39
  spec.add_development_dependency 'listen', '>= 3.0.5', '< 3.2'
@@ -22,9 +22,13 @@ module BerkeleyLibrary
22
22
  def configure_lograge(config)
23
23
  return unless config.respond_to?(:lograge)
24
24
 
25
- config.lograge.tap do |lograge|
25
+ lograge_config = config.lograge
26
+
27
+ custom_options = Events.extract_data_for_lograge(lograge_config)
28
+
29
+ lograge_config.tap do |lograge|
26
30
  lograge.enabled = true
27
- lograge.custom_options = Events.extract_data_for_lograge
31
+ lograge.custom_options = custom_options
28
32
  lograge.formatter = Formatters.lograge_formatter
29
33
  end
30
34
  end
@@ -1,37 +1,93 @@
1
+ require 'berkeley_library/logging/safe_serializer'
2
+
1
3
  module BerkeleyLibrary
2
4
  module Logging
3
5
  module Events
6
+ LOGGED_REQUEST_ATTRIBUTES = %i[origin base_url x_csrf_token].freeze
7
+ LOGGED_SESSION_ATTRIBUTES = %i[session_id _csrf_token].freeze
8
+ LOGGED_PARAMETERS = [:authenticity_token].freeze
9
+ LOGGED_HEADERS = {
10
+ # yes, RFC 2616 uses a variant spelling for 'referrer', it's a known issue
11
+ # https://tools.ietf.org/html/rfc2616#section-14.36
12
+ referer: 'HTTP_REFERER',
13
+ request_id: 'action_dispatch.request_id',
14
+ remote_ip: 'action_dispatch.remote_ip',
15
+ remote_addr: 'REMOTE_ADDR',
16
+ x_forwarded_for: 'HTTP_X_FORWARDED_FOR',
17
+ forwarded: 'HTTP_FORWARDED' # RFC 7239
18
+ }.freeze
19
+
4
20
  class << self
5
- def extract_data_for_lograge
6
- ->(event) { extract_event_data(event) }
21
+
22
+ def extract_data_for_lograge(lograge_config)
23
+ verbose_session_logging = lograge_config.verbose_session_logging
24
+
25
+ ->(event) { extract_event_data(event.payload, verbose_session_logging) }
7
26
  end
8
27
 
9
28
  private
10
29
 
11
- def extract_event_data(event)
12
- event_data = { time: Time.now }
13
- extracted_headers = extract_headers(event)
14
- event_data.merge(extracted_headers)
30
+ def extract_event_data(payload, verbose_session_logging)
31
+ [
32
+ extract_headers(payload),
33
+ extract_request_attributes(payload),
34
+ extract_param_values(payload),
35
+ extract_session(payload, verbose_session_logging)
36
+ ].inject({ time: Time.now }) do |data, values|
37
+ clean_values = SafeSerializer.serialize(values)
38
+ data.merge(clean_values)
39
+ end
15
40
  end
16
41
 
17
- def extract_headers(event)
18
- return {} unless (headers = event.payload[:headers])
19
-
20
- extracted_headers = {
21
- # yes, RFC 2616 uses a variant spelling for 'referrer', it's a known issue
22
- # https://tools.ietf.org/html/rfc2616#section-14.36
23
- referer: headers['HTTP_REFERER'],
24
- request_id: headers['action_dispatch.request_id'],
25
- remote_ip: headers['action_dispatch.remote_ip'],
26
- remote_addr: headers['REMOTE_ADDR'],
27
- x_forwarded_for: headers['HTTP_X_FORWARDED_FOR'],
28
- forwarded: headers['HTTP_FORWARDED'] # RFC 7239
29
- }
30
-
31
- # Some of these 'headers' include recursive structures
32
- # that cause SystemStackErrors in JSON serialization,
33
- # so we convert them all to strings
34
- extracted_headers.transform_values(&:to_s)
42
+ def extract_param_values(payload)
43
+ return {} unless (params = payload[:params])
44
+
45
+ LOGGED_PARAMETERS.each_with_object({}) do |param, values|
46
+ next unless (param_val = params[param])
47
+
48
+ values[param] = param_val
49
+ end
50
+ end
51
+
52
+ def extract_request_attributes(payload)
53
+ return {} unless (request = payload[:request])
54
+
55
+ LOGGED_REQUEST_ATTRIBUTES.each_with_object({}) do |attr, values|
56
+ next unless request.respond_to?(attr)
57
+ next if (attr_val = request.send(attr)).nil?
58
+
59
+ values[attr] = attr_val
60
+ end
61
+ end
62
+
63
+ def extract_headers(payload)
64
+ return {} unless (headers = payload[:headers])
65
+
66
+ LOGGED_HEADERS.each_with_object({}) do |(key, header), values|
67
+ next unless (header_val = headers[header])
68
+
69
+ values[key] = header_val
70
+ end
71
+ end
72
+
73
+ def extract_session(payload, verbose_session_logging)
74
+ return {} unless (request = payload[:request])
75
+ return {} unless (session = request.session)
76
+ return {} unless session.respond_to?(:to_hash)
77
+
78
+ session_hash = session_hash(session, verbose_session_logging)
79
+
80
+ { session: session_hash }
81
+ end
82
+
83
+ def session_hash(session, verbose)
84
+ raw_session_hash = session.to_hash
85
+ return raw_session_hash if verbose
86
+
87
+ LOGGED_SESSION_ATTRIBUTES.filter_map do |attr|
88
+ attr_str = attr.to_s
89
+ [attr, raw_session_hash[attr_str]] if raw_session_hash.key?(attr_str)
90
+ end.to_h
35
91
  end
36
92
  end
37
93
  end
@@ -0,0 +1,15 @@
1
+ module BerkeleyLibrary
2
+ module Logging
3
+ module ExceptionSerializer
4
+ def serialize_exc(ex, serialized = Set.new)
5
+ raw_result = { name: ex.class.name, message: ex.message, stack: ex.backtrace }
6
+ raw_result.tap do |result|
7
+ next unless (cause = ex.cause)
8
+ next if (serialized << ex).include?(cause) # prevent circular references
9
+
10
+ result[:cause] = serialize_exc(cause, serialized)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,10 +1,15 @@
1
1
  require 'ougai'
2
+ require 'berkeley_library/logging/exception_serializer'
2
3
 
3
4
  module BerkeleyLibrary
4
5
  module Logging
5
6
  module Formatters
6
7
 
7
8
  class << self
9
+
10
+ # See https://stackoverflow.com/a/14693789/27358
11
+ ANSI_7C1_RE = %r{\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])}.freeze
12
+
8
13
  def new_json_formatter
9
14
  Bunyan.new
10
15
  end
@@ -23,26 +28,22 @@ module BerkeleyLibrary
23
28
 
24
29
  { msg: message }
25
30
  end
26
- end
27
-
28
- # ------------------------------------------------------------
29
- # Private helper classes
30
31
 
31
- module ErrorCauseSerializer
32
- def serialize_exc(ex, serialized = Set.new)
33
- super(ex).tap do |result|
34
- next unless (cause = ex.cause)
35
- next if (serialized << ex).include?(cause) # prevent circular references
32
+ def strip_ansi_escapes(message)
33
+ return unless message
34
+ return message.gsub(ANSI_7C1_RE, '') if message.is_a?(String)
35
+ return message.map { |v| strip_ansi_escapes(v) } if message.is_a?(Array)
36
+ return message.transform_values { |v| strip_ansi_escapes(v) } if message.is_a?(Hash)
36
37
 
37
- result[:cause] = serialize_exc(cause, serialized)
38
- end
38
+ message
39
39
  end
40
40
  end
41
41
 
42
- private_constant :ErrorCauseSerializer
42
+ # ------------------------------------------------------------
43
+ # Private helper classes
43
44
 
44
45
  class Readable < Ougai::Formatters::Readable
45
- include ErrorCauseSerializer
46
+ include ExceptionSerializer
46
47
 
47
48
  protected
48
49
 
@@ -58,9 +59,11 @@ module BerkeleyLibrary
58
59
  " #{err_hash[:name]} (#{err_hash[:message]}):".tap do |msg|
59
60
  next unless (stack = err_hash[:stack])
60
61
 
61
- msg << "\n"
62
- msg << (' ' * @trace_indent)
63
- msg << stack
62
+ trace_indent = (' ' * @trace_indent)
63
+ trace_separator = "\n#{trace_indent}"
64
+
65
+ msg << trace_separator
66
+ msg << stack.join(trace_separator)
64
67
 
65
68
  next unless (cause_hash = err_hash[:cause])
66
69
 
@@ -74,15 +77,16 @@ module BerkeleyLibrary
74
77
 
75
78
  class Bunyan < Ougai::Formatters::Bunyan
76
79
  include Ougai::Logging::Severity
77
- include ErrorCauseSerializer
80
+ include ExceptionSerializer
78
81
 
79
82
  def _call(severity, time, progname, data)
80
83
  original_data = Formatters.ensure_hash(data)
84
+ decolorized_data = Formatters.strip_ansi_escapes(original_data)
81
85
 
82
86
  # Ougai::Formatters::Bunyan replaces the human-readable severity string
83
87
  # with a numeric level, so we add it here as a separate attribute
84
88
  severity = ensure_human_readable(severity)
85
- merged_data = { severity: severity }.merge(original_data)
89
+ merged_data = { severity: severity }.merge(decolorized_data)
86
90
  super(severity, time, progname, merged_data)
87
91
  end
88
92
 
@@ -7,7 +7,7 @@ module BerkeleyLibrary
7
7
  SUMMARY = 'Opinionated Ruby/Rails logging for UC Berkeley Library'.freeze
8
8
  DESCRIPTION = 'A gem providing shared logging code for UC Berkeley Library gems and Rails applications'.freeze
9
9
  LICENSE = 'MIT'.freeze
10
- VERSION = '0.2.1'.freeze
10
+ VERSION = '0.2.5'.freeze
11
11
  HOMEPAGE = 'https://github.com/BerkeleyLibrary/logging'.freeze
12
12
 
13
13
  private_class_method :new
@@ -0,0 +1,77 @@
1
+ require 'time'
2
+ require 'berkeley_library/logging/exception_serializer'
3
+
4
+ module BerkeleyLibrary
5
+ module Logging
6
+ # Some of values include recursive structures
7
+ # that cause SystemStackErrors in JSON serialization,
8
+ # so we convert them all to strings
9
+ class SafeSerializer
10
+ include ExceptionSerializer
11
+
12
+ RAW_TYPES = [NilClass, FalseClass, TrueClass, Numeric, String, Symbol, Date, Time].freeze
13
+
14
+ def initialize(value)
15
+ @value = value
16
+ end
17
+
18
+ class << self
19
+ def serialize(value)
20
+ SafeSerializer.new(value).serialized_value
21
+ end
22
+
23
+ def placeholder_for(value)
24
+ "#<#{value.class}:#{value.object_id}> (recursive reference)"
25
+ end
26
+ end
27
+
28
+ def serialized_value
29
+ @serialized_value ||= serialize(@value)
30
+ end
31
+
32
+ private
33
+
34
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
35
+ def serialize(value)
36
+ return value if safe_raw_value?(value)
37
+ return SafeSerializer.placeholder_for(value) if serialized_values.include?(value)
38
+
39
+ serialized_values << value
40
+
41
+ return serialize_hash(value) if value.is_a?(Hash)
42
+ return serialize_hash(value.to_hash) if value.respond_to?(:to_hash)
43
+ return serialize_array(value) if value.is_a?(Array)
44
+ return serialize_array(value.to_ary) if value.respond_to?(:to_ary)
45
+ return serialize_exc(value, serialized_values) if value.is_a?(Exception)
46
+
47
+ value.to_s
48
+ end
49
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
50
+
51
+ def safe_raw_value?(value)
52
+ return true if rails_time_with_zone?(value)
53
+
54
+ RAW_TYPES.any? { |t| value.is_a?(t) }
55
+ end
56
+
57
+ def rails_time_with_zone?(value)
58
+ defined?(ActiveSupport::TimeWithZone) && value.is_a?(ActiveSupport::TimeWithZone)
59
+ end
60
+
61
+ def serialize_array(value)
62
+ value.map { |v| serialize(v) }
63
+ end
64
+
65
+ def serialize_hash(value)
66
+ value.each_with_object({}) do |(k, v), h|
67
+ k1, v1 = [k, v].map { |x| serialize(x) }
68
+ h[k1] = v1
69
+ end
70
+ end
71
+
72
+ def serialized_values
73
+ @serialized_values ||= Set.new
74
+ end
75
+ end
76
+ end
77
+ end
@@ -30,36 +30,106 @@ module BerkeleyLibrary
30
30
  expect(lograge.enabled).to eq(true)
31
31
  end
32
32
 
33
- it 'extracts request info from log events' do
34
- Configurator.configure(config)
35
- lograge = config.lograge
33
+ context 'events' do
34
+ let(:params) { { authenticity_token: '8675309' } }
35
+
36
+ let(:session_hash) do
37
+ {
38
+ 'session_id' => '17cd06b1b7cb9744e8fa626ef5f37c67',
39
+ 'user' => {
40
+ 'id' => 71,
41
+ 'user_name' => 'Ms. Magoo',
42
+ 'created_at' => '2021-10-14T09:24:42.730-07:00',
43
+ 'updated_at' => '2021-10-14T09:24:42.729-07:00',
44
+ 'user_role' => 'Administrator',
45
+ 'user_active' => true,
46
+ 'uid' => 1_684_944,
47
+ 'updated_by' => 'Dr. Pibb'
48
+ },
49
+ 'expires_at' => '2021-11-03T12:30:01.281-07:00',
50
+ '_csrf_token' => 'HiN1xUxFcOvWvoe2nwoBSGlmGSN6x0jprpSqDrzquxA='
51
+ }
52
+ end
53
+
54
+ let(:request_headers) do
55
+ {
56
+ 'HTTP_REFERER' => 'value from HTTP_REFERER',
57
+ 'action_dispatch.request_id' => 'value from action_dispatch.request_id',
58
+ 'action_dispatch.remote_ip' => 'value from action_dispatch.remote_ip',
59
+ 'REMOTE_ADDR' => 'value from REMOTE_ADDR',
60
+ 'HTTP_X_FORWARDED_FOR' => 'value from HTTP_X_FORWARDED_FOR',
61
+ 'HTTP_FORWARDED' => 'value from HTTP_FORWARDED'
62
+ }
63
+ end
64
+
65
+ let(:expected_header_map) do
66
+ {
67
+ referer: 'HTTP_REFERER',
68
+ request_id: 'action_dispatch.request_id',
69
+ remote_ip: 'action_dispatch.remote_ip',
70
+ remote_addr: 'REMOTE_ADDR',
71
+ x_forwarded_for: 'HTTP_X_FORWARDED_FOR',
72
+ forwarded: 'HTTP_FORWARDED'
73
+ }
74
+ end
75
+
76
+ attr_reader :session, :request, :payload, :event
77
+
78
+ before(:each) do
79
+ @session = instance_double(ActionDispatch::Request::Session)
80
+ allow(session).to receive(:to_hash).and_return(session_hash)
81
+
82
+ @request = instance_double(ActionDispatch::Request)
83
+ allow(request).to receive(:origin).and_return('http://example.org:3000')
84
+ allow(request).to receive(:base_url).and_return('https://example.org:3443')
85
+ allow(request).to receive(:x_csrf_token).and_return('5551212')
86
+ allow(request).to receive(:session).and_return(session)
87
+
88
+ @payload = {
89
+ params: params,
90
+ request: request,
91
+ headers: request_headers
92
+ }
93
+
94
+ @event = instance_double(ActiveSupport::Notifications::Event)
95
+ allow(event).to receive(:payload).and_return(payload)
96
+ end
97
+
98
+ it 'extracts request info from log events' do
99
+ Configurator.configure(config)
100
+ data = config.lograge.custom_options.call(event)
36
101
 
37
- request_headers = {
38
- 'HTTP_REFERER' => 'value from HTTP_REFERER',
39
- 'action_dispatch.request_id' => 'value from action_dispatch.request_id',
40
- 'action_dispatch.remote_ip' => 'value from action_dispatch.remote_ip',
41
- 'REMOTE_ADDR' => 'value from REMOTE_ADDR',
42
- 'HTTP_X_FORWARDED_FOR' => 'value from HTTP_X_FORWARDED_FOR',
43
- 'HTTP_FORWARDED' => 'value from HTTP_FORWARDED'
44
- }
45
-
46
- expected_header_map = {
47
- referer: 'HTTP_REFERER',
48
- request_id: 'action_dispatch.request_id',
49
- remote_ip: 'action_dispatch.remote_ip',
50
- remote_addr: 'REMOTE_ADDR',
51
- x_forwarded_for: 'HTTP_X_FORWARDED_FOR',
52
- forwarded: 'HTTP_FORWARDED'
53
- }
54
-
55
- event = instance_double(ActiveSupport::Notifications::Event)
56
- allow(event).to receive(:payload).and_return({ headers: request_headers })
57
-
58
- custom_options = lograge.custom_options
59
- data = custom_options.call(event)
60
- expect(data).to be_a(Hash)
61
- expect(data[:time]).to be_a(Time) # TODO: check for accuracy
62
- expected_header_map.each { |xh, rh| expect(data[xh]).to eq(request_headers[rh]) }
102
+ expect(data).to be_a(Hash)
103
+ expect(data[:time]).to be_a(Time)
104
+ expect(data[:time].to_i).to be_within(60).of(Time.now.to_i)
105
+
106
+ expected_header_map.each { |xh, rh| expect(data[xh]).to eq(request_headers[rh]) }
107
+
108
+ expect(data[:authenticity_token]).to eq(params[:authenticity_token])
109
+
110
+ Events::LOGGED_REQUEST_ATTRIBUTES.each do |attr|
111
+ expect(data[attr]).to eq(request.send(attr))
112
+ end
113
+ end
114
+
115
+ it 'includes the entire session if `verbose_session_logging` is true' do
116
+ config.lograge.verbose_session_logging = true
117
+ Configurator.configure(config)
118
+ data = config.lograge.custom_options.call(event)
119
+
120
+ expect(data[:session]).to eq(session_hash)
121
+ end
122
+
123
+ it 'includes only the session ID by default' do
124
+ Configurator.configure(config)
125
+ data = config.lograge.custom_options.call(event)
126
+
127
+ expected_hash = Events::LOGGED_SESSION_ATTRIBUTES.each_with_object({}) do |attr, h|
128
+ h[attr] = session_hash[attr.to_s]
129
+ end
130
+
131
+ expect(data[:session]).to eq(expected_hash)
132
+ end
63
133
  end
64
134
 
65
135
  it 'formats Lograge data as a hash' do
@@ -1,27 +1,81 @@
1
1
  require 'rails_helper'
2
2
  require 'json'
3
+ require 'colorize'
3
4
  require 'berkeley_library/logging'
4
5
 
5
6
  module BerkeleyLibrary
6
7
  module Logging
7
8
  describe Formatters do
8
9
  describe :new_json_formatter do
9
- it 'supports tagged logging' do
10
- out = StringIO.new
11
- logger = Logger.new(out)
10
+ attr_reader :out, :logger
11
+
12
+ before(:each) do
13
+ @out = StringIO.new
14
+ @logger = Logger.new(out)
12
15
  logger.formatter = Formatters.new_json_formatter
16
+ end
13
17
 
14
- logger = ActiveSupport::TaggedLogging.new(logger)
18
+ it 'supports tagged logging' do
19
+ tagged_logger = ActiveSupport::TaggedLogging.new(logger)
15
20
 
16
21
  expected_tag = 'hello'
17
22
  expected_msg = 'this is a test'
18
23
 
19
- logger.tagged(expected_tag) { logger.info(expected_msg) }
24
+ tagged_logger.tagged(expected_tag) { tagged_logger.info(expected_msg) }
20
25
 
21
26
  logged_json = JSON.parse(out.string)
22
27
  expect(logged_json['msg']).to eq(expected_msg)
23
28
  expect(logged_json['tags']).to eq([expected_tag])
24
29
  end
30
+
31
+ it 'decolorizes ANSI-colored strings' do
32
+ colors = %i[red green yellow blue magenta cyan]
33
+ colorized_string = colors.map { |c| c.to_s.colorize(c) }.join(' ')
34
+ expect(colorized_string).to include("\u001b") # just to be sure
35
+
36
+ expected_string = colors.map(&:to_s).join(' ')
37
+
38
+ logger.info(colorized_string)
39
+ logged_json = JSON.parse(out.string)
40
+ msg = logged_json['msg']
41
+ expect(msg).not_to include("\u001b")
42
+ expect(msg).to eq(expected_string)
43
+ end
44
+
45
+ it 'decolorizes ANSI-colored strings in attached data' do
46
+ colors = %i[red green yellow blue magenta cyan]
47
+ colorized_string = colors.map { |c| c.to_s.colorize(c) }.join(' ')
48
+ expect(colorized_string).to include("\u001b") # just to be sure
49
+
50
+ expected_string = colors.map(&:to_s).join(' ')
51
+
52
+ data = {
53
+ the_string: colorized_string,
54
+ additional_data: {
55
+ not_a_string: 12,
56
+ another_string: colorized_string,
57
+ more_strings: [colorized_string, colorized_string]
58
+ }
59
+ }
60
+ logger.info('a colorized string', data)
61
+
62
+ logged_json = JSON.parse(out.string)
63
+ data = logged_json
64
+ expect(data['the_string']).to eq(expected_string)
65
+ additional_data = data['additional_data']
66
+ expect(additional_data['not_a_string']).to eq(12)
67
+ expect(additional_data['another_string']).to eq(expected_string)
68
+ expect(additional_data['more_strings']).to eq([expected_string, expected_string])
69
+ end
70
+
71
+ it 'removes ANSI formatting from ActiveRecord logs' do
72
+ original = " \e[1m\e[36mLendingItem Load (2.0ms)\e[0m \e[1m\e[34mSELECT \"lending_items\".* FROM \"lending_items\" WHERE \"lending_items\".\"directory\" = $1 LIMIT $2\e[0m [[\"directory\", \"b135297126_C068087930\"], [\"LIMIT\", 1]]"
73
+ expected = ' LendingItem Load (2.0ms) SELECT "lending_items".* FROM "lending_items" WHERE "lending_items"."directory" = $1 LIMIT $2 [["directory", "b135297126_C068087930"], ["LIMIT", 1]]'
74
+ logger.info(original)
75
+ logged_json = JSON.parse(out.string)
76
+ msg = logged_json['msg']
77
+ expect(msg).to eq(expected)
78
+ end
25
79
  end
26
80
 
27
81
  describe :ensure_hash do
@@ -14,6 +14,20 @@ module BerkeleyLibrary
14
14
  Rails.env = orig_rails_env
15
15
  end
16
16
 
17
+ describe :new_readable_logger do
18
+ it 'logs ANSI colors' do
19
+ out = StringIO.new
20
+
21
+ logger = Loggers.new_readable_logger(out)
22
+ colors = %i[red green yellow blue magenta cyan]
23
+ colorized_string = colors.map { |c| c.to_s.colorize(c) }.join(' ')
24
+ expect(colorized_string).to include("\u001b") # just to be sure
25
+
26
+ logger.info(colorized_string)
27
+ expect(out.string).to include(colorized_string)
28
+ end
29
+ end
30
+
17
31
  describe :new_json_logger do
18
32
  it 'supports tagged logging' do
19
33
  out = StringIO.new
@@ -0,0 +1,23 @@
1
+ require 'rails_helper'
2
+
3
+ require 'active_support/time'
4
+
5
+ module BerkeleyLibrary
6
+ module Logging
7
+ describe SafeSerializer do
8
+ describe :serialize do
9
+ it 'handles ActiveSupport::TimeWithZone' do
10
+ Time.zone = 'America/Los_Angeles'
11
+
12
+ t = Time.current
13
+ expect(t).to be_a(ActiveSupport::TimeWithZone) # just to be sure
14
+
15
+ expect(SafeSerializer.serialize(t)).to be(t)
16
+
17
+ h = { time: t }
18
+ expect(SafeSerializer.serialize(h)).to eq(h)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,27 +1,79 @@
1
1
  require 'standalone_helper'
2
2
  require 'json'
3
+ require 'colorize'
3
4
  require 'berkeley_library/logging'
4
5
 
5
6
  module BerkeleyLibrary
6
7
  module Logging
7
8
  describe Formatters do
8
9
  describe :new_json_formatter do
9
- it 'supports tagged logging' do
10
- out = StringIO.new
11
- logger = Logger.new(out)
10
+ attr_reader :out, :logger
11
+
12
+ before(:each) do
13
+ @out = StringIO.new
14
+ @logger = Logger.new(out)
12
15
  logger.formatter = Formatters.new_json_formatter
16
+ end
13
17
 
14
- logger = ActiveSupport::TaggedLogging.new(logger)
18
+ it 'supports tagged logging' do
19
+ tagged_logger = ActiveSupport::TaggedLogging.new(logger)
15
20
 
16
21
  expected_tag = 'hello'
17
22
  expected_msg = 'this is a test'
18
23
 
19
- logger.tagged(expected_tag) { logger.info(expected_msg) }
24
+ tagged_logger.tagged(expected_tag) { tagged_logger.info(expected_msg) }
20
25
 
21
26
  logged_json = JSON.parse(out.string)
22
27
  expect(logged_json['msg']).to eq(expected_msg)
23
28
  expect(logged_json['tags']).to eq([expected_tag])
24
29
  end
30
+
31
+ it 'decolorizes ANSI-colored strings' do
32
+ colors = %i[red green yellow blue magenta cyan]
33
+ colorized_string = colors.map { |c| c.to_s.colorize(c) }.join(' ')
34
+ expect(colorized_string).to include("\u001b") # just to be sure
35
+
36
+ expected_string = colors.map(&:to_s).join(' ')
37
+
38
+ logger.info(colorized_string)
39
+ logged_json = JSON.parse(out.string)
40
+ msg = logged_json['msg']
41
+ expect(msg).not_to include("\u001b")
42
+ expect(msg).to eq(expected_string)
43
+ end
44
+
45
+ it 'decolorizes ANSI-colored strings in attached data' do
46
+ colors = %i[red green yellow blue magenta cyan]
47
+ colorized_string = colors.map { |c| c.to_s.colorize(c) }.join(' ')
48
+ expect(colorized_string).to include("\u001b") # just to be sure
49
+
50
+ expected_string = colors.map(&:to_s).join(' ')
51
+
52
+ data = {
53
+ the_string: colorized_string,
54
+ additional_data: {
55
+ another_string: colorized_string,
56
+ more_strings: [colorized_string, colorized_string]
57
+ }
58
+ }
59
+ logger.info('a colorized string', data)
60
+
61
+ logged_json = JSON.parse(out.string)
62
+ data = logged_json
63
+ expect(data['the_string']).to eq(expected_string)
64
+ additional_data = data['additional_data']
65
+ expect(additional_data['another_string']).to eq(expected_string)
66
+ expect(additional_data['more_strings']).to eq([expected_string, expected_string])
67
+ end
68
+
69
+ it 'removes ANSI formatting from ActiveRecord logs' do
70
+ original = " \e[1m\e[36mLendingItem Load (2.0ms)\e[0m \e[1m\e[34mSELECT \"lending_items\".* FROM \"lending_items\" WHERE \"lending_items\".\"directory\" = $1 LIMIT $2\e[0m [[\"directory\", \"b135297126_C068087930\"], [\"LIMIT\", 1]]"
71
+ expected = ' LendingItem Load (2.0ms) SELECT "lending_items".* FROM "lending_items" WHERE "lending_items"."directory" = $1 LIMIT $2 [["directory", "b135297126_C068087930"], ["LIMIT", 1]]'
72
+ logger.info(original)
73
+ logged_json = JSON.parse(out.string)
74
+ msg = logged_json['msg']
75
+ expect(msg).to eq(expected)
76
+ end
25
77
  end
26
78
 
27
79
  describe :ensure_hash do
@@ -1,5 +1,6 @@
1
1
  require 'standalone_helper'
2
2
  require 'json'
3
+ require 'colorize'
3
4
 
4
5
  module BerkeleyLibrary
5
6
  module Logging
@@ -23,6 +24,18 @@ module BerkeleyLibrary
23
24
  Object.send(:remove_const, :TestError)
24
25
  end
25
26
 
27
+ describe :new_readable_logger do
28
+ it 'logs ANSI colors' do
29
+ logger = Loggers.new_readable_logger(out)
30
+ colors = %i[red green yellow blue magenta cyan]
31
+ colorized_string = colors.map { |c| c.to_s.colorize(c) }.join(' ')
32
+ expect(colorized_string).to include("\u001b") # just to be sure
33
+
34
+ logger.info(colorized_string)
35
+ expect(out.string).to include(colorized_string)
36
+ end
37
+ end
38
+
26
39
  describe :new_json_logger do
27
40
 
28
41
  # TODO: rewrite this as a matcher
@@ -114,7 +127,14 @@ module BerkeleyLibrary
114
127
  end
115
128
  end
116
129
 
117
- expect { Loggers.new_json_logger(out).error(ex_outer) }.not_to raise_error(SystemStackError)
130
+ begin
131
+ Loggers.new_json_logger(out).error(ex_outer)
132
+ rescue SystemStackError => e
133
+ RSpec::Expectations.fail_with("Expected no SystemStackError, but got #{e}: #{e.backtrace[0]}")
134
+ end
135
+
136
+ expect(out.string).to include(msg_outer)
137
+ expect(out.string).to include(msg_inner)
118
138
  end
119
139
  # rubocop:enable Naming/RescuedExceptionsVariableName
120
140
 
@@ -0,0 +1,137 @@
1
+ require 'standalone_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module Logging
5
+ describe SafeSerializer do
6
+ describe :serialize do
7
+ it 'returns primitives unchanged' do
8
+ values = [
9
+ nil,
10
+ false,
11
+ true,
12
+ 1,
13
+ 1.0,
14
+ '1/3'.to_r,
15
+ 'a string',
16
+ :a_symbol,
17
+ Date.today,
18
+ Time.now
19
+ ]
20
+
21
+ aggregate_failures do
22
+ values.each do |original|
23
+ actual = SafeSerializer.serialize(original)
24
+ expect(actual).to be(original)
25
+ end
26
+ end
27
+ end
28
+
29
+ it 'returns an object as a string' do
30
+ value = Object.new
31
+ expect(SafeSerializer.serialize(value)).to eq(value.to_s)
32
+ end
33
+
34
+ it 'returns a hash as a hash' do
35
+ hash = { a: 1, b: 2 }
36
+ expect(SafeSerializer.serialize(hash)).to eq(hash)
37
+ end
38
+
39
+ it 'returns an array as an array' do
40
+ arr = [0, 1, 2, 'elvis', false]
41
+ expect(SafeSerializer.serialize(arr)).to eq(arr)
42
+ end
43
+
44
+ it 'cleans nested values' do
45
+ b_value = Object.new
46
+ c_key = Object.new
47
+ d_value = Object.new
48
+
49
+ d_array = ['d', :d, { d: "\ud7ff", 'd' => d_value }]
50
+
51
+ h = {
52
+ a: 1,
53
+ b: b_value,
54
+ c_key => 'c value',
55
+ 0xd => d_array,
56
+ "\uEEEE" => 0xe
57
+ }
58
+
59
+ expected = {
60
+ a: 1,
61
+ b: b_value.to_s,
62
+ c_key.to_s => 'c value',
63
+ 0xd => ['d', :d, { d: "\ud7ff", 'd' => d_value.to_s }],
64
+ "\uEEEE" => 0xe
65
+ }
66
+
67
+ actual = SafeSerializer.serialize(h)
68
+ expect(actual).to eq(expected)
69
+ end
70
+
71
+ it 'handles recursive structures' do
72
+ a = []
73
+ h = { a: a }
74
+ a << h
75
+
76
+ h_expected = { a: [SafeSerializer.placeholder_for(h)] }
77
+ a_expected = [{ a: SafeSerializer.placeholder_for(a) }]
78
+
79
+ expect(SafeSerializer.serialize(h)).to eq(h_expected)
80
+ expect(SafeSerializer.serialize(a)).to eq(a_expected)
81
+ end
82
+
83
+ context 'exceptions' do
84
+ # rubocop:disable Lint/ConstantDefinitionInBlock
85
+ before(:each) do
86
+ class ::TestError < StandardError
87
+ attr_writer :cause
88
+
89
+ def cause
90
+ @cause || super
91
+ end
92
+ end
93
+ end
94
+ # rubocop:enable Lint/ConstantDefinitionInBlock
95
+
96
+ after(:each) do
97
+ Object.send(:remove_const, :TestError)
98
+ end
99
+
100
+ # rubocop:disable Naming/RescuedExceptionsVariableName
101
+ it 'handles exceptions' do
102
+ msg_outer = 'Help I am trapped in the outer part of a unit test'
103
+ msg_inner = 'Help I am trapped in the inner part of a unit test'
104
+
105
+ begin
106
+ raise TestError, msg_inner
107
+ rescue TestError => ex_inner
108
+ begin
109
+ raise TestError, msg_outer
110
+ rescue TestError => ex_outer
111
+ ex_inner.cause = ex_outer
112
+ end
113
+ end
114
+
115
+ expect(ex_outer.cause).to eq(ex_inner) # just to be sure
116
+ expect(ex_inner.cause).to eq(ex_outer) # just to be sure
117
+
118
+ expected = {
119
+ name: 'TestError',
120
+ message: msg_outer,
121
+ stack: ex_outer.backtrace,
122
+ cause: {
123
+ name: 'TestError',
124
+ message: msg_inner,
125
+ stack: ex_inner.backtrace
126
+ }
127
+ }
128
+
129
+ expect(SafeSerializer.serialize(ex_outer)).to eq(expected)
130
+ end
131
+ # rubocop:enable Naming/RescuedExceptionsVariableName
132
+
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berkeley_library-logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Moles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-19 00:00:00.000000000 Z
11
+ date: 2021-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.1
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: lograge
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +122,6 @@ dependencies:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
124
  version: '1.0'
111
- - !ruby/object:Gem::Dependency
112
- name: colorize
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '0.8'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '0.8'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: dotenv
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -290,17 +290,20 @@ files:
290
290
  - LICENSE.md
291
291
  - README.md
292
292
  - Rakefile
293
+ - artifacts/.keep
293
294
  - berkeley_library-logging.gemspec
294
295
  - docker-compose.yml
295
296
  - lib/berkeley_library/logging.rb
296
297
  - lib/berkeley_library/logging/configurator.rb
297
298
  - lib/berkeley_library/logging/env.rb
298
299
  - lib/berkeley_library/logging/events.rb
300
+ - lib/berkeley_library/logging/exception_serializer.rb
299
301
  - lib/berkeley_library/logging/formatters.rb
300
302
  - lib/berkeley_library/logging/logger.rb
301
303
  - lib/berkeley_library/logging/loggers.rb
302
304
  - lib/berkeley_library/logging/module_info.rb
303
305
  - lib/berkeley_library/logging/railtie.rb
306
+ - lib/berkeley_library/logging/safe_serializer.rb
304
307
  - lib/berkeley_library/logging/tagged_logging_extensions.rb
305
308
  - rakelib/.rubocop.yml
306
309
  - rakelib/bundle.rake
@@ -314,12 +317,14 @@ files:
314
317
  - spec/rails/ucblit/logging/formatters_spec.rb
315
318
  - spec/rails/ucblit/logging/loggers_spec.rb
316
319
  - spec/rails/ucblit/logging/railtie_spec.rb
320
+ - spec/rails/ucblit/logging/safe_serializer_spec.rb
317
321
  - spec/rails/ucblit/logging_spec.rb
318
322
  - spec/rails_helper.rb
319
323
  - spec/spec_helper.rb
320
324
  - spec/standalone/ucblit/logging/configurator_spec.rb
321
325
  - spec/standalone/ucblit/logging/formatters_spec.rb
322
326
  - spec/standalone/ucblit/logging/loggers_spec.rb
327
+ - spec/standalone/ucblit/logging/safe_serializer_spec.rb
323
328
  - spec/standalone/ucblit/logging_spec.rb
324
329
  - spec/standalone_helper.rb
325
330
  homepage: https://github.com/BerkeleyLibrary/logging
@@ -346,17 +351,20 @@ signing_key:
346
351
  specification_version: 4
347
352
  summary: Opinionated Ruby/Rails logging for UC Berkeley Library
348
353
  test_files:
354
+ - artifacts/.keep
349
355
  - spec/.rubocop.yml
350
356
  - spec/rails/ucblit/logging/configurator_spec.rb
351
357
  - spec/rails/ucblit/logging/env_spec.rb
352
358
  - spec/rails/ucblit/logging/formatters_spec.rb
353
359
  - spec/rails/ucblit/logging/loggers_spec.rb
354
360
  - spec/rails/ucblit/logging/railtie_spec.rb
361
+ - spec/rails/ucblit/logging/safe_serializer_spec.rb
355
362
  - spec/rails/ucblit/logging_spec.rb
356
363
  - spec/rails_helper.rb
357
364
  - spec/spec_helper.rb
358
365
  - spec/standalone/ucblit/logging/configurator_spec.rb
359
366
  - spec/standalone/ucblit/logging/formatters_spec.rb
360
367
  - spec/standalone/ucblit/logging/loggers_spec.rb
368
+ - spec/standalone/ucblit/logging/safe_serializer_spec.rb
361
369
  - spec/standalone/ucblit/logging_spec.rb
362
370
  - spec/standalone_helper.rb