dumb-logger 1.0.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 +7 -0
- data/.gitignore +14 -0
- data/.yardopts +7 -0
- data/Gemfile +37 -0
- data/LICENCE.md +203 -0
- data/README.md +166 -0
- data/Rakefile +53 -0
- data/dumb-logger.gemspec +50 -0
- data/features/attributes.feature +70 -0
- data/features/bitmasks.feature +44 -0
- data/features/labels.feature +39 -0
- data/features/levels.feature +46 -0
- data/features/output.feature +136 -0
- data/features/step_definitions/parts.rb +97 -0
- data/features/support/env.rb +107 -0
- data/features/support/hooks.rb +3 -0
- data/lib/dumb-logger.rb +567 -0
- data/lib/dumb-logger/version.rb +66 -0
- data/lib/dumblogger.rb +21 -0
- data/rel-eng/custom/GemTagger.py +71 -0
- data/rel-eng/packages/.readme +3 -0
- data/rel-eng/tito.props +6 -0
- data/rubygem-dumb-logger.spec +86 -0
- metadata +117 -0
@@ -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
|
data/lib/dumb-logger.rb
ADDED
@@ -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
|