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 +7 -0
- data/.gitignore +6 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +24 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/mlogger/version.rb +3 -0
- data/lib/mlogger.rb +650 -0
- data/mlogger.gemspec +35 -0
- metadata +99 -0
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
data/Gemfile
ADDED
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
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
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: []
|