rbs 0.5.0 → 0.9.1
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/CHANGELOG.md +33 -0
- data/Gemfile +2 -0
- data/Rakefile +2 -3
- data/docs/stdlib.md +0 -2
- data/docs/syntax.md +6 -3
- data/goodcheck.yml +65 -0
- data/lib/rbs.rb +1 -0
- data/lib/rbs/ast/comment.rb +6 -0
- data/lib/rbs/ast/declarations.rb +44 -6
- data/lib/rbs/cli.rb +21 -3
- data/lib/rbs/constant_table.rb +1 -1
- data/lib/rbs/definition_builder.rb +290 -124
- data/lib/rbs/environment.rb +50 -37
- data/lib/rbs/errors.rb +68 -25
- data/lib/rbs/factory.rb +14 -0
- data/lib/rbs/location.rb +15 -0
- data/lib/rbs/parser.y +89 -28
- data/lib/rbs/prototype/rb.rb +2 -2
- data/lib/rbs/prototype/rbi.rb +1 -1
- data/lib/rbs/prototype/runtime.rb +1 -1
- data/lib/rbs/substitution.rb +6 -2
- data/lib/rbs/test.rb +82 -3
- data/lib/rbs/test/errors.rb +5 -1
- data/lib/rbs/test/hook.rb +133 -259
- data/lib/rbs/test/observer.rb +17 -0
- data/lib/rbs/test/setup.rb +37 -20
- data/lib/rbs/test/setup_helper.rb +29 -0
- data/lib/rbs/test/spy.rb +0 -321
- data/lib/rbs/test/tester.rb +118 -0
- data/lib/rbs/test/type_check.rb +42 -5
- data/lib/rbs/validator.rb +4 -0
- data/lib/rbs/version.rb +1 -1
- data/lib/rbs/writer.rb +2 -2
- data/schema/decls.json +21 -10
- data/stdlib/builtin/enumerable.rbs +2 -2
- data/stdlib/builtin/proc.rbs +1 -2
- data/stdlib/json/json.rbs +6 -0
- data/stdlib/logger/formatter.rbs +23 -0
- data/stdlib/logger/log_device.rbs +39 -0
- data/stdlib/logger/logger.rbs +507 -0
- data/stdlib/logger/period.rbs +7 -0
- data/stdlib/logger/severity.rbs +8 -0
- data/stdlib/pty/pty.rbs +159 -0
- metadata +13 -3
- data/lib/rbs/test/test_helper.rb +0 -180
@@ -0,0 +1,118 @@
|
|
1
|
+
module RBS
|
2
|
+
module Test
|
3
|
+
class Tester
|
4
|
+
attr_reader :env
|
5
|
+
attr_reader :targets
|
6
|
+
|
7
|
+
def initialize(env:)
|
8
|
+
@env = env
|
9
|
+
@targets = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def factory
|
13
|
+
@factory ||= Factory.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def builder
|
17
|
+
@builder ||= DefinitionBuilder.new(env: env)
|
18
|
+
end
|
19
|
+
|
20
|
+
def install!(klass, sample_size:)
|
21
|
+
RBS.logger.info { "Installing runtime type checker in #{klass}..." }
|
22
|
+
|
23
|
+
type_name = factory.type_name(klass.name).absolute!
|
24
|
+
|
25
|
+
builder.build_instance(type_name).tap do |definition|
|
26
|
+
instance_key = new_key(type_name, "InstanceChecker")
|
27
|
+
Observer.register(instance_key, MethodCallTester.new(klass, builder, definition, kind: :instance, sample_size: sample_size))
|
28
|
+
|
29
|
+
definition.methods.each do |name, method|
|
30
|
+
if method.implemented_in == type_name
|
31
|
+
RBS.logger.info { "Setting up method hook in ##{name}..." }
|
32
|
+
Hook.hook_instance_method klass, name, key: instance_key
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
builder.build_singleton(type_name).tap do |definition|
|
38
|
+
singleton_key = new_key(type_name, "SingletonChecker")
|
39
|
+
Observer.register(singleton_key, MethodCallTester.new(klass.singleton_class, builder, definition, kind: :singleton, sample_size: sample_size))
|
40
|
+
|
41
|
+
definition.methods.each do |name, method|
|
42
|
+
if method.implemented_in == type_name || name == :new
|
43
|
+
RBS.logger.info { "Setting up method hook in .#{name}..." }
|
44
|
+
Hook.hook_singleton_method klass, name, key: singleton_key
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
targets << klass
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_key(type_name, prefix)
|
53
|
+
"#{prefix}__#{type_name}__#{SecureRandom.hex(10)}"
|
54
|
+
end
|
55
|
+
|
56
|
+
class TypeError < Exception
|
57
|
+
attr_reader :errors
|
58
|
+
|
59
|
+
def initialize(errors)
|
60
|
+
@errors = errors
|
61
|
+
|
62
|
+
super "TypeError: #{errors.map {|e| Errors.to_string(e) }.join(", ")}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class MethodCallTester
|
67
|
+
attr_reader :self_class
|
68
|
+
attr_reader :definition
|
69
|
+
attr_reader :builder
|
70
|
+
attr_reader :kind
|
71
|
+
attr_reader :sample_size
|
72
|
+
|
73
|
+
def initialize(self_class, builder, definition, kind:, sample_size:)
|
74
|
+
@self_class = self_class
|
75
|
+
@definition = definition
|
76
|
+
@builder = builder
|
77
|
+
@kind = kind
|
78
|
+
@sample_size = sample_size
|
79
|
+
end
|
80
|
+
|
81
|
+
def env
|
82
|
+
builder.env
|
83
|
+
end
|
84
|
+
|
85
|
+
def check
|
86
|
+
@check ||= TypeCheck.new(self_class: self_class, builder: builder, sample_size: sample_size)
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_method_name(name)
|
90
|
+
case kind
|
91
|
+
when :instance
|
92
|
+
"##{name}"
|
93
|
+
when :singleton
|
94
|
+
".#{name}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def call(receiver, trace)
|
99
|
+
method_name = trace.method_name
|
100
|
+
method = definition.methods[method_name]
|
101
|
+
if method
|
102
|
+
RBS.logger.debug { "Type checking `#{self_class}#{format_method_name(method_name)}`..."}
|
103
|
+
errors = check.overloaded_call(method, format_method_name(method_name), trace, errors: [])
|
104
|
+
|
105
|
+
if errors.empty?
|
106
|
+
RBS.logger.debug { "No type error detected 👏" }
|
107
|
+
else
|
108
|
+
RBS.logger.debug { "Detected type error 🚨" }
|
109
|
+
raise TypeError.new(errors)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
RBS.logger.error { "Type checking `#{self_class}#{method_name}` call but no method found in definition" }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/rbs/test/type_check.rb
CHANGED
@@ -3,10 +3,38 @@ module RBS
|
|
3
3
|
class TypeCheck
|
4
4
|
attr_reader :self_class
|
5
5
|
attr_reader :builder
|
6
|
+
attr_reader :sample_size
|
6
7
|
|
7
|
-
|
8
|
+
DEFAULT_SAMPLE_SIZE = 100
|
9
|
+
|
10
|
+
def initialize(self_class:, builder:, sample_size:)
|
8
11
|
@self_class = self_class
|
9
12
|
@builder = builder
|
13
|
+
@sample_size = sample_size
|
14
|
+
end
|
15
|
+
|
16
|
+
def overloaded_call(method, method_name, call, errors:)
|
17
|
+
es = method.method_types.map do |method_type|
|
18
|
+
es = method_call(method_name, method_type, call, errors: [])
|
19
|
+
|
20
|
+
if es.empty?
|
21
|
+
return errors
|
22
|
+
else
|
23
|
+
es
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if es.size == 1
|
28
|
+
errors.push(*es[0])
|
29
|
+
else
|
30
|
+
errors << Errors::UnresolvedOverloadingError.new(
|
31
|
+
klass: self_class,
|
32
|
+
method_name: method_name,
|
33
|
+
method_types: method.method_types
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
errors
|
10
38
|
end
|
11
39
|
|
12
40
|
def method_call(method_name, method_type, call, errors:)
|
@@ -56,7 +84,7 @@ module RBS
|
|
56
84
|
end
|
57
85
|
|
58
86
|
def return(method_name, method_type, fun, call, errors, return_error:)
|
59
|
-
|
87
|
+
if call.return?
|
60
88
|
unless value(call.return_value, fun.return_type)
|
61
89
|
errors << return_error.new(klass: self_class,
|
62
90
|
method_name: method_name,
|
@@ -151,6 +179,10 @@ module RBS
|
|
151
179
|
end
|
152
180
|
end
|
153
181
|
|
182
|
+
def sample(array)
|
183
|
+
sample_size && (array.size > sample_size) ? array.sample(sample_size) : array
|
184
|
+
end
|
185
|
+
|
154
186
|
def value(val, type)
|
155
187
|
case type
|
156
188
|
when Types::Bases::Any
|
@@ -175,9 +207,14 @@ module RBS
|
|
175
207
|
klass = Object.const_get(type.name.to_s)
|
176
208
|
case
|
177
209
|
when klass == ::Array
|
178
|
-
Test.call(val, IS_AP, klass) && val.
|
210
|
+
Test.call(val, IS_AP, klass) && sample(val).yield_self do |val|
|
211
|
+
val.all? {|v| value(v, type.args[0]) }
|
212
|
+
end
|
179
213
|
when klass == ::Hash
|
180
|
-
Test.call(val, IS_AP, klass) && val.
|
214
|
+
Test.call(val, IS_AP, klass) && sample(val.keys).yield_self do |keys|
|
215
|
+
values = val.values_at(*keys)
|
216
|
+
keys.all? {|key| value(key, type.args[0]) } && values.all? {|v| value(v, type.args[1]) }
|
217
|
+
end
|
181
218
|
when klass == ::Range
|
182
219
|
Test.call(val, IS_AP, klass) && value(val.begin, type.args[0]) && value(val.end, type.args[0])
|
183
220
|
when klass == ::Enumerator
|
@@ -198,7 +235,7 @@ module RBS
|
|
198
235
|
end
|
199
236
|
end
|
200
237
|
|
201
|
-
values.all? do |v|
|
238
|
+
sample(values).all? do |v|
|
202
239
|
if v.size == 1
|
203
240
|
# Only one block argument.
|
204
241
|
value(v[0], type.args[0]) || value(v, type.args[0])
|
data/lib/rbs/validator.rb
CHANGED
@@ -41,6 +41,10 @@ module RBS
|
|
41
41
|
params: type_params.each.map(&:name),
|
42
42
|
location: type.location
|
43
43
|
)
|
44
|
+
|
45
|
+
when Types::Alias, Types::ClassSingleton
|
46
|
+
type = absolute_type(type, context: context) { type.name.absolute! }
|
47
|
+
NoTypeFoundError.check!(type.name, env: env, location: type.location)
|
44
48
|
end
|
45
49
|
|
46
50
|
type.each_type do |type|
|
data/lib/rbs/version.rb
CHANGED
data/lib/rbs/writer.rb
CHANGED
data/schema/decls.json
CHANGED
@@ -226,15 +226,11 @@
|
|
226
226
|
"$ref": "#/definitions/classMember"
|
227
227
|
}
|
228
228
|
},
|
229
|
-
"
|
230
|
-
"
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
{
|
235
|
-
"type": "null"
|
236
|
-
}
|
237
|
-
]
|
229
|
+
"self_types": {
|
230
|
+
"type": "array",
|
231
|
+
"items": {
|
232
|
+
"$ref": "#/definitions/moduleSelf"
|
233
|
+
}
|
238
234
|
},
|
239
235
|
"annotations": {
|
240
236
|
"type": "array",
|
@@ -249,7 +245,22 @@
|
|
249
245
|
"$ref": "location.json"
|
250
246
|
}
|
251
247
|
},
|
252
|
-
"required": ["declaration", "name", "type_params", "members", "
|
248
|
+
"required": ["declaration", "name", "type_params", "members", "self_types", "annotations", "location", "comment"]
|
249
|
+
},
|
250
|
+
"moduleSelf": {
|
251
|
+
"type": "object",
|
252
|
+
"properties": {
|
253
|
+
"name": {
|
254
|
+
"type": "string"
|
255
|
+
},
|
256
|
+
"args": {
|
257
|
+
"type": "array",
|
258
|
+
"items": {
|
259
|
+
"$ref": "types.json"
|
260
|
+
}
|
261
|
+
}
|
262
|
+
},
|
263
|
+
"required": ["name", "args"]
|
253
264
|
},
|
254
265
|
"interfaceMember": {
|
255
266
|
"oneOf": [
|
@@ -46,7 +46,7 @@ module Enumerable[unchecked out Elem, out Return]: _Each[Elem, Return]
|
|
46
46
|
| () { (Elem arg0) -> untyped } -> bool
|
47
47
|
|
48
48
|
def collect: [U] () { (Elem arg0) -> U } -> ::Array[U]
|
49
|
-
| () -> ::Enumerator[Elem,
|
49
|
+
| () -> ::Enumerator[Elem, ::Array[untyped]]
|
50
50
|
|
51
51
|
def collect_concat: [U] () { (Elem arg0) -> ::Enumerator[U, untyped] } -> ::Array[U]
|
52
52
|
|
@@ -317,7 +317,7 @@ module Enumerable[unchecked out Elem, out Return]: _Each[Elem, Return]
|
|
317
317
|
| () -> ::Enumerator[Elem, Return]
|
318
318
|
|
319
319
|
def map: [U] () { (Elem arg0) -> U } -> ::Array[U]
|
320
|
-
| () -> ::Enumerator[Elem,
|
320
|
+
| () -> ::Enumerator[Elem, ::Array[untyped]]
|
321
321
|
|
322
322
|
def member?: (untyped arg0) -> bool
|
323
323
|
|
data/stdlib/builtin/proc.rbs
CHANGED
@@ -269,8 +269,7 @@ class Proc < Object
|
|
269
269
|
# See also Object\#hash.
|
270
270
|
def hash: () -> Integer
|
271
271
|
|
272
|
-
def initialize: () -> void
|
273
|
-
| () { (*untyped) -> untyped } -> void
|
272
|
+
def initialize: () { (*untyped) -> untyped } -> void
|
274
273
|
|
275
274
|
# Returns `true` for a [Proc](Proc.downloaded.ruby_doc) object for which
|
276
275
|
# argument handling is rigid. Such procs are typically generated by
|
data/stdlib/json/json.rbs
CHANGED
@@ -42,6 +42,9 @@ end
|
|
42
42
|
class JSON::Ext::Generator::State
|
43
43
|
end
|
44
44
|
|
45
|
+
class JSON::Ext::Parser
|
46
|
+
end
|
47
|
+
|
45
48
|
module JSON::Pure
|
46
49
|
end
|
47
50
|
|
@@ -51,6 +54,9 @@ end
|
|
51
54
|
class JSON::Pure::Generator::State
|
52
55
|
end
|
53
56
|
|
57
|
+
class JSON::Pure::Parser
|
58
|
+
end
|
59
|
+
|
54
60
|
type json_generator = singleton(::JSON::Ext::Generator) | singleton(::JSON::Pure::Generator)
|
55
61
|
type json_parser = singleton(::JSON::Ext::Parser) | singleton(::JSON::Pure::Parser)
|
56
62
|
type json_state = singleton(JSON::Ext::Generator::State) | singleton(JSON::Pure::Generator::State)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Logger
|
2
|
+
class Formatter
|
3
|
+
public
|
4
|
+
|
5
|
+
attr_accessor datetime_format: String?
|
6
|
+
|
7
|
+
def call: (String severity, Time time, untyped progname, untyped msg) -> String
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def format_datetime: (Time time) -> untyped
|
12
|
+
|
13
|
+
def initialize: () -> void
|
14
|
+
|
15
|
+
def msg2str: (String | Exception | untyped msg) -> String
|
16
|
+
end
|
17
|
+
|
18
|
+
interface _Formatter
|
19
|
+
def call: (String severity, Time time, untyped progname, untyped msg) -> _ToS
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Logger::Formatter::Format: String
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Logger
|
2
|
+
class LogDevice
|
3
|
+
# TODO: Write type signature for MonitorMixin
|
4
|
+
# include MonitorMixin
|
5
|
+
|
6
|
+
include Period
|
7
|
+
|
8
|
+
attr_reader dev: _WriteCloser
|
9
|
+
attr_reader filename: String?
|
10
|
+
|
11
|
+
public
|
12
|
+
|
13
|
+
def close: () -> nil
|
14
|
+
|
15
|
+
def reopen: (?logdev log) -> self
|
16
|
+
|
17
|
+
def write: (untyped message) -> untyped
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def add_log_header: (IO file) -> untyped
|
22
|
+
|
23
|
+
def check_shift_log: () -> untyped
|
24
|
+
|
25
|
+
def create_logfile: (String filename) -> File
|
26
|
+
|
27
|
+
def initialize: (?untyped logdev, ?binmode: bool, ?shift_period_suffix: String, ?shift_size: Integer, ?shift_age: Numeric | String) -> void
|
28
|
+
|
29
|
+
def lock_shift_log: () { () -> untyped } -> untyped
|
30
|
+
|
31
|
+
def open_logfile: (String filename) -> File
|
32
|
+
|
33
|
+
def set_dev: (logdev log) -> untyped
|
34
|
+
|
35
|
+
def shift_log_age: () -> true
|
36
|
+
|
37
|
+
def shift_log_period: (Time period_end) -> true
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,507 @@
|
|
1
|
+
# ## Description
|
2
|
+
#
|
3
|
+
# The Logger class provides a simple but sophisticated logging utility that you
|
4
|
+
# can use to output messages.
|
5
|
+
#
|
6
|
+
# The messages have associated levels, such as `INFO` or `ERROR` that indicate
|
7
|
+
# their importance. You can then give the Logger a level, and only messages at
|
8
|
+
# that level or higher will be printed.
|
9
|
+
#
|
10
|
+
# The levels are:
|
11
|
+
#
|
12
|
+
# `UNKNOWN`
|
13
|
+
# : An unknown message that should always be logged.
|
14
|
+
# `FATAL`
|
15
|
+
# : An unhandleable error that results in a program crash.
|
16
|
+
# `ERROR`
|
17
|
+
# : A handleable error condition.
|
18
|
+
# `WARN`
|
19
|
+
# : A warning.
|
20
|
+
# `INFO`
|
21
|
+
# : Generic (useful) information about system operation.
|
22
|
+
# `DEBUG`
|
23
|
+
# : Low-level information for developers.
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# For instance, in a production system, you may have your Logger set to `INFO`
|
27
|
+
# or even `WARN`. When you are developing the system, however, you probably want
|
28
|
+
# to know about the program's internal state, and would set the Logger to
|
29
|
+
# `DEBUG`.
|
30
|
+
#
|
31
|
+
# **Note**: Logger does not escape or sanitize any messages passed to it.
|
32
|
+
# Developers should be aware of when potentially malicious data (user-input) is
|
33
|
+
# passed to Logger, and manually escape the untrusted data:
|
34
|
+
#
|
35
|
+
# logger.info("User-input: #{input.dump}")
|
36
|
+
# logger.info("User-input: %p" % input)
|
37
|
+
#
|
38
|
+
# You can use #formatter= for escaping all data.
|
39
|
+
#
|
40
|
+
# original_formatter = Logger::Formatter.new
|
41
|
+
# logger.formatter = proc { |severity, datetime, progname, msg|
|
42
|
+
# original_formatter.call(severity, datetime, progname, msg.dump)
|
43
|
+
# }
|
44
|
+
# logger.info(input)
|
45
|
+
#
|
46
|
+
# ### Example
|
47
|
+
#
|
48
|
+
# This creates a Logger that outputs to the standard output stream, with a level
|
49
|
+
# of `WARN`:
|
50
|
+
#
|
51
|
+
# require 'logger'
|
52
|
+
#
|
53
|
+
# logger = Logger.new(STDOUT)
|
54
|
+
# logger.level = Logger::WARN
|
55
|
+
#
|
56
|
+
# logger.debug("Created logger")
|
57
|
+
# logger.info("Program started")
|
58
|
+
# logger.warn("Nothing to do!")
|
59
|
+
#
|
60
|
+
# path = "a_non_existent_file"
|
61
|
+
#
|
62
|
+
# begin
|
63
|
+
# File.foreach(path) do |line|
|
64
|
+
# unless line =~ /^(\w+) = (.*)$/
|
65
|
+
# logger.error("Line in wrong format: #{line.chomp}")
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
# rescue => err
|
69
|
+
# logger.fatal("Caught exception; exiting")
|
70
|
+
# logger.fatal(err)
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# Because the Logger's level is set to `WARN`, only the warning, error, and
|
74
|
+
# fatal messages are recorded. The debug and info messages are silently
|
75
|
+
# discarded.
|
76
|
+
#
|
77
|
+
# ### Features
|
78
|
+
#
|
79
|
+
# There are several interesting features that Logger provides, like auto-rolling
|
80
|
+
# of log files, setting the format of log messages, and specifying a program
|
81
|
+
# name in conjunction with the message. The next section shows you how to
|
82
|
+
# achieve these things.
|
83
|
+
#
|
84
|
+
# ## HOWTOs
|
85
|
+
#
|
86
|
+
# ### How to create a logger
|
87
|
+
#
|
88
|
+
# The options below give you various choices, in more or less increasing
|
89
|
+
# complexity.
|
90
|
+
#
|
91
|
+
# 1. Create a logger which logs messages to STDERR/STDOUT.
|
92
|
+
#
|
93
|
+
# logger = Logger.new(STDERR)
|
94
|
+
# logger = Logger.new(STDOUT)
|
95
|
+
#
|
96
|
+
# 2. Create a logger for the file which has the specified name.
|
97
|
+
#
|
98
|
+
# logger = Logger.new('logfile.log')
|
99
|
+
#
|
100
|
+
# 3. Create a logger for the specified file.
|
101
|
+
#
|
102
|
+
# file = File.open('foo.log', File::WRONLY | File::APPEND)
|
103
|
+
# # To create new logfile, add File::CREAT like:
|
104
|
+
# # file = File.open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
|
105
|
+
# logger = Logger.new(file)
|
106
|
+
#
|
107
|
+
# 4. Create a logger which ages the logfile once it reaches a certain size.
|
108
|
+
# Leave 10 "old" log files where each file is about 1,024,000 bytes.
|
109
|
+
#
|
110
|
+
# logger = Logger.new('foo.log', 10, 1024000)
|
111
|
+
#
|
112
|
+
# 5. Create a logger which ages the logfile daily/weekly/monthly.
|
113
|
+
#
|
114
|
+
# logger = Logger.new('foo.log', 'daily')
|
115
|
+
# logger = Logger.new('foo.log', 'weekly')
|
116
|
+
# logger = Logger.new('foo.log', 'monthly')
|
117
|
+
#
|
118
|
+
#
|
119
|
+
# ### How to log a message
|
120
|
+
#
|
121
|
+
# Notice the different methods (`fatal`, `error`, `info`) being used to log
|
122
|
+
# messages of various levels? Other methods in this family are `warn` and
|
123
|
+
# `debug`. `add` is used below to log a message of an arbitrary (perhaps
|
124
|
+
# dynamic) level.
|
125
|
+
#
|
126
|
+
# 1. Message in a block.
|
127
|
+
#
|
128
|
+
# logger.fatal { "Argument 'foo' not given." }
|
129
|
+
#
|
130
|
+
# 2. Message as a string.
|
131
|
+
#
|
132
|
+
# logger.error "Argument #{@foo} mismatch."
|
133
|
+
#
|
134
|
+
# 3. With progname.
|
135
|
+
#
|
136
|
+
# logger.info('initialize') { "Initializing..." }
|
137
|
+
#
|
138
|
+
# 4. With severity.
|
139
|
+
#
|
140
|
+
# logger.add(Logger::FATAL) { 'Fatal error!' }
|
141
|
+
#
|
142
|
+
#
|
143
|
+
# The block form allows you to create potentially complex log messages, but to
|
144
|
+
# delay their evaluation until and unless the message is logged. For example,
|
145
|
+
# if we have the following:
|
146
|
+
#
|
147
|
+
# logger.debug { "This is a " + potentially + " expensive operation" }
|
148
|
+
#
|
149
|
+
# If the logger's level is `INFO` or higher, no debug messages will be logged,
|
150
|
+
# and the entire block will not even be evaluated. Compare to this:
|
151
|
+
#
|
152
|
+
# logger.debug("This is a " + potentially + " expensive operation")
|
153
|
+
#
|
154
|
+
# Here, the string concatenation is done every time, even if the log level is
|
155
|
+
# not set to show the debug message.
|
156
|
+
#
|
157
|
+
# ### How to close a logger
|
158
|
+
#
|
159
|
+
# logger.close
|
160
|
+
#
|
161
|
+
# ### Setting severity threshold
|
162
|
+
#
|
163
|
+
# 1. Original interface.
|
164
|
+
#
|
165
|
+
# logger.sev_threshold = Logger::WARN
|
166
|
+
#
|
167
|
+
# 2. Log4r (somewhat) compatible interface.
|
168
|
+
#
|
169
|
+
# logger.level = Logger::INFO
|
170
|
+
#
|
171
|
+
# # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
|
172
|
+
#
|
173
|
+
# 3. Symbol or String (case insensitive)
|
174
|
+
#
|
175
|
+
# logger.level = :info
|
176
|
+
# logger.level = 'INFO'
|
177
|
+
#
|
178
|
+
# # :debug < :info < :warn < :error < :fatal < :unknown
|
179
|
+
#
|
180
|
+
# 4. Constructor
|
181
|
+
#
|
182
|
+
# Logger.new(logdev, level: Logger::INFO)
|
183
|
+
# Logger.new(logdev, level: :info)
|
184
|
+
# Logger.new(logdev, level: 'INFO')
|
185
|
+
#
|
186
|
+
#
|
187
|
+
# ## Format
|
188
|
+
#
|
189
|
+
# Log messages are rendered in the output stream in a certain format by default.
|
190
|
+
# The default format and a sample are shown below:
|
191
|
+
#
|
192
|
+
# Log format:
|
193
|
+
# SeverityID, [DateTime #pid] SeverityLabel -- ProgName: message
|
194
|
+
#
|
195
|
+
# Log sample:
|
196
|
+
# I, [1999-03-03T02:34:24.895701 #19074] INFO -- Main: info.
|
197
|
+
#
|
198
|
+
# You may change the date and time format via #datetime_format=.
|
199
|
+
#
|
200
|
+
# logger.datetime_format = '%Y-%m-%d %H:%M:%S'
|
201
|
+
# # e.g. "2004-01-03 00:54:26"
|
202
|
+
#
|
203
|
+
# or via the constructor.
|
204
|
+
#
|
205
|
+
# Logger.new(logdev, datetime_format: '%Y-%m-%d %H:%M:%S')
|
206
|
+
#
|
207
|
+
# Or, you may change the overall format via the #formatter= method.
|
208
|
+
#
|
209
|
+
# logger.formatter = proc do |severity, datetime, progname, msg|
|
210
|
+
# "#{datetime}: #{msg}\n"
|
211
|
+
# end
|
212
|
+
# # e.g. "2005-09-22 08:51:08 +0900: hello world"
|
213
|
+
#
|
214
|
+
# or via the constructor.
|
215
|
+
#
|
216
|
+
# Logger.new(logdev, formatter: proc {|severity, datetime, progname, msg|
|
217
|
+
# "#{datetime}: #{msg}\n"
|
218
|
+
# })
|
219
|
+
#
|
220
|
+
class Logger
|
221
|
+
interface _WriteCloser
|
222
|
+
def write: (_ToS) -> untyped
|
223
|
+
|
224
|
+
def close: () -> untyped
|
225
|
+
end
|
226
|
+
type logdev = _WriteCloser | String
|
227
|
+
|
228
|
+
include Logger::Severity
|
229
|
+
|
230
|
+
public
|
231
|
+
|
232
|
+
# Dump given message to the log device without any formatting. If no log device
|
233
|
+
# exists, return `nil`.
|
234
|
+
#
|
235
|
+
def <<: (untyped msg) -> (untyped | nil)
|
236
|
+
|
237
|
+
# ### Args
|
238
|
+
#
|
239
|
+
# `severity`
|
240
|
+
# : Severity. Constants are defined in Logger namespace: `DEBUG`, `INFO`,
|
241
|
+
# `WARN`, `ERROR`, `FATAL`, or `UNKNOWN`.
|
242
|
+
# `message`
|
243
|
+
# : The log message. A String or Exception.
|
244
|
+
# `progname`
|
245
|
+
# : Program name string. Can be omitted. Treated as a message if no
|
246
|
+
# `message` and `block` are given.
|
247
|
+
# `block`
|
248
|
+
# : Can be omitted. Called to get a message string if `message` is nil.
|
249
|
+
#
|
250
|
+
#
|
251
|
+
# ### Return
|
252
|
+
#
|
253
|
+
# When the given severity is not high enough (for this particular logger), log
|
254
|
+
# no message, and return `true`.
|
255
|
+
#
|
256
|
+
# ### Description
|
257
|
+
#
|
258
|
+
# Log a message if the given severity is high enough. This is the generic
|
259
|
+
# logging method. Users will be more inclined to use #debug, #info, #warn,
|
260
|
+
# #error, and #fatal.
|
261
|
+
#
|
262
|
+
# **Message format**: `message` can be any object, but it has to be converted to
|
263
|
+
# a String in order to log it. Generally, `inspect` is used if the given object
|
264
|
+
# is not a String. A special case is an `Exception` object, which will be
|
265
|
+
# printed in detail, including message, class, and backtrace. See #msg2str for
|
266
|
+
# the implementation if required.
|
267
|
+
#
|
268
|
+
# ### Bugs
|
269
|
+
#
|
270
|
+
# * Logfile is not locked.
|
271
|
+
# * Append open does not need to lock file.
|
272
|
+
# * If the OS supports multi I/O, records possibly may be mixed.
|
273
|
+
#
|
274
|
+
def add: (Integer severity, ?untyped message, ?untyped progname) ?{ () -> untyped } -> true
|
275
|
+
|
276
|
+
# Close the logging device.
|
277
|
+
#
|
278
|
+
def close: () -> untyped
|
279
|
+
|
280
|
+
# Returns the date format being used. See #datetime_format=
|
281
|
+
#
|
282
|
+
def datetime_format: () -> String?
|
283
|
+
|
284
|
+
# Set date-time format.
|
285
|
+
#
|
286
|
+
# `datetime_format`
|
287
|
+
# : A string suitable for passing to `strftime`.
|
288
|
+
#
|
289
|
+
def datetime_format=: (String datetime_format) -> String
|
290
|
+
| (nil datetime_format) -> nil
|
291
|
+
|
292
|
+
# Log a `DEBUG` message.
|
293
|
+
#
|
294
|
+
# See #info for more information.
|
295
|
+
#
|
296
|
+
def debug: (?untyped progname) ?{ () -> untyped } -> true
|
297
|
+
|
298
|
+
# Sets the severity to DEBUG.
|
299
|
+
#
|
300
|
+
def debug!: () -> Integer
|
301
|
+
|
302
|
+
# Returns `true` iff the current severity level allows for the printing of
|
303
|
+
# `DEBUG` messages.
|
304
|
+
#
|
305
|
+
def debug?: () -> bool
|
306
|
+
|
307
|
+
# Log an `ERROR` message.
|
308
|
+
#
|
309
|
+
# See #info for more information.
|
310
|
+
#
|
311
|
+
def error: (?untyped progname) ?{ () -> untyped } -> true
|
312
|
+
|
313
|
+
# Sets the severity to ERROR.
|
314
|
+
#
|
315
|
+
def error!: () -> Integer
|
316
|
+
|
317
|
+
# Returns `true` iff the current severity level allows for the printing of
|
318
|
+
# `ERROR` messages.
|
319
|
+
#
|
320
|
+
def error?: () -> bool
|
321
|
+
|
322
|
+
# Log a `FATAL` message.
|
323
|
+
#
|
324
|
+
# See #info for more information.
|
325
|
+
#
|
326
|
+
def fatal: (?untyped progname) ?{ () -> untyped } -> true
|
327
|
+
|
328
|
+
# Sets the severity to FATAL.
|
329
|
+
#
|
330
|
+
def fatal!: () -> Integer
|
331
|
+
|
332
|
+
# Returns `true` iff the current severity level allows for the printing of
|
333
|
+
# `FATAL` messages.
|
334
|
+
#
|
335
|
+
def fatal?: () -> bool
|
336
|
+
|
337
|
+
# Logging formatter, as a `Proc` that will take four arguments and return the
|
338
|
+
# formatted message. The arguments are:
|
339
|
+
#
|
340
|
+
# `severity`
|
341
|
+
# : The Severity of the log message.
|
342
|
+
# `time`
|
343
|
+
# : A Time instance representing when the message was logged.
|
344
|
+
# `progname`
|
345
|
+
# : The #progname configured, or passed to the logger method.
|
346
|
+
# `msg`
|
347
|
+
# : The *Object* the user passed to the log message; not necessarily a String.
|
348
|
+
#
|
349
|
+
#
|
350
|
+
# The block should return an Object that can be written to the logging device
|
351
|
+
# via `write`. The default formatter is used when no formatter is set.
|
352
|
+
#
|
353
|
+
def formatter: () -> (_Formatter | nil)
|
354
|
+
|
355
|
+
def formatter=: (_Formatter) -> _Formatter
|
356
|
+
| (nil) -> nil
|
357
|
+
|
358
|
+
# Log an `INFO` message.
|
359
|
+
#
|
360
|
+
# `message`
|
361
|
+
# : The message to log; does not need to be a String.
|
362
|
+
# `progname`
|
363
|
+
# : In the block form, this is the #progname to use in the log message. The
|
364
|
+
# default can be set with #progname=.
|
365
|
+
# `block`
|
366
|
+
# : Evaluates to the message to log. This is not evaluated unless the
|
367
|
+
# logger's level is sufficient to log the message. This allows you to
|
368
|
+
# create potentially expensive logging messages that are only called when
|
369
|
+
# the logger is configured to show them.
|
370
|
+
#
|
371
|
+
#
|
372
|
+
# ### Examples
|
373
|
+
#
|
374
|
+
# logger.info("MainApp") { "Received connection from #{ip}" }
|
375
|
+
# # ...
|
376
|
+
# logger.info "Waiting for input from user"
|
377
|
+
# # ...
|
378
|
+
# logger.info { "User typed #{input}" }
|
379
|
+
#
|
380
|
+
# You'll probably stick to the second form above, unless you want to provide a
|
381
|
+
# program name (which you can do with #progname= as well).
|
382
|
+
#
|
383
|
+
# ### Return
|
384
|
+
#
|
385
|
+
# See #add.
|
386
|
+
#
|
387
|
+
def info: (?untyped progname) ?{ () -> untyped } -> true
|
388
|
+
|
389
|
+
# Sets the severity to INFO.
|
390
|
+
#
|
391
|
+
def info!: () -> Integer
|
392
|
+
|
393
|
+
# Returns `true` iff the current severity level allows for the printing of
|
394
|
+
# `INFO` messages.
|
395
|
+
#
|
396
|
+
def info?: () -> bool
|
397
|
+
|
398
|
+
# Logging severity threshold (e.g. `Logger::INFO`).
|
399
|
+
#
|
400
|
+
def level: () -> Integer
|
401
|
+
|
402
|
+
# Set logging severity threshold.
|
403
|
+
#
|
404
|
+
# `severity`
|
405
|
+
# : The Severity of the log message.
|
406
|
+
#
|
407
|
+
#
|
408
|
+
def level=: (Integer | String severity) -> Integer
|
409
|
+
|
410
|
+
alias log add
|
411
|
+
|
412
|
+
# Program name to include in log messages.
|
413
|
+
#
|
414
|
+
def progname: () -> untyped
|
415
|
+
|
416
|
+
def progname=: (untyped) -> untyped
|
417
|
+
|
418
|
+
# ### Args
|
419
|
+
#
|
420
|
+
# `logdev`
|
421
|
+
# : The log device. This is a filename (String) or IO object (typically
|
422
|
+
# `STDOUT`, `STDERR`, or an open file). reopen the same filename if it is
|
423
|
+
# `nil`, do nothing for IO. Default is `nil`.
|
424
|
+
#
|
425
|
+
#
|
426
|
+
# ### Description
|
427
|
+
#
|
428
|
+
# Reopen a log device.
|
429
|
+
#
|
430
|
+
def reopen: () -> self
|
431
|
+
| (logdev?) -> self
|
432
|
+
|
433
|
+
# Logging severity threshold (e.g. `Logger::INFO`).
|
434
|
+
#
|
435
|
+
alias sev_threshold level
|
436
|
+
|
437
|
+
alias sev_threshold= level=
|
438
|
+
|
439
|
+
# Log an `UNKNOWN` message. This will be printed no matter what the logger's
|
440
|
+
# level is.
|
441
|
+
#
|
442
|
+
# See #info for more information.
|
443
|
+
#
|
444
|
+
def unknown: (?untyped progname) ?{ () -> untyped } -> true
|
445
|
+
|
446
|
+
# Log a `WARN` message.
|
447
|
+
#
|
448
|
+
# See #info for more information.
|
449
|
+
#
|
450
|
+
def warn: (?untyped progname) ?{ () -> untyped } -> true
|
451
|
+
|
452
|
+
# Sets the severity to WARN.
|
453
|
+
#
|
454
|
+
def warn!: () -> Integer
|
455
|
+
|
456
|
+
# Returns `true` iff the current severity level allows for the printing of
|
457
|
+
# `WARN` messages.
|
458
|
+
#
|
459
|
+
def warn?: () -> bool
|
460
|
+
|
461
|
+
private
|
462
|
+
|
463
|
+
def format_message: (String severity, Time datetime, untyped progname, untyped msg) -> _ToS
|
464
|
+
|
465
|
+
def format_severity: (Integer severity) -> String
|
466
|
+
|
467
|
+
# ### Args
|
468
|
+
#
|
469
|
+
# `logdev`
|
470
|
+
# : The log device. This is a filename (String) or IO object (typically
|
471
|
+
# `STDOUT`, `STDERR`, or an open file).
|
472
|
+
# `shift_age`
|
473
|
+
# : Number of old log files to keep, **or** frequency of rotation (`daily`,
|
474
|
+
# `weekly` or `monthly`). Default value is 0, which disables log file
|
475
|
+
# rotation.
|
476
|
+
# `shift_size`
|
477
|
+
# : Maximum logfile size in bytes (only applies when `shift_age` is a positive
|
478
|
+
# Integer). Defaults to `1048576` (1MB).
|
479
|
+
# `level`
|
480
|
+
# : Logging severity threshold. Default values is Logger::DEBUG.
|
481
|
+
# `progname`
|
482
|
+
# : Program name to include in log messages. Default value is nil.
|
483
|
+
# `formatter`
|
484
|
+
# : Logging formatter. Default values is an instance of Logger::Formatter.
|
485
|
+
# `datetime_format`
|
486
|
+
# : Date and time format. Default value is '%Y-%m-%d %H:%M:%S'.
|
487
|
+
# `binmode`
|
488
|
+
# : Use binary mode on the log device. Default value is false.
|
489
|
+
# `shift_period_suffix`
|
490
|
+
# : The log file suffix format for `daily`, `weekly` or `monthly` rotation.
|
491
|
+
# Default is '%Y%m%d'.
|
492
|
+
#
|
493
|
+
#
|
494
|
+
# ### Description
|
495
|
+
#
|
496
|
+
# Create an instance.
|
497
|
+
#
|
498
|
+
def initialize: (logdev logdev, ?Numeric | String shift_age, ?Integer shift_size, ?shift_period_suffix: String, ?binmode: bool, ?datetime_format: String, ?formatter: _Formatter, ?progname: String, ?level: Integer) -> void
|
499
|
+
end
|
500
|
+
|
501
|
+
Logger::ProgName: String
|
502
|
+
|
503
|
+
# Severity label for logging (max 5 chars).
|
504
|
+
#
|
505
|
+
Logger::SEV_LABEL: Array[String]
|
506
|
+
|
507
|
+
Logger::VERSION: String
|