berkeley_library-logging 0.2.1 → 0.2.5

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
  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