mlogger 1.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 97f10839f0123451cded5029fccd41450b3a6fcc
4
+ data.tar.gz: 1ca522b5e1fb344790afa3ea9fe83858051cc55e
5
+ SHA512:
6
+ metadata.gz: 1486c2954255917e34255e716607f842f326db1d243b91526ba8326b2c3f6cfd6323eae53f865c749d57750e089cc25bd53849e94acb7bd375769992cdc22927
7
+ data.tar.gz: b4086c63b1f9d780e5e1b99f6ce2d81a56b2c4fcf7dd7a32c106ec6c6f847bbc1123eb5e531949bb1741e37b381bf1196ac946d95bde0848b65b5456eec09ddb
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .idea
2
+ mlogger.iml
3
+ pkg/
4
+ /**/*.log
5
+ /**/*.log.*
6
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://gems.ruby-china.org/"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in mlogger.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Hdzi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Simple logging utility with multiIO
2
+
3
+ ## CAUTION
4
+
5
+ mlogger is based on [logger](https://github.com/nahi/logger) that add some features.
6
+
7
+ ## Document
8
+ See RDoc in lib/mlogger.rb. To get RDoc in html format, exec 'rake doc'.
9
+
10
+ ## Author
11
+
12
+ ### Base [logger](https://github.com/nahi/logger)
13
+ * NAKAMURA, Hiroshi a.k.a. NaHi
14
+ * nahi@ruby-lang.org
15
+ * Documentation: Gavin Sinclair
16
+
17
+ ### New Features
18
+ * Hdzi
19
+ * taojinhou@qq.com
20
+
21
+ Legal notice
22
+ This program is copyrighted free software by NAKAMURA, Hiroshi. You can
23
+ redistribute it and/or modify it under the same terms of Ruby's license;
24
+ either the dual license version in 2003, or any later version.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "mlogger"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ class MLogger
2
+ VERSION = "1.0.2"
3
+ end
data/lib/mlogger.rb ADDED
@@ -0,0 +1,650 @@
1
+ # mlogger.rb - simple logging utility with multiIO
2
+ # Copyright (C) 2000-2003, 2005, 2008, 2011 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ # Copyright (C) 2018 Hdzi <taojinhou@qq.com>
4
+ #
5
+ # Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair
6
+ # License::
7
+ # You can redistribute it and/or modify it under the same terms of Ruby's
8
+ # license; either the dual license version in 2003, or any later version.
9
+ # Revision:: $Id$
10
+ #
11
+ # A simple system for logging messages. See Logger for more documentation.
12
+
13
+
14
+ require 'monitor'
15
+ require 'mlogger/version'
16
+
17
+ # == Description
18
+ #
19
+ # The Logger class provides a simple but sophisticated logging utility that
20
+ # you can use to output messages.
21
+ #
22
+ # The messages have associated
23
+ # levels, such as +INFO+ or +ERROR+ that indicate their importance.
24
+ # You can then give the Logger a level, and only messages at that
25
+ # level of higher will be printed.
26
+ #
27
+ # The levels are:
28
+ #
29
+ # +FATAL+:: an unhandleable error that results in a program crash
30
+ # +ERROR+:: a handleable error condition
31
+ # +WARN+:: a warning
32
+ # +INFO+:: generic (useful) information about system operation
33
+ # +DEBUG+:: low-level information for developers
34
+ #
35
+ # For instance, in a production system, you may have your Logger set to
36
+ # +INFO+ or even +WARN+
37
+ # When you are developing the system, however, you probably
38
+ # want to know about the program's internal state, and would set the Logger to
39
+ # +DEBUG+.
40
+ #
41
+ # *Note*: Logger does not escape or sanitize any messages passed to it.
42
+ # Developers should be aware of when potentially malicious data (user-input)
43
+ # is passed to Logger, and manually escape the untrusted data:
44
+ #
45
+ # logger.info("User-input: #{input.dump}")
46
+ # logger.info("User-input: %p" % input)
47
+ #
48
+ # You can use #formatter= for escaping all data.
49
+ #
50
+ # original_formatter = MLogger::Formatter.new
51
+ # logger.formatter = proc { |severity, datetime, progname, msg|
52
+ # original_formatter.call(severity, datetime, progname, msg.dump)
53
+ # }
54
+ # logger.info(input)
55
+ #
56
+ # === Example
57
+ #
58
+ # This creates a logger to the standard output stream, with a level of +WARN+
59
+ #
60
+ # log = MLogger.new(STDOUT)
61
+ # log.level = MLogger::WARN
62
+ # # as run: log.level = :warn
63
+ #
64
+ # log.debug("Created logger")
65
+ # log.info("Program started")
66
+ # log.warn("Nothing to do!")
67
+ #
68
+ # begin
69
+ # File.each_line(path) do |line|
70
+ # unless line =~ /^(\w+) = (.*)$/
71
+ # log.error("Line in wrong format: #{line}")
72
+ # end
73
+ # end
74
+ # rescue => err
75
+ # log.fatal("Caught exception; exiting")
76
+ # log.fatal(err)
77
+ # end
78
+ #
79
+ # Because the Logger's level is set to +WARN+, only the warning, error, and
80
+ # fatal messages are recorded. The debug and info messages are silently
81
+ # discarded.
82
+ #
83
+ # === Features
84
+ #
85
+ # There are several interesting features that Logger provides, like
86
+ # auto-rolling of log files, setting the format of log messages, and
87
+ # specifying a program name in conjunction with the message. The next section
88
+ # shows you how to achieve these things.
89
+ #
90
+ #
91
+ # == HOWTOs
92
+ #
93
+ # === How to create a logger
94
+ #
95
+ # The options below give you various choices, in more or less increasing
96
+ # complexity.
97
+ #
98
+ # 1. Create a logger which logs messages to STDERR/STDOUT.
99
+ #
100
+ # logger = MLogger.new(STDERR)
101
+ # logger = MLogger.new(STDOUT)
102
+ #
103
+ # 2. Create a logger for the file which has the specified name.
104
+ #
105
+ # logger = MLogger.new('logfile.log')
106
+ #
107
+ # 3. Create a logger for the specified file.
108
+ #
109
+ # file = File.open('foo.log', File::WRONLY | File::APPEND)
110
+ # # To create new (and to remove old) logfile, add File::CREAT like;
111
+ # # file = open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
112
+ # logger = MLogger.new(file)
113
+ #
114
+ # 4. Create a logger which ages logfile once it reaches a certain size. Leave
115
+ # 10 "old log files" and each file is about 1,024,000 bytes.
116
+ #
117
+ # logger = MLogger.new(['foo.log', {age: 10, size: 1024000}])
118
+ #
119
+ # 5. Create a logger which ages logfile daily/weekly/monthly.
120
+ #
121
+ # logger = MLogger.new(['foo.log', {age: 'daily'}])
122
+ # logger = MLogger.new(['foo.log', {age: 'weekly'}])
123
+ # logger = MLogger.new(['foo.log', {age: 'monthly'}])
124
+ #
125
+ # 6. Create a logger with multiIO
126
+ #
127
+ # logger = MLogger.new STDOUT, STDERR
128
+ # logger = MLogger.new ['foo.log', {age: 'daily'}], ['foo2.log', {age: 'weekly'}]
129
+ # logger = MLogger.new STDOUT, 'foo.log'
130
+ # logger = MLogger.new STDOUT, ['foo.log', {age: 'daily'}]
131
+ #
132
+ # 7. Create a logger with different log levels with different IO.
133
+ #
134
+ # logger = MLogger.new STDOUT # default io
135
+ # logger.change_level_logdev MLogger::WARN, STDERR
136
+ # logger.change_level_logdev [:warn, :error], STDERR
137
+ #
138
+ # === How to log a message
139
+ #
140
+ # Notice the different methods (+fatal+, +error+, +info+) being used to log
141
+ # messages of various levels? Other methods in this family are +warn+ and
142
+ # +debug+. +add+ is used below to log a message of an arbitrary (perhaps
143
+ # dynamic) level.
144
+ #
145
+ # 1. Message in block.
146
+ #
147
+ # logger.fatal { "Argument 'foo' not given." }
148
+ #
149
+ # 2. Message as a string.
150
+ #
151
+ # logger.error "Argument #{ @foo } mismatch."
152
+ #
153
+ # 3. With progname.
154
+ #
155
+ # logger.info('initialize') { "Initializing..." }
156
+ #
157
+ #
158
+ # The block form allows you to create potentially complex log messages,
159
+ # but to delay their evaluation until and unless the message is
160
+ # logged. For example, if we have the following:
161
+ #
162
+ # logger.debug { "This is a " + potentially + " expensive operation" }
163
+ #
164
+ # If the logger's level is +INFO+ or higher, no debug messages will be logged,
165
+ # and the entire block will not even be evaluated. Compare to this:
166
+ #
167
+ # logger.debug("This is a " + potentially + " expensive operation")
168
+ #
169
+ # Here, the string concatenation is done every time, even if the log
170
+ # level is not set to show the debug message.
171
+ #
172
+ # === How to close a logger
173
+ #
174
+ # logger.close
175
+ #
176
+ # === Setting severity threshold
177
+ #
178
+ # 1. Original interface.
179
+ #
180
+ # logger.sev_threshold = MLogger::WARN
181
+ #
182
+ # 2. Log4r (somewhat) compatible interface.
183
+ #
184
+ # logger.level = MLogger::INFO
185
+ #
186
+ # DEBUG < INFO < WARN < ERROR < FATAL
187
+ #
188
+ #
189
+ # == Format
190
+ #
191
+ # Log messages are rendered in the output stream in a certain format by
192
+ # default. The default format and a sample are shown below:
193
+ #
194
+ # Log format:
195
+ # SeverityID, [Date Time mSec #pid] SeverityLabel -- ProgName: message
196
+ #
197
+ # Log sample:
198
+ # I, [Wed Mar 03 02:34:24 JST 1999 895701 #19074] INFO -- Main: info.
199
+ #
200
+ # You may change the date and time format via #datetime_format=
201
+ #
202
+ # logger.datetime_format = "%Y-%m-%d %H:%M:%S"
203
+ # # e.g. "2004-01-03 00:54:26"
204
+ #
205
+ # Or, you may change the overall format with #formatter= method.
206
+ #
207
+ # logger.formatter = proc do |severity, datetime, progname, msg|
208
+ # "#{datetime}: #{msg}\n"
209
+ # end
210
+ # # e.g. "Thu Sep 22 08:51:08 GMT+9:00 2005: hello world"
211
+ #
212
+ class MLogger
213
+ ProgName = "#{File.basename(__FILE__)}/#{VERSION}" # :nodoc:
214
+
215
+ class Error < RuntimeError # :nodoc:
216
+ end
217
+ # not used after 1.2.7. just for compat.
218
+ class ShiftingError < Error # :nodoc:
219
+ end
220
+
221
+ # When you set +$LOGGER_LEVEL+ before require mlogger, you can define yourself log level
222
+ # like default value [:DEBUG, :INFO, :WARN, :ERROR, :FATAL]
223
+ # DEBUG < INFO < WARN < ERROR < FATAL
224
+ LOGGER_LEVEL = $LOGGER_LEVEL || [:DEBUG, :INFO, :WARN, :ERROR, :FATAL]
225
+
226
+ LOGGER_LEVEL.each.with_index do |level, index| # :nodoc:
227
+ level_upcase, level_downcase = level.upcase, level.downcase
228
+
229
+ const_set level_upcase, index
230
+
231
+ define_method "#{level_downcase}" do |progname = nil, &block|
232
+ add(index, nil, progname, &block)
233
+ end
234
+
235
+ define_method "#{level_downcase}?" do
236
+ @level <= index
237
+ end
238
+ end
239
+
240
+ # Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
241
+ attr_reader :level
242
+
243
+ def level= level # :nodoc:
244
+ @level = trans_level(level)
245
+ end
246
+
247
+ # program name to include in log messages.
248
+ attr_accessor :progname
249
+
250
+ # Set date-time format.
251
+ #
252
+ # +datetime_format+:: A string suitable for passing to +strftime+.
253
+ def datetime_format=(datetime_format)
254
+ @default_formatter.datetime_format = datetime_format
255
+ end
256
+
257
+ # Returns the date format being used. See #datetime_format=
258
+ def datetime_format
259
+ @default_formatter.datetime_format
260
+ end
261
+
262
+ # Logging formatter, as a +Proc+ that will take four arguments and
263
+ # return the formatted message. The arguments are:
264
+ #
265
+ # +severity+:: The Severity of the log message
266
+ # +time+:: A Time instance representing when the message was logged
267
+ # +progname+:: The #progname configured, or passed to the logger method
268
+ # +msg+:: The _Object_ the user passed to the log message; not necessarily a String.
269
+ #
270
+ # The block should return an Object that can be written to the logging device via +write+. The
271
+ # default formatter is used when no formatter is set.
272
+ attr_accessor :formatter
273
+
274
+ alias sev_threshold level
275
+ alias sev_threshold= level=
276
+
277
+ #
278
+ # === Synopsis
279
+ #
280
+ # MLogger.new(+logdev+)
281
+ # MLogger.new([name, {age: 7, size: 1048576}])
282
+ # MLogger.new([name, {age: 'weekly'}])
283
+ # MLogger.new([+logdev+, {age: +shift_age+, size: +shift_size+}])
284
+ #
285
+ # === Args
286
+ #
287
+ # Every Arg is a logdev information:
288
+ #
289
+ # +logdev+::
290
+ # The log device.
291
+ # Array This is a filename (String) or IO object (typically
292
+ # +STDOUT+, +STDERR+, or an open file).
293
+ # +shift_age+::
294
+ # Number of old log files to keep, *or* frequency of rotation (+daily+,
295
+ # +weekly+ or +monthly+).
296
+ # +shift_size+::
297
+ # Maximum logfile size (only applies when +shift_age+ is a number).
298
+ # === Description
299
+ #
300
+ # Create an instance.
301
+ #
302
+ def initialize(*logdev)
303
+ @progname = nil
304
+ @level = 0
305
+ @default_formatter = Formatter.new
306
+ @formatter = nil
307
+ logdev << STDOUT if logdev.empty?
308
+ @logdev = LogDeveices.new(*logdev)
309
+ @level_logdev = {}
310
+ end
311
+
312
+ #
313
+ # === Synopsis
314
+ #
315
+ # Logger#change_level_logdev(levels, *logdev)
316
+ #
317
+ # == Use
318
+ #
319
+ # logger.change_level_logdev :warn, STDERR
320
+ # logger.change_level_logdev [:warn, :error], STDERR
321
+ # logger.change_level_logdev MLogger::WARN..MLogger::FATAL, STDERR
322
+ #
323
+ # === Args
324
+ #
325
+ # +levels+::
326
+ # Severity. Constants are defined in Logger namespace: +DEBUG+, +INFO+,
327
+ # +WARN+, +ERROR+, +FATAL+. Also can use a Severity Array.
328
+ # +logdev+::
329
+ # The log device. This is a filename (String) or IO object (typically
330
+ # +STDOUT+, +STDERR+, or an open file).
331
+ # +shift_age+::
332
+ # Number of old log files to keep, *or* frequency of rotation (+daily+,
333
+ # +weekly+ or +monthly+).
334
+ # +shift_size+::
335
+ # Maximum logfile size (only applies when +shift_age+ is a number).
336
+ #
337
+ # === Description
338
+ #
339
+ # Change LEVEL LogDev
340
+ #
341
+ def change_level_logdev(levels, *logdev)
342
+ levels = [levels] unless levels.is_a? Enumerable
343
+ log_deveices = LogDeveices.new(*logdev)
344
+ levels.each do |level|
345
+ level = trans_level level
346
+ @level_logdev[level] = log_deveices
347
+ end
348
+ end
349
+
350
+ #
351
+ # Dump given message to the log device without any formatting. If no log
352
+ # device exists, return +nil+.
353
+ #
354
+ def <<(msg)
355
+ unless @logdev.nil?
356
+ @logdev.write(msg)
357
+ end
358
+ end
359
+
360
+ #
361
+ # Close the logging device.
362
+ #
363
+ def close
364
+ @logdev.close if @logdev
365
+ @level_logdev.values.each {|logdev| logdev.close if logdev}
366
+ end
367
+
368
+ private
369
+
370
+ def trans_level(level)
371
+ (level.is_a? String or level.is_a? Symbol) ?
372
+ MLogger.const_get(level.upcase) : level
373
+ end
374
+
375
+ def format_severity(severity)
376
+ LOGGER_LEVEL[severity] || 'ANY'
377
+ end
378
+
379
+ def format_message(severity, datetime, progname, msg)
380
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
381
+ end
382
+
383
+ #
384
+ # === Synopsis
385
+ #
386
+ # MLogger#add(severity, message = nil, progname = nil) { ... }
387
+ #
388
+ # === Args
389
+ #
390
+ # +severity+::
391
+ # Severity. Constants are defined in Logger namespace: +DEBUG+, +INFO+,
392
+ # +WARN+, +ERROR+, +FATAL+.
393
+ # +message+::
394
+ # The log message. A String or Exception.
395
+ # +progname+::
396
+ # Program name string. Can be omitted. Treated as a message if no
397
+ # +message+ and +block+ are given.
398
+ # +block+::
399
+ # Can be omitted. Called to get a message string if +message+ is nil.
400
+ #
401
+ # === Return
402
+ #
403
+ # +true+ if successful, +false+ otherwise.
404
+ #
405
+ # When the given severity is not high enough (for this particular logger), log
406
+ # no message, and return +true+.
407
+ #
408
+ # === Description
409
+ #
410
+ # Log a message if the given severity is high enough. This is the generic
411
+ # logging method. Users will be more inclined to use #debug, #info, #warn,
412
+ # #error, and #fatal.
413
+ #
414
+ # <b>Message format</b>: +message+ can be any object, but it has to be
415
+ # converted to a String in order to log it. Generally, +inspect+ is used
416
+ # if the given object is not a String.
417
+ # A special case is an +Exception+ object, which will be printed in detail,
418
+ # including message, class, and backtrace. See #msg2str for the
419
+ # implementation if required.
420
+ #
421
+ # === Bugs
422
+ #
423
+ # * Logfile is not locked.
424
+ # * Append open does not need to lock file.
425
+ # * If the OS which supports multi I/O, records possibly be mixed.
426
+ #
427
+ def add(severity, message = nil, progname = nil, &block)
428
+ if @logdev.nil? or severity < @level
429
+ return true
430
+ end
431
+ progname ||= @progname
432
+ if message.nil?
433
+ if block_given?
434
+ message = yield
435
+ else
436
+ message = progname
437
+ progname = @progname
438
+ end
439
+ end
440
+ (@level_logdev[severity] || @logdev).write(
441
+ format_message(format_severity(severity), Time.now, progname, message))
442
+ true
443
+ end
444
+
445
+ alias log add
446
+
447
+
448
+ # Default formatter for log messages
449
+ class Formatter
450
+ Format = "%s PID:%d %5s %s: %s\n"
451
+
452
+ attr_accessor :datetime_format
453
+
454
+ def initialize
455
+ @datetime_format = nil
456
+ end
457
+
458
+ def call(severity, time, progname, msg)
459
+ Format % [format_datetime(time), $$, severity, progname,
460
+ msg2str(msg)]
461
+ end
462
+
463
+ private
464
+
465
+ def format_datetime(time)
466
+ if @datetime_format.nil?
467
+ time.strftime("%Y-%m-%d %H:%M:%S.") << "%06d " % time.usec
468
+ else
469
+ time.strftime(@datetime_format)
470
+ end
471
+ end
472
+
473
+ def msg2str(msg)
474
+ case msg
475
+ when ::String
476
+ msg
477
+ when ::Exception
478
+ "#{ msg.message } (#{ msg.class })\n" <<
479
+ (msg.backtrace || []).join("\n")
480
+ else
481
+ msg.inspect
482
+ end
483
+ end
484
+ end
485
+
486
+ class LogDeveices
487
+ attr_reader :devs
488
+
489
+ def initialize *logs
490
+ @devs = logs.map do |log|
491
+ # *STDOUT/*STDERR raise "IOError: not opened for reading"
492
+ LogDevice.new *(log.is_a?(Array) ? log : [log])
493
+ end
494
+ end
495
+
496
+ def write message
497
+ @devs.each {|dev| dev.write message}
498
+ end
499
+
500
+ def close
501
+ @devs.each {|dev| dev.close}
502
+ end
503
+
504
+ # Device used for logging messages.
505
+ class LogDevice
506
+ attr_reader :dev
507
+ attr_reader :filename
508
+
509
+ class LogDeviceMutex
510
+ include MonitorMixin
511
+ end
512
+
513
+ def initialize(log = nil, opt = {})
514
+ @dev = @filename = @shift_age = @shift_size = nil
515
+ @mutex = LogDeviceMutex.new
516
+ if log.respond_to?(:write) and log.respond_to?(:close)
517
+ @dev = log
518
+ else
519
+ @dev = open_logfile(log)
520
+ @dev.sync = true
521
+ @filename = log
522
+ @shift_age = opt[:age] || 7
523
+ @shift_size = opt[:size] || 1048576
524
+ end
525
+ end
526
+
527
+ def write(message)
528
+ begin
529
+ @mutex.synchronize do
530
+ if @shift_age and @dev.respond_to?(:stat)
531
+ begin
532
+ check_shift_log
533
+ rescue
534
+ warn("log shifting failed. #{$!}")
535
+ end
536
+ end
537
+ begin
538
+ @dev.write(message)
539
+ rescue
540
+ warn("log writing failed. #{$!}")
541
+ end
542
+ end
543
+ rescue Exception => ignored
544
+ warn("log writing failed. #{ignored}")
545
+ end
546
+ end
547
+
548
+ def close
549
+ return if @dev == STDOUT || @dev == STDERR
550
+
551
+ begin
552
+ @mutex.synchronize do
553
+ @dev.close rescue nil
554
+ end
555
+ rescue Exception
556
+ @dev.close rescue nil
557
+ end
558
+ end
559
+
560
+ private
561
+
562
+ def open_logfile(filename)
563
+ if (FileTest.exist?(filename))
564
+ open(filename, (File::WRONLY | File::APPEND))
565
+ else
566
+ create_logfile(filename)
567
+ end
568
+ end
569
+
570
+ def create_logfile(filename)
571
+ logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
572
+ logdev.sync = true
573
+ add_log_header(logdev)
574
+ logdev
575
+ end
576
+
577
+ def add_log_header(file)
578
+ file.write(
579
+ "# Logfile created on %s by %s\n" % [Time.now.to_s, MLogger::ProgName]
580
+ )
581
+ end
582
+
583
+ SiD = 24 * 60 * 60
584
+
585
+ def check_shift_log
586
+ if @shift_age.is_a?(Integer)
587
+ # Note: always returns false if '0'.
588
+ if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
589
+ shift_log_age
590
+ end
591
+ else
592
+ now = Time.now
593
+ period_end = previous_period_end(now)
594
+ if @dev.stat.mtime <= period_end
595
+ shift_log_period(period_end)
596
+ end
597
+ end
598
+ end
599
+
600
+ def shift_log_age
601
+ (@shift_age - 3).downto(0) do |i|
602
+ if FileTest.exist?("#{@filename}.#{i}")
603
+ File.rename("#{@filename}.#{i}", "#{@filename}.#{i + 1}")
604
+ end
605
+ end
606
+ @dev.close rescue nil
607
+ File.rename("#{@filename}", "#{@filename}.0")
608
+ @dev = create_logfile(@filename)
609
+ return true
610
+ end
611
+
612
+ def shift_log_period(period_end)
613
+ postfix = period_end.strftime("%Y%m%d") # YYYYMMDD
614
+ age_file = "#{@filename}.#{postfix}"
615
+ if FileTest.exist?(age_file)
616
+ # try to avoid filename crash caused by Timestamp change.
617
+ idx = 0
618
+ # .99 can be overridden; avoid too much file search with 'loop do'
619
+ while idx < 100
620
+ idx += 1
621
+ age_file = "#{@filename}.#{postfix}.#{idx}"
622
+ break unless FileTest.exist?(age_file)
623
+ end
624
+ end
625
+ @dev.close rescue nil
626
+ File.rename("#{@filename}", age_file)
627
+ @dev = create_logfile(@filename)
628
+ return true
629
+ end
630
+
631
+ def previous_period_end(now)
632
+ case @shift_age
633
+ when /^daily$/
634
+ eod(now - 1 * SiD)
635
+ when /^weekly$/
636
+ eod(now - ((now.wday + 1) * SiD))
637
+ when /^monthly$/
638
+ eod(now - now.mday * SiD)
639
+ else
640
+ now
641
+ end
642
+ end
643
+
644
+ def eod(t)
645
+ Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
646
+ end
647
+ end
648
+ end
649
+ end
650
+
data/mlogger.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "mlogger/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mlogger"
7
+ spec.version = MLogger::VERSION
8
+ spec.authors = ["NAKAMURA", "Hiroshi","Hdzi"]
9
+ spec.email = ["taojinhou@qq.com"]
10
+
11
+ spec.summary = %q{simple logging utility with multiIO}
12
+ spec.description = %q{simple logging utility with multiIO}
13
+ spec.homepage = "http://github.com/Hdzi/mlogger"
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against " \
22
+ "public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.16"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "minitest", "~> 5.0"
35
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mlogger
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - NAKAMURA
8
+ - Hiroshi
9
+ - Hdzi
10
+ autorequire:
11
+ bindir: exe
12
+ cert_chain: []
13
+ date: 2018-01-12 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.16'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.16'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '10.0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '10.0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: minitest
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '5.0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '5.0'
57
+ description: simple logging utility with multiIO
58
+ email:
59
+ - taojinhou@qq.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - lib/mlogger.rb
72
+ - lib/mlogger/version.rb
73
+ - mlogger.gemspec
74
+ homepage: http://github.com/Hdzi/mlogger
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.6.14
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: simple logging utility with multiIO
99
+ test_files: []