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