berkeley_library-logging 0.2.4 → 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: 91d0f1a398e99a9e138f90946025b7d5576cda6a4b6e4e8c137b77ec8febbd2f
4
- data.tar.gz: 1300926d8af70a24e6fff33e3ac1e8af79c428e7c35302fab335d255f713d7a1
3
+ metadata.gz: cafd11d3f9d11029ae88c9ed475e08d9c5227d56f2924085145e6363fe76bfc1
4
+ data.tar.gz: 42eb37cd205ee71cbe710d86ddafd0a342ca31b3f54b40d988b16d5786ef6335
5
5
  SHA512:
6
- metadata.gz: 2271582ebc293b95a3f9dc18c9c922b780abf1ab1636f549f9ac1093a4b72dabd8a6c224bd51e53be7b128b0f65759dd639bef744f7586659b98fcbd4eb19431
7
- data.tar.gz: 874db3c599bfa7a1a00d9b15c35bc85120f1ed573ba5088c99c83bc12ef81ea1ab8e5c2fd081996c6ac684a0c2befc573de57547c951111b6e9e41c726847e64
6
+ metadata.gz: bc1a877b7dd123c92aa2cbfdcb8f55676bb6ef2257857f4af395e19c2d27fb950e3fcbf69813a0a639dd372f50a711e3b5cc84dfd593aa67dd5035534447a339
7
+ data.tar.gz: 2fc6bbf11a1d51aa90a15d0841df1c49a38aad8add0a824e1dfcab0bfe4d3489bb6a40d8afe56e3da255d6caac62d68535acfd726f4673604fb1548f5c71d0d0
@@ -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,11 +4,7 @@
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
10
  <orderEntry type="library" scope="PROVIDED" name="actioncable (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -22,7 +18,7 @@
22
18
  <orderEntry type="library" scope="PROVIDED" name="activerecord (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
23
19
  <orderEntry type="library" scope="PROVIDED" name="activestorage (v6.1.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
24
20
  <orderEntry type="library" scope="PROVIDED" name="activesupport (v6.1.4.1, 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" />
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" />
@@ -54,7 +50,7 @@
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
52
  <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.12.4, RVM: ruby-2.7.4) [gem]" level="application" />
57
- <orderEntry type="library" scope="PROVIDED" name="oj (v3.13.7, 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
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" />
@@ -97,6 +93,10 @@
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.3.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/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
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
+
1
6
  # 0.2.4 (2021-11-02)
2
7
 
3
8
  - Rails event logs now include the following, in addition to the
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'
@@ -20,7 +20,7 @@ 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}"
@@ -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,41 +1,46 @@
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
- LOGGED_REQUEST_ATTRIBUTES = %i[origin base_url x_csrf_token].freeze
6
- LOGGED_PARAMETERS = [:authenticity_token].freeze
7
- LOGGED_HEADERS = {
8
- # yes, RFC 2616 uses a variant spelling for 'referrer', it's a known issue
9
- # https://tools.ietf.org/html/rfc2616#section-14.36
10
- referer: 'HTTP_REFERER',
11
- request_id: 'action_dispatch.request_id',
12
- remote_ip: 'action_dispatch.remote_ip',
13
- remote_addr: 'REMOTE_ADDR',
14
- x_forwarded_for: 'HTTP_X_FORWARDED_FOR',
15
- forwarded: 'HTTP_FORWARDED' # RFC 7239
16
- }.freeze
17
-
18
- def extract_data_for_lograge
19
- ->(event) { extract_event_data(event) }
20
- end
21
21
 
22
- private
22
+ def extract_data_for_lograge(lograge_config)
23
+ verbose_session_logging = lograge_config.verbose_session_logging
23
24
 
24
- def extract_event_data(event)
25
- { time: Time.now }.tap do |event_data|
26
- headers = extract_headers(event)
27
- event_data.merge!(headers)
25
+ ->(event) { extract_event_data(event.payload, verbose_session_logging) }
26
+ end
28
27
 
29
- request_attributes = extract_request_attributes(event)
30
- event_data.merge!(request_attributes)
28
+ private
31
29
 
32
- param_values = extract_param_values(event)
33
- event_data.merge!(param_values)
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)
34
39
  end
35
40
  end
36
41
 
37
- def extract_param_values(event)
38
- return {} unless (params = event.payload[:params])
42
+ def extract_param_values(payload)
43
+ return {} unless (params = payload[:params])
39
44
 
40
45
  LOGGED_PARAMETERS.each_with_object({}) do |param, values|
41
46
  next unless (param_val = params[param])
@@ -44,29 +49,46 @@ module BerkeleyLibrary
44
49
  end
45
50
  end
46
51
 
47
- def extract_request_attributes(event)
48
- return {} unless (request = event.payload[:request])
52
+ def extract_request_attributes(payload)
53
+ return {} unless (request = payload[:request])
49
54
 
50
55
  LOGGED_REQUEST_ATTRIBUTES.each_with_object({}) do |attr, values|
51
56
  next unless request.respond_to?(attr)
52
- next unless (attr_val = request.send(attr))
57
+ next if (attr_val = request.send(attr)).nil?
53
58
 
54
59
  values[attr] = attr_val
55
60
  end
56
61
  end
57
62
 
58
- def extract_headers(event)
59
- return {} unless (headers = event.payload[:headers])
63
+ def extract_headers(payload)
64
+ return {} unless (headers = payload[:headers])
60
65
 
61
66
  LOGGED_HEADERS.each_with_object({}) do |(key, header), values|
62
67
  next unless (header_val = headers[header])
63
68
 
64
- # Some of these 'headers' include recursive structures
65
- # that cause SystemStackErrors in JSON serialization,
66
- # so we convert them all to strings
67
- values[key] = header_val.to_s
69
+ values[key] = header_val
68
70
  end
69
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
91
+ end
70
92
  end
71
93
  end
72
94
  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,4 +1,5 @@
1
1
  require 'ougai'
2
+ require 'berkeley_library/logging/exception_serializer'
2
3
 
3
4
  module BerkeleyLibrary
4
5
  module Logging
@@ -41,21 +42,8 @@ module BerkeleyLibrary
41
42
  # ------------------------------------------------------------
42
43
  # Private helper classes
43
44
 
44
- module ErrorCauseSerializer
45
- def serialize_exc(ex, serialized = Set.new)
46
- super(ex).tap do |result|
47
- next unless (cause = ex.cause)
48
- next if (serialized << ex).include?(cause) # prevent circular references
49
-
50
- result[:cause] = serialize_exc(cause, serialized)
51
- end
52
- end
53
- end
54
-
55
- private_constant :ErrorCauseSerializer
56
-
57
45
  class Readable < Ougai::Formatters::Readable
58
- include ErrorCauseSerializer
46
+ include ExceptionSerializer
59
47
 
60
48
  protected
61
49
 
@@ -71,9 +59,11 @@ module BerkeleyLibrary
71
59
  " #{err_hash[:name]} (#{err_hash[:message]}):".tap do |msg|
72
60
  next unless (stack = err_hash[:stack])
73
61
 
74
- msg << "\n"
75
- msg << (' ' * @trace_indent)
76
- msg << stack
62
+ trace_indent = (' ' * @trace_indent)
63
+ trace_separator = "\n#{trace_indent}"
64
+
65
+ msg << trace_separator
66
+ msg << stack.join(trace_separator)
77
67
 
78
68
  next unless (cause_hash = err_hash[:cause])
79
69
 
@@ -87,7 +77,7 @@ module BerkeleyLibrary
87
77
 
88
78
  class Bunyan < Ougai::Formatters::Bunyan
89
79
  include Ougai::Logging::Severity
90
- include ErrorCauseSerializer
80
+ include ExceptionSerializer
91
81
 
92
82
  def _call(severity, time, progname, data)
93
83
  original_data = Formatters.ensure_hash(data)
@@ -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.4'.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,53 +30,105 @@ 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)
101
+
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
36
130
 
37
- params = { authenticity_token: '8675309' }
38
- request = OpenStruct.new(
39
- origin: 'http://example.org:3000',
40
- base_url: 'https://example.org:3443',
41
- x_csrf_token: '5551212'
42
- )
43
-
44
- request_headers = {
45
- 'HTTP_REFERER' => 'value from HTTP_REFERER',
46
- 'action_dispatch.request_id' => 'value from action_dispatch.request_id',
47
- 'action_dispatch.remote_ip' => 'value from action_dispatch.remote_ip',
48
- 'REMOTE_ADDR' => 'value from REMOTE_ADDR',
49
- 'HTTP_X_FORWARDED_FOR' => 'value from HTTP_X_FORWARDED_FOR',
50
- 'HTTP_FORWARDED' => 'value from HTTP_FORWARDED'
51
- }
52
-
53
- expected_header_map = {
54
- referer: 'HTTP_REFERER',
55
- request_id: 'action_dispatch.request_id',
56
- remote_ip: 'action_dispatch.remote_ip',
57
- remote_addr: 'REMOTE_ADDR',
58
- x_forwarded_for: 'HTTP_X_FORWARDED_FOR',
59
- forwarded: 'HTTP_FORWARDED'
60
- }
61
-
62
- payload = {
63
- params: params,
64
- request: request,
65
- headers: request_headers
66
- }
67
-
68
- event = instance_double(ActiveSupport::Notifications::Event)
69
- allow(event).to receive(:payload).and_return(payload)
70
-
71
- custom_options = lograge.custom_options
72
- data = custom_options.call(event)
73
- expect(data).to be_a(Hash)
74
- expect(data[:time]).to be_a(Time)
75
- expect(data[:time].to_i).to be_within(60).of(Time.now.to_i)
76
- expected_header_map.each { |xh, rh| expect(data[xh]).to eq(request_headers[rh]) }
77
- expect(data[:authenticity_token]).to eq(params[:authenticity_token])
78
- %i[origin base_url x_csrf_token].each do |attr|
79
- expect(data[attr]).to eq(request.send(attr))
131
+ expect(data[:session]).to eq(expected_hash)
80
132
  end
81
133
  end
82
134
 
@@ -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
@@ -132,6 +132,9 @@ module BerkeleyLibrary
132
132
  rescue SystemStackError => e
133
133
  RSpec::Expectations.fail_with("Expected no SystemStackError, but got #{e}: #{e.backtrace[0]}")
134
134
  end
135
+
136
+ expect(out.string).to include(msg_outer)
137
+ expect(out.string).to include(msg_inner)
135
138
  end
136
139
  # rubocop:enable Naming/RescuedExceptionsVariableName
137
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.4
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-11-03 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
@@ -297,11 +297,13 @@ files:
297
297
  - lib/berkeley_library/logging/configurator.rb
298
298
  - lib/berkeley_library/logging/env.rb
299
299
  - lib/berkeley_library/logging/events.rb
300
+ - lib/berkeley_library/logging/exception_serializer.rb
300
301
  - lib/berkeley_library/logging/formatters.rb
301
302
  - lib/berkeley_library/logging/logger.rb
302
303
  - lib/berkeley_library/logging/loggers.rb
303
304
  - lib/berkeley_library/logging/module_info.rb
304
305
  - lib/berkeley_library/logging/railtie.rb
306
+ - lib/berkeley_library/logging/safe_serializer.rb
305
307
  - lib/berkeley_library/logging/tagged_logging_extensions.rb
306
308
  - rakelib/.rubocop.yml
307
309
  - rakelib/bundle.rake
@@ -315,12 +317,14 @@ files:
315
317
  - spec/rails/ucblit/logging/formatters_spec.rb
316
318
  - spec/rails/ucblit/logging/loggers_spec.rb
317
319
  - spec/rails/ucblit/logging/railtie_spec.rb
320
+ - spec/rails/ucblit/logging/safe_serializer_spec.rb
318
321
  - spec/rails/ucblit/logging_spec.rb
319
322
  - spec/rails_helper.rb
320
323
  - spec/spec_helper.rb
321
324
  - spec/standalone/ucblit/logging/configurator_spec.rb
322
325
  - spec/standalone/ucblit/logging/formatters_spec.rb
323
326
  - spec/standalone/ucblit/logging/loggers_spec.rb
327
+ - spec/standalone/ucblit/logging/safe_serializer_spec.rb
324
328
  - spec/standalone/ucblit/logging_spec.rb
325
329
  - spec/standalone_helper.rb
326
330
  homepage: https://github.com/BerkeleyLibrary/logging
@@ -347,17 +351,20 @@ signing_key:
347
351
  specification_version: 4
348
352
  summary: Opinionated Ruby/Rails logging for UC Berkeley Library
349
353
  test_files:
354
+ - artifacts/.keep
350
355
  - spec/.rubocop.yml
351
356
  - spec/rails/ucblit/logging/configurator_spec.rb
352
357
  - spec/rails/ucblit/logging/env_spec.rb
353
358
  - spec/rails/ucblit/logging/formatters_spec.rb
354
359
  - spec/rails/ucblit/logging/loggers_spec.rb
355
360
  - spec/rails/ucblit/logging/railtie_spec.rb
361
+ - spec/rails/ucblit/logging/safe_serializer_spec.rb
356
362
  - spec/rails/ucblit/logging_spec.rb
357
363
  - spec/rails_helper.rb
358
364
  - spec/spec_helper.rb
359
365
  - spec/standalone/ucblit/logging/configurator_spec.rb
360
366
  - spec/standalone/ucblit/logging/formatters_spec.rb
361
367
  - spec/standalone/ucblit/logging/loggers_spec.rb
368
+ - spec/standalone/ucblit/logging/safe_serializer_spec.rb
362
369
  - spec/standalone/ucblit/logging_spec.rb
363
370
  - spec/standalone_helper.rb