berkeley_library-logging 0.2.4 → 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: 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