dumb-logger 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|