console 1.19.0 → 1.29.0
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
- checksums.yaml.gz.sig +0 -0
- data/bake/console.rb +3 -3
- data/lib/console/adapter.rb +2 -1
- data/lib/console/capture.rb +46 -17
- data/lib/console/compatible/logger.rb +4 -3
- data/lib/console/event/failure.rb +43 -39
- data/lib/console/event/generic.rb +9 -6
- data/lib/console/event/spawn.rb +35 -27
- data/lib/console/event.rb +3 -4
- data/lib/console/filter.rb +24 -10
- data/lib/console/format/safe.rb +136 -0
- data/lib/console/format.rb +14 -0
- data/lib/console/interface.rb +53 -0
- data/lib/console/logger.rb +17 -16
- data/lib/console/output/default.rb +8 -5
- data/lib/console/output/failure.rb +32 -0
- data/lib/console/output/null.rb +5 -1
- data/lib/console/output/sensitive.rb +12 -10
- data/lib/console/{serialized/logger.rb → output/serialized.rb} +14 -49
- data/lib/console/output/split.rb +3 -3
- data/lib/console/{terminal/logger.rb → output/terminal.rb} +98 -54
- data/lib/console/output/wrapper.rb +28 -0
- data/lib/console/output.rb +7 -8
- data/lib/console/progress.rb +20 -9
- data/lib/console/resolver.rb +5 -5
- data/lib/console/terminal/formatter/failure.rb +57 -0
- data/lib/console/terminal/formatter/progress.rb +58 -0
- data/lib/console/terminal/formatter/spawn.rb +42 -0
- data/lib/console/terminal/text.rb +6 -2
- data/lib/console/terminal/xterm.rb +10 -3
- data/lib/console/terminal.rb +19 -2
- data/lib/console/version.rb +2 -2
- data/lib/console/warn.rb +33 -0
- data/lib/console.rb +4 -22
- data/license.md +5 -1
- data/readme.md +39 -2
- data/releases.md +25 -0
- data.tar.gz.sig +0 -0
- metadata +31 -92
- metadata.gz.sig +0 -0
- data/lib/console/buffer.rb +0 -25
- data/lib/console/event/progress.rb +0 -60
- data/lib/console/output/json.rb +0 -16
- data/lib/console/output/text.rb +0 -16
- data/lib/console/output/xterm.rb +0 -16
- data/lib/console/split.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fa166f5900ef3a9c90b337ce6dd57faa99527fd9746a47aa61bb7f7535e606d
|
4
|
+
data.tar.gz: c6d7d06fdd475006e000c3da6d65fdcd3a5c58e10c90afa8b380f795d45a16d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 620b134d260d72d3b69a57e455584d556d611aa8a441fbd7624722b1cfaf3541015335717b82da8ea2aa0682f94e2898c9f28c4162fa5b5842297035f81bd5c0
|
7
|
+
data.tar.gz: be9318b5f775a9bd33f5c8951ffae4b921dbe0618f209080645a3eb12dcd096ef092d94f1239564e03497961ebbfbbb926d43294b8b524a94486e6b62a835b1a
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/bake/console.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2020-
|
4
|
+
# Copyright, 2020-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
# Increase the verbosity of the logger to info.
|
7
7
|
def info
|
8
|
-
require_relative
|
8
|
+
require_relative "../lib/console"
|
9
9
|
|
10
10
|
Console.logger.info!
|
11
11
|
end
|
12
12
|
|
13
13
|
# Increase the verbosity of the logger to debug.
|
14
14
|
def debug
|
15
|
-
require_relative
|
15
|
+
require_relative "../lib/console"
|
16
16
|
|
17
17
|
Console.logger.debug!
|
18
18
|
end
|
data/lib/console/adapter.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2023, by Samuel Williams.
|
4
|
+
# Copyright, 2023-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
module Console
|
7
|
+
# This namespace is reserved for logging adapters provided by other gems.
|
7
8
|
module Adapter
|
8
9
|
end
|
9
10
|
end
|
data/lib/console/capture.rb
CHANGED
@@ -1,35 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "filter"
|
7
|
+
require_relative "output/failure"
|
7
8
|
|
8
9
|
module Console
|
9
10
|
# A general sink which captures all events into a buffer.
|
10
11
|
class Capture
|
11
12
|
def initialize
|
12
|
-
@
|
13
|
+
@records = []
|
13
14
|
@verbose = false
|
14
15
|
end
|
15
16
|
|
16
|
-
attr :
|
17
|
+
attr :records
|
18
|
+
|
19
|
+
# @deprecated Use {#records} instead of {#buffer}.
|
20
|
+
alias buffer records
|
21
|
+
|
22
|
+
alias to_a records
|
23
|
+
|
17
24
|
attr :verbose
|
18
25
|
|
19
|
-
def
|
20
|
-
@
|
26
|
+
def include?(pattern)
|
27
|
+
@records.any? do |record|
|
28
|
+
record[:subject].to_s&.match?(pattern) or record[:message].to_s&.match?(pattern)
|
29
|
+
end
|
21
30
|
end
|
22
31
|
|
23
|
-
def
|
24
|
-
|
32
|
+
def each(&block)
|
33
|
+
@records.each(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
include Enumerable
|
37
|
+
|
38
|
+
def first
|
39
|
+
@records.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def last
|
43
|
+
@records.last
|
25
44
|
end
|
26
45
|
|
27
46
|
def clear
|
28
|
-
@
|
47
|
+
@records.clear
|
29
48
|
end
|
30
49
|
|
31
50
|
def empty?
|
32
|
-
@
|
51
|
+
@records.empty?
|
33
52
|
end
|
34
53
|
|
35
54
|
def verbose!(value = true)
|
@@ -40,32 +59,42 @@ module Console
|
|
40
59
|
@verbose
|
41
60
|
end
|
42
61
|
|
43
|
-
def call(subject = nil, *arguments, severity: UNKNOWN, **options, &block)
|
44
|
-
|
62
|
+
def call(subject = nil, *arguments, severity: UNKNOWN, event: nil, **options, &block)
|
63
|
+
record = {
|
45
64
|
time: ::Time.now.iso8601,
|
46
65
|
severity: severity,
|
47
66
|
**options,
|
48
67
|
}
|
49
68
|
|
50
69
|
if subject
|
51
|
-
|
70
|
+
record[:subject] = subject
|
71
|
+
end
|
72
|
+
|
73
|
+
if event
|
74
|
+
record[:event] = event.to_hash
|
52
75
|
end
|
53
76
|
|
54
77
|
if arguments.any?
|
55
|
-
|
78
|
+
record[:arguments] = arguments
|
79
|
+
end
|
80
|
+
|
81
|
+
if annotation = Fiber.current.annotation
|
82
|
+
record[:annotation] = annotation
|
56
83
|
end
|
57
84
|
|
58
85
|
if block_given?
|
59
86
|
if block.arity.zero?
|
60
|
-
|
87
|
+
record[:message] = yield
|
61
88
|
else
|
62
89
|
buffer = StringIO.new
|
63
90
|
yield buffer
|
64
|
-
|
91
|
+
record[:message] = buffer.string
|
65
92
|
end
|
93
|
+
else
|
94
|
+
record[:message] = arguments.join(" ")
|
66
95
|
end
|
67
96
|
|
68
|
-
@
|
97
|
+
@records << record
|
69
98
|
end
|
70
99
|
end
|
71
100
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2022, by Samuel Williams.
|
4
|
+
# Copyright, 2022-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require
|
6
|
+
require "logger"
|
7
7
|
|
8
8
|
module Console
|
9
9
|
module Compatible
|
10
|
+
# A compatible interface for {::Logger} which can be used with {Console}.
|
10
11
|
class Logger < ::Logger
|
11
12
|
class LogDevice
|
12
13
|
def initialize(subject, output)
|
@@ -29,7 +30,7 @@ module Console
|
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
|
-
def initialize(subject, output)
|
33
|
+
def initialize(subject, output = Console)
|
33
34
|
super(nil)
|
34
35
|
|
35
36
|
@progname = subject
|
@@ -1,73 +1,77 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2021, by Robert Schulze.
|
6
|
+
# Copyright, 2024, by Patrik Wenger.
|
6
7
|
|
7
|
-
require_relative
|
8
|
+
require_relative "generic"
|
8
9
|
|
9
10
|
module Console
|
10
11
|
module Event
|
12
|
+
# Represents a failure event.
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# Console::Event::Failure.for(exception).emit(self)
|
16
|
+
# ```
|
11
17
|
class Failure < Generic
|
12
|
-
def self.
|
18
|
+
def self.default_root
|
13
19
|
Dir.getwd
|
14
20
|
rescue # e.g. Errno::EMFILE
|
15
21
|
nil
|
16
22
|
end
|
17
23
|
|
18
24
|
def self.for(exception)
|
19
|
-
self.new(exception, self.
|
25
|
+
self.new(exception, self.default_root)
|
20
26
|
end
|
21
27
|
|
22
|
-
def
|
23
|
-
|
24
|
-
@root = root
|
28
|
+
def self.log(subject, exception, **options)
|
29
|
+
Console.error(subject, **self.for(exception).to_hash, **options)
|
25
30
|
end
|
26
31
|
|
27
|
-
|
28
|
-
attr :root
|
32
|
+
attr_reader :exception
|
29
33
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
terminal[:exception_backtrace] ||= terminal.style(:red)
|
34
|
-
terminal[:exception_backtrace_other] ||= terminal.style(:red, nil, :faint)
|
35
|
-
terminal[:exception_message] ||= terminal.style(:default)
|
34
|
+
def initialize(exception, root = Dir.getwd)
|
35
|
+
@exception = exception
|
36
|
+
@root = root
|
36
37
|
end
|
37
38
|
|
38
|
-
def
|
39
|
-
|
39
|
+
def to_hash
|
40
|
+
Hash.new.tap do |hash|
|
41
|
+
hash[:type] = :failure
|
42
|
+
hash[:root] = @root if @root
|
43
|
+
extract(@exception, hash)
|
44
|
+
end
|
40
45
|
end
|
41
46
|
|
42
|
-
def
|
43
|
-
|
47
|
+
def emit(*arguments, **options)
|
48
|
+
options[:severity] ||= :error
|
49
|
+
|
50
|
+
super
|
44
51
|
end
|
45
52
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
lines.each do |line|
|
52
|
-
output.puts " #{terminal[:exception_detail]}#{line}#{terminal.reset}"
|
53
|
-
end
|
54
|
-
|
55
|
-
root_pattern = /^#{@root}\// if @root
|
53
|
+
private
|
54
|
+
|
55
|
+
def extract(exception, hash)
|
56
|
+
hash[:class] = exception.class.name
|
56
57
|
|
57
|
-
exception.
|
58
|
-
|
59
|
-
style = :exception_backtrace
|
58
|
+
if exception.respond_to?(:detailed_message)
|
59
|
+
message = exception.detailed_message
|
60
60
|
|
61
|
-
#
|
62
|
-
|
63
|
-
style = :exception_backtrace_other
|
64
|
-
end
|
61
|
+
# We want to remove the trailling exception class as we format it differently:
|
62
|
+
message.sub!(/\s*\(.*?\)$/, "")
|
65
63
|
|
66
|
-
|
64
|
+
hash[:message] = message
|
65
|
+
else
|
66
|
+
hash[:message] = exception.message
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
69
|
+
hash[:backtrace] = exception.backtrace
|
70
|
+
|
71
|
+
if cause = exception.cause
|
72
|
+
hash[:cause] = Hash.new.tap do |cause_hash|
|
73
|
+
extract(cause, cause_hash)
|
74
|
+
end
|
71
75
|
end
|
72
76
|
end
|
73
77
|
end
|
@@ -1,22 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
module Console
|
7
7
|
module Event
|
8
8
|
class Generic
|
9
|
-
def
|
9
|
+
def as_json(...)
|
10
|
+
to_hash
|
10
11
|
end
|
11
12
|
|
12
|
-
def
|
13
|
+
def to_json(...)
|
14
|
+
JSON.generate(as_json, ...)
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
16
|
-
|
17
|
+
def to_s
|
18
|
+
to_json
|
17
19
|
end
|
18
20
|
|
19
|
-
def
|
21
|
+
def emit(*arguments, **options)
|
22
|
+
Console.call(*arguments, event: self, **options)
|
20
23
|
end
|
21
24
|
end
|
22
25
|
end
|
data/lib/console/event/spawn.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "generic"
|
7
|
+
require_relative "../clock"
|
7
8
|
|
8
9
|
module Console
|
9
10
|
module Event
|
11
|
+
# Represents a spawn event.
|
12
|
+
#
|
13
|
+
# ```ruby
|
14
|
+
# Console.info(self, **Console::Event::Spawn.for("ls", "-l"))
|
15
|
+
#
|
16
|
+
# event = Console::Event::Spawn.for("ls", "-l")
|
17
|
+
# event.status = Process.wait
|
18
|
+
# ```
|
10
19
|
class Spawn < Generic
|
11
20
|
def self.for(*arguments, **options)
|
12
21
|
# Extract out the command environment:
|
@@ -22,44 +31,43 @@ module Console
|
|
22
31
|
@environment = environment
|
23
32
|
@arguments = arguments
|
24
33
|
@options = options
|
34
|
+
|
35
|
+
@start_time = Clock.now
|
36
|
+
|
37
|
+
@end_time = nil
|
38
|
+
@status = nil
|
25
39
|
end
|
26
40
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def chdir_string(options)
|
32
|
-
if options and chdir = options[:chdir]
|
33
|
-
" in #{chdir}"
|
41
|
+
def duration
|
42
|
+
if @end_time
|
43
|
+
@end_time - @start_time
|
34
44
|
end
|
35
45
|
end
|
36
46
|
|
37
|
-
def
|
38
|
-
terminal[:shell_command] ||= terminal.style(:blue, nil, :bold)
|
39
|
-
end
|
40
|
-
|
41
|
-
def to_h
|
47
|
+
def to_hash
|
42
48
|
Hash.new.tap do |hash|
|
49
|
+
hash[:type] = :spawn
|
43
50
|
hash[:environment] = @environment if @environment&.any?
|
44
51
|
hash[:arguments] = @arguments if @arguments&.any?
|
45
52
|
hash[:options] = @options if @options&.any?
|
53
|
+
|
54
|
+
hash[:status] = @status.to_i if @status
|
55
|
+
|
56
|
+
if duration = self.duration
|
57
|
+
hash[:duration] = duration
|
58
|
+
end
|
46
59
|
end
|
47
60
|
end
|
48
61
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
end
|
62
|
+
def emit(*arguments, **options)
|
63
|
+
options[:severity] ||= :info
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def status=(status)
|
68
|
+
@end_time = Time.now
|
69
|
+
@status = status
|
59
70
|
end
|
60
71
|
end
|
61
72
|
end
|
62
|
-
|
63
|
-
# Deprecated.
|
64
|
-
Shell = Event::Spawn
|
65
73
|
end
|
data/lib/console/event.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative 'event/progress'
|
6
|
+
require_relative "event/spawn"
|
7
|
+
require_relative "event/failure"
|
data/lib/console/filter.rb
CHANGED
@@ -1,17 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by Bryan Powell.
|
6
6
|
# Copyright, 2020, by Michael Adams.
|
7
7
|
# Copyright, 2021, by Robert Schulze.
|
8
8
|
|
9
|
-
require_relative 'buffer'
|
10
|
-
|
11
9
|
module Console
|
12
|
-
UNKNOWN =
|
10
|
+
UNKNOWN = :unknown
|
13
11
|
|
14
12
|
class Filter
|
13
|
+
if Object.const_defined?(:Ractor) and RUBY_VERSION >= "3.1"
|
14
|
+
def self.define_immutable_method(name, &block)
|
15
|
+
block = Ractor.make_shareable(block)
|
16
|
+
self.define_method(name, &block)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
def self.define_immutable_method(name, &block)
|
20
|
+
define_method(name, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
15
24
|
def self.[] **levels
|
16
25
|
klass = Class.new(self)
|
17
26
|
minimum_level, maximum_level = levels.values.minmax
|
@@ -24,17 +33,17 @@ module Console
|
|
24
33
|
levels.each do |name, level|
|
25
34
|
const_set(name.to_s.upcase, level)
|
26
35
|
|
27
|
-
|
36
|
+
define_immutable_method(name) do |subject = nil, *arguments, **options, &block|
|
28
37
|
if self.enabled?(subject, level)
|
29
|
-
|
38
|
+
@output.call(subject, *arguments, severity: name, **@options, **options, &block)
|
30
39
|
end
|
31
40
|
end
|
32
41
|
|
33
|
-
|
42
|
+
define_immutable_method("#{name}!") do
|
34
43
|
@level = level
|
35
44
|
end
|
36
45
|
|
37
|
-
|
46
|
+
define_immutable_method("#{name}?") do
|
38
47
|
@level <= level
|
39
48
|
end
|
40
49
|
end
|
@@ -134,8 +143,13 @@ module Console
|
|
134
143
|
@subjects.delete(subject)
|
135
144
|
end
|
136
145
|
|
137
|
-
def call(*arguments, **options, &block)
|
138
|
-
|
146
|
+
def call(subject, *arguments, **options, &block)
|
147
|
+
severity = options[:severity] || UNKNOWN
|
148
|
+
level = self.class::LEVELS[severity]
|
149
|
+
|
150
|
+
if self.enabled?(subject, level)
|
151
|
+
@output.call(subject, *arguments, **options, &block)
|
152
|
+
end
|
139
153
|
end
|
140
154
|
end
|
141
155
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023-2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module Console
|
9
|
+
module Format
|
10
|
+
# This class is used to safely dump objects.
|
11
|
+
# It will attempt to dump the object using the given format, but if it fails, it will generate a safe version of the object.
|
12
|
+
class Safe
|
13
|
+
def initialize(format: ::JSON, limit: 8, encoding: ::Encoding::UTF_8)
|
14
|
+
@format = format
|
15
|
+
@limit = limit
|
16
|
+
@encoding = encoding
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump(object)
|
20
|
+
@format.dump(object, @limit)
|
21
|
+
rescue SystemStackError, StandardError => error
|
22
|
+
@format.dump(safe_dump(object, error))
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def filter_backtrace(error)
|
28
|
+
frames = error.backtrace
|
29
|
+
filtered = {}
|
30
|
+
filtered_count = nil
|
31
|
+
skipped = nil
|
32
|
+
|
33
|
+
frames = frames.filter_map do |frame|
|
34
|
+
if filtered[frame]
|
35
|
+
if filtered_count == nil
|
36
|
+
filtered_count = 1
|
37
|
+
skipped = frame.dup
|
38
|
+
else
|
39
|
+
filtered_count += 1
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
else
|
43
|
+
if skipped
|
44
|
+
if filtered_count > 1
|
45
|
+
skipped.replace("[... #{filtered_count} frames skipped ...]")
|
46
|
+
end
|
47
|
+
|
48
|
+
filtered_count = nil
|
49
|
+
skipped = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
filtered[frame] = true
|
53
|
+
frame
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if skipped && filtered_count > 1
|
58
|
+
skipped.replace("[... #{filtered_count} frames skipped ...]")
|
59
|
+
end
|
60
|
+
|
61
|
+
return frames
|
62
|
+
end
|
63
|
+
|
64
|
+
def safe_dump(object, error)
|
65
|
+
object = safe_dump_recurse(object)
|
66
|
+
|
67
|
+
object[:truncated] = true
|
68
|
+
object[:error] = {
|
69
|
+
class: safe_dump_recurse(error.class.name),
|
70
|
+
message: safe_dump_recurse(error.message),
|
71
|
+
backtrace: safe_dump_recurse(filter_backtrace(error)),
|
72
|
+
}
|
73
|
+
|
74
|
+
return object
|
75
|
+
end
|
76
|
+
|
77
|
+
def replacement_for(object)
|
78
|
+
case object
|
79
|
+
when Array
|
80
|
+
"[...]"
|
81
|
+
when Hash
|
82
|
+
"{...}"
|
83
|
+
else
|
84
|
+
"..."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def default_objects
|
89
|
+
Hash.new.compare_by_identity
|
90
|
+
end
|
91
|
+
|
92
|
+
# This will recursively generate a safe version of the object.
|
93
|
+
# Nested hashes and arrays will be transformed recursively.
|
94
|
+
# Strings will be encoded with the given encoding.
|
95
|
+
# Primitive values will be returned as-is.
|
96
|
+
# Other values will be converted using `as_json` if available, otherwise `to_s`.
|
97
|
+
def safe_dump_recurse(object, limit = @limit, objects = default_objects)
|
98
|
+
if limit <= 0 || objects[object]
|
99
|
+
return replacement_for(object)
|
100
|
+
end
|
101
|
+
|
102
|
+
case object
|
103
|
+
when Hash
|
104
|
+
objects[object] = true
|
105
|
+
|
106
|
+
object.to_h do |key, value|
|
107
|
+
[
|
108
|
+
String(key).encode(@encoding, invalid: :replace, undef: :replace),
|
109
|
+
safe_dump_recurse(value, limit - 1, objects)
|
110
|
+
]
|
111
|
+
end
|
112
|
+
when Array
|
113
|
+
objects[object] = true
|
114
|
+
|
115
|
+
object.map do |value|
|
116
|
+
safe_dump_recurse(value, limit - 1, objects)
|
117
|
+
end
|
118
|
+
when String
|
119
|
+
object.encode(@encoding, invalid: :replace, undef: :replace)
|
120
|
+
when Numeric, TrueClass, FalseClass, NilClass
|
121
|
+
object
|
122
|
+
else
|
123
|
+
objects[object] = true
|
124
|
+
|
125
|
+
# We could do something like this but the chance `as_json` will blow up.
|
126
|
+
# We'd need to be extremely careful about it.
|
127
|
+
# if object.respond_to?(:as_json)
|
128
|
+
# safe_dump_recurse(object.as_json, limit - 1, objects)
|
129
|
+
# else
|
130
|
+
|
131
|
+
safe_dump_recurse(object.to_s, limit - 1, objects)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|