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 +4 -4
- data/.idea/inspectionProfiles/Project_Default.xml +2 -1
- data/.idea/logging.iml +8 -8
- data/CHANGES.md +5 -0
- data/Rakefile +3 -1
- data/berkeley_library-logging.gemspec +1 -1
- data/lib/berkeley_library/logging/configurator.rb +6 -2
- data/lib/berkeley_library/logging/events.rb +58 -36
- data/lib/berkeley_library/logging/exception_serializer.rb +15 -0
- data/lib/berkeley_library/logging/formatters.rb +8 -18
- data/lib/berkeley_library/logging/module_info.rb +1 -1
- data/lib/berkeley_library/logging/safe_serializer.rb +77 -0
- data/spec/rails/ucblit/logging/configurator_spec.rb +98 -46
- data/spec/rails/ucblit/logging/safe_serializer_spec.rb +23 -0
- data/spec/standalone/ucblit/logging/loggers_spec.rb +3 -0
- data/spec/standalone/ucblit/logging/safe_serializer_spec.rb +137 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cafd11d3f9d11029ae88c9ed475e08d9c5227d56f2924085145e6363fe76bfc1
|
|
4
|
+
data.tar.gz: 42eb37cd205ee71cbe710d86ddafd0a342ca31b3f54b40d988b16d5786ef6335
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
22
|
+
def extract_data_for_lograge(lograge_config)
|
|
23
|
+
verbose_session_logging = lograge_config.verbose_session_logging
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
30
|
-
event_data.merge!(request_attributes)
|
|
28
|
+
private
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
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(
|
|
38
|
-
return {} unless (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(
|
|
48
|
-
return {} unless (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
|
|
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(
|
|
59
|
-
return {} unless (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
|
-
|
|
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
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
+
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-
|
|
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
|