dumb-logger 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ Proc.new {
2
+ libdir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
3
+ $:.replace($: | [ libdir ])
4
+ }.call
5
+ require('dumb-logger')
6
+ require('aruba/cucumber')
7
+ require('debugger')
8
+
9
+ # @private
10
+ #
11
+ # This module provides helper methods for the Cucumber testing suite.
12
+ #
13
+ module DumbLogger_TestSupport
14
+
15
+ #
16
+ # Suggested by from https://github.com/codegram/spinach
17
+ #
18
+
19
+ #
20
+ # Provide helpers to wrap IO streams by temporarily redirecting them
21
+ # to a StringIO object.
22
+ #
23
+
24
+ # @private
25
+ #
26
+ # Capture IO to one or more streams during block execution.
27
+ #
28
+ # @param [Array<Symbol,String>] stms
29
+ # One or more stream identifiers to be captured. Symbols like `:$stderr`
30
+ # and strings like `"$stdout"` are acceptable.
31
+ #
32
+ # @yield
33
+ # Block for which stream traffic should be captured.
34
+ #
35
+ # @return [String,Hash<<String,Symbol>=>String>]
36
+ # If only one stream was specified, the result will be a simple string.
37
+ #
38
+ def capture_streams(*stms, &block)
39
+ if (stms.any? { |o| (! (o.kind_of?(String) || o.kind_of?(Symbol))) })
40
+ raise ArgumentError.new('streams must be strings or symbols')
41
+ end
42
+ ovalues = stms.inject({}) { |memo,stm|
43
+ stmname = stm.to_s
44
+ stmobj = eval(stmname)
45
+ unless (stmobj.kind_of?(IO))
46
+ raise ArgumentError.new("'#{stm.inspect}' is not an IO object")
47
+ end
48
+ stat = {
49
+ :persistent => stmobj,
50
+ :temporary => StringIO.new,
51
+ }
52
+ eval("#{stmname} = stat[:temporary]")
53
+ memo[stm] = stat
54
+ memo
55
+ }
56
+ #
57
+ # Make sure we restore the streams to their original settings if an
58
+ # exception gets raised. We don't care about the exception, just
59
+ # making sure the streams are as they were when we were called.
60
+ #
61
+ rvalues = stms.map { |o| {o => ''} }.reduce(:merge)
62
+ begin
63
+ yield
64
+ ensure
65
+ rvalues = ovalues.inject({}) { |memo,(stm,stat)|
66
+ eval("#{stm.to_s} = stat[:persistent]")
67
+ memo[stm] = stat[:temporary].string
68
+ memo
69
+ }
70
+ end
71
+ rvalues = rvalues.values.first if (rvalues.count == 1)
72
+ return rvalues
73
+ end # def capture_streams
74
+
75
+ # @private
76
+ #
77
+ # Capture standard output activity as a string.
78
+ #
79
+ # @yield
80
+ # Block during the execution of which output is to be captured.
81
+ #
82
+ # @return [String]
83
+ # Returns whatever was sent to `$stdout` during the block's execution.
84
+ #
85
+ def capture_stdout(&block)
86
+ return capture_stream(:$stdout, &block)
87
+ end # def capture_stdout
88
+
89
+ # @private
90
+ #
91
+ # Capture standard error activity as a string.
92
+ #
93
+ # @yield (see #capture_stdout)
94
+ #
95
+ # @return [String]
96
+ # Returns whatever was sent to `$stderr` during the block's execution.
97
+ #
98
+ # @see #capture_stdout
99
+ # @see #capture_streams
100
+ #
101
+ def capture_stderr(&block)
102
+ return capture_stream(:$stderr, &block)
103
+ end # def capture_stderr
104
+
105
+ end # module DumbLogger_TestSupport
106
+
107
+ include DumbLogger_TestSupport
@@ -0,0 +1,3 @@
1
+ Before() do
2
+ @exception_raised = nil
3
+ end
@@ -0,0 +1,567 @@
1
+ # -*- coding: utf-8 -*-
2
+ #--
3
+ # Copyright © 2015 Ken Coar
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #++
17
+
18
+ require('dumb-logger/version')
19
+
20
+ #
21
+ # This is just a one-off class to allow reporting things to stderr
22
+ # according to the verbosity level. Very simple, not a complete
23
+ # logger at all.
24
+ #
25
+ # @todo
26
+ # Allow assignment of prefices to levels the way we now do labels.
27
+ # Will probably only work with level-based reporting, since
28
+ # mask-based reports may get transmitted due to a mask `AND` that
29
+ # doesn't match any named masks.
30
+ #
31
+ # @todo
32
+ # Add a `:seek_to_eof` option so that text written to a sink is
33
+ # *always* possitioned after any data written by other processes.
34
+ # (Except on the first write to a file in truncation `:append => false`
35
+ # mode, of course.)
36
+ #
37
+ class DumbLogger
38
+
39
+ #
40
+ # class DumbLogger eigenclass.
41
+ #
42
+ # Since we may have had to open up a file, make sure closing it
43
+ # again is part of the instance teardown process.
44
+ #
45
+ class << self
46
+ #
47
+ # If we have a currently open output stream that needs to be
48
+ # closed (usually because we opened it ourself), close it as part
49
+ # of the DumbLogger object teardown.
50
+ #
51
+ # @param [DumbLogger] obj
52
+ # Instance being torn down ('destructed').
53
+ #
54
+ def finalize(obj)
55
+ if (obj.instance_variable_get(:@options)[:needs_close])
56
+ obj.sink.close
57
+ end
58
+ end # def finalize
59
+ end # class DumbLogger eigenclass
60
+
61
+ #
62
+ # Message flag for "do not append a newline".
63
+ #
64
+ NO_NL = :no_nl
65
+
66
+ #
67
+ # Treat loglevel numbers as bitmasks.
68
+ #
69
+ USE_BITMASK = :loglevels_are_bitmasks
70
+
71
+ #
72
+ # Treat loglevel numbers as actual levels.
73
+ #
74
+ USE_LEVELS = :loglevels_are_numbers
75
+
76
+ #
77
+ # Special sink values, which will get evaluated on every transmission.
78
+ #
79
+ SPECIAL_SINKS = [
80
+ :$stdout,
81
+ :$stderr,
82
+ ]
83
+
84
+ #
85
+ # @!attribute [rw] level_style
86
+ #
87
+ # Control whether loglevels are treated as ascending integers, or as
88
+ # bitmasks.
89
+ #
90
+ # @return [Symbol]
91
+ # Returns the current setting (either {USE_LEVELS} or {USE_BITMASK}).
92
+ #
93
+ # @raise [ArgumentError]
94
+ # Raises an *ArgumentError* exception if the style isn't
95
+ # recognised.
96
+ #
97
+ def level_style
98
+ return @options[:level_style]
99
+ end # def level_style
100
+
101
+ def level_style=(style)
102
+ unless ([ USE_LEVELS, USE_BITMASK ].include?(style))
103
+ raise ArgumentError.new('invalid loglevel style')
104
+ end
105
+ @options[:level_style] = style
106
+ end # def level_style=
107
+
108
+ #
109
+ # @!attribute [rw] loglevel
110
+ #
111
+ # If loglevels are being treated as integers, this is the maximum
112
+ # level that will reported; that is, if a message is submitted with
113
+ # level 7, but the loglevel is 5, the message will *not* be
114
+ # reported.
115
+ #
116
+ # If loglevels are being treated as bitmasks, messages will be
117
+ # reported only if submitted with a loglevel which has at least one
118
+ # bit set that is also set in the instance loglevel.
119
+ #
120
+ # When used as an attribute writer (*e.g.*, `obj.loglevel = val`),
121
+ # the argument will be treated as an integer.
122
+ #
123
+ # @return [Integer]
124
+ # Returns the maximum loglevel/logging mask in effect henceforth.
125
+ #
126
+ # @raise [ArgumentError]
127
+ # Raise an *ArgumentError* exception if the new value cannot be
128
+ # converted to an integer.
129
+ def loglevel=(arg)
130
+ unless (arg.respond_to?(:to_i))
131
+ raise ArgumentError.new('loglevels are integers')
132
+ end
133
+ @options[:loglevel] = arg.to_i
134
+ return @options[:loglevel]
135
+ end # def loglevel=
136
+ alias_method(:logmask=, :loglevel=)
137
+
138
+ def loglevel
139
+ return @options[:loglevel].to_i
140
+ end # def loglevel
141
+ alias_method(:logmask, :loglevel)
142
+
143
+ #
144
+ # Returns `true` if loglevel numbers are interpreted as integers
145
+ # rather than bitmasks. (See {#level_style} for more information.)
146
+ #
147
+ # @return [Boolean]
148
+ # Returns `true` if loglevels are regarded as integers rather than
149
+ # bitmasks, or `false` otherwise.
150
+ #
151
+ # @see #log_masks?
152
+ # @see #level_style
153
+ #
154
+ def log_levels?
155
+ return (@options[:level_style] == USE_LEVELS) ? true : false
156
+ end # def log_levels?
157
+
158
+ #
159
+ # Returns `true` if loglevel numbers are interpreted as bitmasks
160
+ # rather than integers. (See {#level_style} for more information.)
161
+ #
162
+ # Determine how loglevel numbers are interpreted. (See
163
+ # {#level_style} for more information.)
164
+ #
165
+ # Returns `true` if they're treated as bitmasks rather than integers.
166
+ #
167
+ # @return [Boolean]
168
+ # Returns `true` if loglevels are regarded as bitmasks rather than
169
+ # integers, or `false` otherwise.
170
+ #
171
+ # @see #log_levels?
172
+ # @see #level_style
173
+ #
174
+ def log_masks?
175
+ return (@options[:level_style] == USE_BITMASK) ? true : false
176
+ end # def log_masks?
177
+
178
+ #
179
+ # Allow the user to assign labels to different log levels or mask
180
+ # combinations. All labels will be downcased and converted to
181
+ # symbols.
182
+ #
183
+ # In addition, the labels are added to the instance as methods that
184
+ # will log messages with the specified level.
185
+ #
186
+ # @see #labeled_levels
187
+ #
188
+ # @param [Hash{String,Symbol=>Integer}] labelhash
189
+ # Hash of names or symbols and the integer log levels/masks they're
190
+ # labelling.
191
+ #
192
+ # @return [Hash<Symbol,Integer>]
193
+ # Returns a hash of the labels (as symbols) and levels/masks that
194
+ # have been assigned.
195
+ #
196
+ # @raise [ArgumentError]
197
+ # Raises an *ArgumentError* exception if the argument isn't a hash
198
+ # with integer values.
199
+ #
200
+ def label_levels(labelhash)
201
+ unless (labelhash.kind_of?(Hash))
202
+ raise ArgumentError.new('level labels must be supplied as a hash')
203
+ end
204
+ unless (labelhash.values.all? { |o| o.kind_of?(Integer) })
205
+ raise ArgumentError.new('labeled levels must be integers')
206
+ end
207
+ newhash = labelhash.inject({}) { |memo,(label,level)|
208
+ label_sym = label.to_s.downcase.to_sym
209
+ memo[label_sym] = level
210
+ memo
211
+ }
212
+ @options[:labels].merge!(newhash)
213
+ newhash.each do |label,level|
214
+ self.define_singleton_method(label) do |*args|
215
+ (scratch, newargs) = args.partition { |o| o.kind_of?(Integer) }
216
+ return self.message(level, *newargs)
217
+ end
218
+ end
219
+ return newhash
220
+ end # def label_levels
221
+
222
+ #
223
+ # Return a list of all the levels or bitmasks that have been labeled.
224
+ # The return value is suitable for use as input to the #label_levels
225
+ # method of this or another instance of this class.
226
+ #
227
+ # @see #label_levels
228
+ #
229
+ # @return [Hash<Symbol,Integer>]
230
+ # Returns a hash of labels (as symbols) and the log levels they
231
+ # identify.
232
+ #
233
+ def labeled_levels
234
+ return Hash[@options[:labels].sort].freeze
235
+ end # def labeled_levels
236
+
237
+ # @private
238
+ #
239
+ # List of option keys settable in the constructor.
240
+ #
241
+ CONSTRUCTOR_OPTIONS = [
242
+ :append,
243
+ :level_style,
244
+ :loglevel,
245
+ :logmask,
246
+ :labels,
247
+ :prefix,
248
+ :sink,
249
+ ]
250
+
251
+ #
252
+ # @!attribute [r] options
253
+ #
254
+ # Options controlling various aspects of `DumbLogger`'s operation.
255
+ #
256
+ # @return [Hash]
257
+ # Returns current set of DumbLogger options for the instance.
258
+ #
259
+ def options
260
+ return @options.dup.freeze
261
+ end # def options
262
+
263
+ #
264
+ # @!attribute [rw] prefix
265
+ #
266
+ # Prefix string to be inserted at the beginning of each line of
267
+ # output we emit.
268
+ #
269
+ # @note
270
+ # This can be overridden at runtime *via* the `:prefix` option hash
271
+ # element to the {#message} method (*q.v.*).
272
+ #
273
+ # @return [String]
274
+ # Sets or returns the prefix string to be used henceforth.
275
+ #
276
+ def prefix
277
+ return @options[:prefix]
278
+ end # def prefix
279
+
280
+ def prefix=(arg)
281
+ @options[:prefix] = arg.to_s
282
+ return self.prefix
283
+ end # def prefix=
284
+
285
+ #
286
+ # Re-open the current sink (unless it's a stream). This may be
287
+ # useful if you want to stop and truncate in the middle of logging
288
+ # (by changing the {#append=} option), or something.
289
+ #
290
+ # @return [Boolean]
291
+ # Returns `true` if the sink was successfully re-opened, or `false`
292
+ # otherwise (such as if it's a stream).
293
+ #
294
+ # @raise [IOError]
295
+ # Raises an *IOError* exception if the sink stream is already
296
+ # closed.
297
+ #
298
+ def reopen
299
+ return false unless (@options[:needs_close] && self.sink.kind_of?(String))
300
+ raise IOError.new('sink stream is already closed') if (@sink_io.closed?)
301
+ @sink_io.reopen(self.sink, (self.append? ? 'a' : 'w'))
302
+ @sink_io.sync = true if (@sink_io.respond_to?(:sync=))
303
+ return true
304
+ end # def reopen
305
+
306
+ #
307
+ # @!attribute [rw] sink
308
+ #
309
+ # Sets or returns the sink to which we send our messages.
310
+ #
311
+ # When setting the sink, the value can be an IO instance, a special
312
+ # symbol, or a string. If a string, the `:append` flag from the
313
+ # instance options (see {#append=} and {#append?}) is used to
314
+ # determine whether the file will be rewritten from the beginning,
315
+ # or just have text appended to it.
316
+ #
317
+ # Sinking to one of the special symbols (`:$stderr` or `:$stdout`;
318
+ # see {SPECIAL_SINKS}) results in the sink being re-evaluated at
319
+ # each call to {#message}. This is useful if these streams might be
320
+ # altered after the logger has been instantiated.
321
+ #
322
+ # @note
323
+ # File sink contents may appear unpredictable under the following
324
+ # conditions:
325
+ # * Messages are being sinked to a file, **and**
326
+ # * the file is being accessed by one or more other processes, **and**
327
+ # * changes to the file are interleaved between those made by the
328
+ # `DumbLogger` {#message} method and activity by the other
329
+ # process(es).
330
+ #
331
+ # @return [IO,String,Symbol]
332
+ # Returns the sink path, special name, or IO object.
333
+ #
334
+ def sink
335
+ return @options[:sink]
336
+ end # def sink
337
+
338
+ def sink=(arg)
339
+ if (@options[:needs_close] \
340
+ && @sink_io.respond_to?(:close) \
341
+ && (! [ self.sink, @sink_io ].include?(arg)))
342
+ @sink_io.close unless (@sink_io.closed?)
343
+ @sink_io = nil
344
+ end
345
+
346
+ @options[:volatile] = false
347
+ if (arg.kind_of?(IO))
348
+ #
349
+ # If it's an IO, then we assume it's already open.
350
+ #
351
+ @options[:sink] = @sink_io = arg
352
+ @options[:needs_close] = false
353
+ elsif (SPECIAL_SINKS.include?(arg))
354
+ #
355
+ # If it's one of our special symbols, we don't actually do
356
+ # anything except record the fact -- because they get
357
+ # interpreted at each #message call.
358
+ #
359
+ @options[:sink] = arg
360
+ @sink_io = nil
361
+ @options[:volatile] = true
362
+ else
363
+ #
364
+ # If it's a string, we treat it as a file name, open it, and
365
+ # flag it for closing later.
366
+ #
367
+ @options[:sink] = arg
368
+ @sink_io = File.open(@options[:sink], (self.append? ? 'a' : 'w'))
369
+ @options[:needs_close] = true
370
+ end
371
+ @sink_io.sync = true if (@sink_io.respond_to?(:sync=))
372
+ return self.sink
373
+ end # def sink=
374
+
375
+ #
376
+ # Constructor.
377
+ #
378
+ # @param [Hash] args
379
+ # @option args [Boolean] :append (true)
380
+ # If true, any **files** opened will have transmitted text appended to
381
+ # them. See {#append=}.
382
+ # @note
383
+ # Streams are **always** treated as being in `:append => true` mode.
384
+ # @option args [String] :prefix ('')
385
+ # String to insert at the beginning of each line of report text.
386
+ # See {#prefix=}.
387
+ # @option args [IO,String] :sink (:$stderr)
388
+ # Where reports should be sent. See {#sink=}.
389
+ # @option args [Integer] :loglevel (0)
390
+ # Maximum log level for reports. See {#loglevel=}.
391
+ # @option args [Integer] :logmask (0)
392
+ # Alias for `:loglevel`.
393
+ # @option args [Symbol] :level_style (USE_LEVELS)
394
+ # Whether message loglevels should be treated as integer levels or
395
+ # as bitmasks. See {#level_style=}.
396
+ #
397
+ # @raise [ArgumentError]
398
+ # Raises an *ArgumentError* exception if the argument isn't a hash.
399
+ #
400
+ def initialize(args={})
401
+ unless (args.kind_of?(Hash))
402
+ raise ArgumentError.new("#{self.class.name}.new requires a hash")
403
+ end
404
+ @options = {
405
+ :labels => {},
406
+ }
407
+ #
408
+ # Here are the default settings for a new instance with no
409
+ # arguments. We put 'em here so they don't show up in docco under
410
+ # the Constants heading.
411
+ #
412
+ default_opts = {
413
+ :append => true,
414
+ :level_style => USE_LEVELS,
415
+ :loglevel => 0,
416
+ :prefix => '',
417
+ :sink => :$stderr,
418
+ }
419
+ #
420
+ # Make a new hash merging the user arguments on top of the
421
+ # defaults. This avoids altering the user's hash.
422
+ #
423
+ temp_opts = default_opts.merge(args)
424
+ #
425
+ # Throw out any option keys we don't recognise.
426
+ #
427
+ temp_opts.delete_if { |k,v| (! CONSTRUCTOR_OPTIONS.include?(k)) }
428
+ #
429
+ # Do loglevel stuff. We're going to run this through the writer
430
+ # method, since it has argument validation code.
431
+ #
432
+ # If the user wants to use bitmasks, then the :logmask argument
433
+ # key takes precedence over the :loglevel one.
434
+ #
435
+ self.level_style = temp_opts[:level_style]
436
+ temp_opts.delete(:level_style)
437
+ if (self.log_masks?)
438
+ temp_opts[:loglevel] = temp_opts[:logmask] if (temp_opts.key?(:logmask))
439
+ end
440
+ temp_opts.delete(:logmask)
441
+ #
442
+ # Now go through the remaining options and handle them. If the
443
+ # option has an associated writer method, call it -- otherwise,
444
+ # just load it into the `@options` hash.
445
+ #
446
+ temp_opts.each do |opt,val|
447
+ wmethod = (opt.to_s + '=').to_sym
448
+ if (self.respond_to?(wmethod))
449
+ self.send(wmethod, val)
450
+ else
451
+ @options[opt] = val
452
+ end
453
+ end
454
+ end # def initialize
455
+
456
+ #
457
+ # @!attribute [rw] append
458
+ #
459
+ # Controls the behaviour of sink files (but *not* IO streams). If
460
+ # `true`, report text will be added to the end of any existing
461
+ # contents; if `false`, files will be truncated and reports will
462
+ # begin at position `0`.
463
+ #
464
+ # @note
465
+ # This setting is only important when a sink is being activated,
466
+ # such as `DumbLogger` object instantiation or because of a call to
467
+ # {#sink=}, and it controls the position of the first write to the
468
+ # sink. Once a sink is activated (opened), writing continues
469
+ # sequentially from that point.
470
+ #
471
+ # @note
472
+ # Setting this attribute *only* affects **files** opened by
473
+ # `DumbLogger`. Stream sinks are *always* in append-mode. As long
474
+ # as the sink is a stream, this setting will be ignored -- but it
475
+ # will become active whenever the sink becomes a file.
476
+ #
477
+ # @return [Boolean]
478
+ # Sets or returns the file append-on-write control value.
479
+ #
480
+ def append
481
+ return (@options[:append] ? true : false)
482
+ end # def append
483
+
484
+ def append=(arg)
485
+ @options[:append] = (arg ? true : false)
486
+ return self.append
487
+ end # def append=
488
+
489
+ #
490
+ # @return [Boolean]
491
+ # Returns `true` if new sink files opened by the instance will have
492
+ # report text appended to them.
493
+ #
494
+ def append?
495
+ return self.append
496
+ end # def append?
497
+
498
+ #
499
+ # Submit a message for possible transmission to the current sink.
500
+ # The argument is an array of arrays, strings, integers, and/or
501
+ # symbols. Reports with a loglevel of zero (the default) are
502
+ # *always* transmitted.
503
+ #
504
+ # @param [Array<Array,String,Symbol,Integer,Hash>] args
505
+ # * The last integer in the array will be treated as the report's
506
+ # loglevel; default is `0`.
507
+ # * Any `Array` elements in the arguments will be merged and the
508
+ # values interpreted as level labels (see {#label_levels}). If
509
+ # loglevels are bitmasks, the labeled levels are ORed together;
510
+ # otherwise the lowest labeled level will be used for the message.
511
+ # * Any `Hash` elements in the array will be merged and will
512
+ # temporarily override instance-wide options -- *e.g.*,
513
+ # `{ :prefix => 'alt' }` .
514
+ # * If the `DumbLogger::NO_NL` value (a `Symbol`) appears in the
515
+ # array, or a hash element of `:return => false`, the report will
516
+ # not include a terminating newline (useful for
517
+ # `"progress:..done"` reports).
518
+ # * Any strings in the array are treated as text to be reported,
519
+ # one _per_ line. Each line will begin with the value of
520
+ # #prefix, and only the final line is subject to the
521
+ # DumbLogger::NO_NL special-casing.
522
+ #
523
+ # @return [nil,Integer]
524
+ # Returns either `nil` if the message's loglevel is higher than the
525
+ # reporting level, or the level of the report.
526
+ #
527
+ # If integer levels are being used, a non-`nil` return value is
528
+ # that of the message. If bitmask levels are being used, the
529
+ # return value is a mask of the active level bits that applied to
530
+ # the message -- *i.e.*, `msg_bits & logging_mask` .
531
+ #
532
+ def message(*args)
533
+ #
534
+ # Extract any symbols, hashes, and integers from the argument
535
+ # list. This makes the calling format very flexible.
536
+ #
537
+ (symopts, args) = args.partition { |elt| elt.kind_of?(Symbol) }
538
+ symlevels = (symopts & self.labeled_levels.keys).map { |o|
539
+ self.labeled_levels[o]
540
+ }.compact
541
+ (hashopts, args) = args.partition { |elt| elt.kind_of?(Hash) }
542
+ hashopts = hashopts.reduce(:merge) || {}
543
+ (level, args) = args.partition { |elt| elt.kind_of?(Integer) }
544
+ level = level.last || hashopts[:level] || hashopts[:mask] || 0
545
+ cur_loglevel = self.loglevel
546
+ cur_style = self.level_style
547
+ unless (level.zero?)
548
+ if (self.log_levels?)
549
+ return nil if ([ cur_loglevel, *symlevels ].min < level)
550
+ elsif (self.log_masks?)
551
+ level = [ level, *symlevels ].reduce(:|) & cur_loglevel
552
+ return nil if (level.zero?)
553
+ end
554
+ end
555
+ prefix_text = hashopts[:prefix] || self.prefix
556
+ text = prefix_text + args.join("\n#{prefix_text}")
557
+ unless (hashopts.key?(:newline))
558
+ hashopts[:newline]= (! symopts.include?(NO_NL))
559
+ end
560
+ text << "\n" if (hashopts[:newline])
561
+ stream = @options[:volatile] ? eval(self.sink.to_s) : @sink_io
562
+ stream.write(text)
563
+ stream.flush if (@options[:volatile])
564
+ return level
565
+ end # def message
566
+
567
+ end # class DumbLogger