logger 1.2.8.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fca3ed305eb759ed28a0ea917d653feee81fc4d399a53a44e6649d8e8389defb
4
- data.tar.gz: 90cef9d07cb58cb5049c1a869ae56ec609e0dfd1befcac6053b97602e11ee609
3
+ metadata.gz: 6b7218297d498d773ba9c79d57749a01f259d336a4e9b5acbfe51274d2442500
4
+ data.tar.gz: c455593961790cfaf065b7b6b7798d9c184900e1ccdd0aa4f89411e33400e4b0
5
5
  SHA512:
6
- metadata.gz: c2c96afb68a6e3a22e53673119f673bd6490d6d1c5c184da06677633648c0e86ce6c7eaacbb4dca55c4d3c800ea8d7708c55235a854987c98ed3f20feee185f9
7
- data.tar.gz: c5e39c441722c99cd6fee091939303d22448a329543699b1deac011d3500f3cf939987ff6c4c64db1cff526a746e1e6050f9af801f057f3aca5181ea734599fa
6
+ metadata.gz: 5db2250788d1a9c583685501312a5f6ef277b3148b164b5ed1c2b123a88560ff795b40eee1ac70cf5f466e3dbd771e3d93773c387599b7f7c9916dd7319a99a6
7
+ data.tar.gz: 94a96a2fba4c2e56e18e2e40f4ca0d0238edd355f65b95182e9ffdfb9a380bd7f932cf9734c0baa2a4c7b79df182cf27151972bfdd3efc6829f58530cd28c287
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ - ruby-head
6
+ before_install: gem install bundler -v 1.16.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in logger.gemspec
6
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+ 1. Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22
+ SUCH DAMAGE.
@@ -0,0 +1,39 @@
1
+ # Logger
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/logger`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'logger'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install logger
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/logger.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [BSD-2-Clause](LICENSE.txt).
@@ -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" << "test/lib"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "logger"
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__)
@@ -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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  # logger.rb - simple logging utility
2
3
  # Copyright (C) 2000-2003, 2005, 2008, 2011 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
4
  #
@@ -7,47 +8,42 @@
7
8
  # license; either the dual license version in 2003, or any later version.
8
9
  # Revision:: $Id$
9
10
  #
10
- # See Logger for documentation.
11
-
11
+ # A simple system for logging messages. See Logger for more documentation.
12
12
 
13
13
  require 'monitor'
14
14
 
15
-
16
15
  # == Description
17
16
  #
18
17
  # The Logger class provides a simple but sophisticated logging utility that
19
- # anyone can use because it's included in the Ruby 1.8.x standard library.
20
- #
21
- # The HOWTOs below give a code-based overview of Logger's usage, but the basic
22
- # concept is as follows. You create a Logger object (output to a file or
23
- # elsewhere), and use it to log messages. The messages will have varying
24
- # levels (+info+, +error+, etc), reflecting their varying importance. The
25
- # levels, and their meanings, are:
26
- #
27
- # +FATAL+:: an unhandleable error that results in a program crash
28
- # +ERROR+:: a handleable error condition
29
- # +WARN+:: a warning
30
- # +INFO+:: generic (useful) information about system operation
31
- # +DEBUG+:: low-level information for developers
32
- #
33
- # So each message has a level, and the Logger itself has a level, which acts
34
- # as a filter, so you can control the amount of information emitted from the
35
- # logger without having to remove actual messages.
36
- #
37
- # For instance, in a production system, you may have your logger(s) set to
38
- # +INFO+ (or +WARN+ if you don't want the log files growing large with
39
- # repetitive information). When you are developing it, though, you probably
40
- # want to know about the program's internal state, and would set them to
18
+ # you can use to output messages.
19
+ #
20
+ # The messages have associated levels, such as +INFO+ or +ERROR+ that indicate
21
+ # their importance. You can then give the Logger a level, and only messages
22
+ # at that level or higher will be printed.
23
+ #
24
+ # The levels are:
25
+ #
26
+ # +UNKNOWN+:: An unknown message that should always be logged.
27
+ # +FATAL+:: An unhandleable error that results in a program crash.
28
+ # +ERROR+:: A handleable error condition.
29
+ # +WARN+:: A warning.
30
+ # +INFO+:: Generic (useful) information about system operation.
31
+ # +DEBUG+:: Low-level information for developers.
32
+ #
33
+ # For instance, in a production system, you may have your Logger set to
34
+ # +INFO+ or even +WARN+.
35
+ # When you are developing the system, however, you probably
36
+ # want to know about the program's internal state, and would set the Logger to
41
37
  # +DEBUG+.
42
38
  #
43
- # **Note**: Logger does not escape or sanitize any messages passed to it.
39
+ # *Note*: Logger does not escape or sanitize any messages passed to it.
44
40
  # Developers should be aware of when potentially malicious data (user-input)
45
41
  # is passed to Logger, and manually escape the untrusted data:
46
42
  #
47
43
  # logger.info("User-input: #{input.dump}")
48
44
  # logger.info("User-input: %p" % input)
49
45
  #
50
- # You can use Logger#formatter= for escaping all data.
46
+ # You can use #formatter= for escaping all data.
51
47
  #
52
48
  # original_formatter = Logger::Formatter.new
53
49
  # logger.formatter = proc { |severity, datetime, progname, msg|
@@ -57,24 +53,29 @@ require 'monitor'
57
53
  #
58
54
  # === Example
59
55
  #
60
- # A simple example demonstrates the above explanation:
56
+ # This creates a Logger that outputs to the standard output stream, with a
57
+ # level of +WARN+:
58
+ #
59
+ # require 'logger'
61
60
  #
62
- # log = Logger.new(STDOUT)
63
- # log.level = Logger::WARN
61
+ # logger = Logger.new(STDOUT)
62
+ # logger.level = Logger::WARN
64
63
  #
65
- # log.debug("Created logger")
66
- # log.info("Program started")
67
- # log.warn("Nothing to do!")
64
+ # logger.debug("Created logger")
65
+ # logger.info("Program started")
66
+ # logger.warn("Nothing to do!")
67
+ #
68
+ # path = "a_non_existent_file"
68
69
  #
69
70
  # begin
70
- # File.each_line(path) do |line|
71
+ # File.foreach(path) do |line|
71
72
  # unless line =~ /^(\w+) = (.*)$/
72
- # log.error("Line in wrong format: #{line}")
73
+ # logger.error("Line in wrong format: #{line.chomp}")
73
74
  # end
74
75
  # end
75
76
  # rescue => err
76
- # log.fatal("Caught exception; exiting")
77
- # log.fatal(err)
77
+ # logger.fatal("Caught exception; exiting")
78
+ # logger.fatal(err)
78
79
  # end
79
80
  #
80
81
  # Because the Logger's level is set to +WARN+, only the warning, error, and
@@ -108,16 +109,16 @@ require 'monitor'
108
109
  # 3. Create a logger for the specified file.
109
110
  #
110
111
  # file = File.open('foo.log', File::WRONLY | File::APPEND)
111
- # # To create new (and to remove old) logfile, add File::CREAT like;
112
- # # file = open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
112
+ # # To create new logfile, add File::CREAT like:
113
+ # # file = File.open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
113
114
  # logger = Logger.new(file)
114
115
  #
115
- # 4. Create a logger which ages logfile once it reaches a certain size. Leave
116
- # 10 "old log files" and each file is about 1,024,000 bytes.
116
+ # 4. Create a logger which ages the logfile once it reaches a certain size.
117
+ # Leave 10 "old" log files where each file is about 1,024,000 bytes.
117
118
  #
118
119
  # logger = Logger.new('foo.log', 10, 1024000)
119
120
  #
120
- # 5. Create a logger which ages logfile daily/weekly/monthly.
121
+ # 5. Create a logger which ages the logfile daily/weekly/monthly.
121
122
  #
122
123
  # logger = Logger.new('foo.log', 'daily')
123
124
  # logger = Logger.new('foo.log', 'weekly')
@@ -126,17 +127,17 @@ require 'monitor'
126
127
  # === How to log a message
127
128
  #
128
129
  # Notice the different methods (+fatal+, +error+, +info+) being used to log
129
- # messages of various levels. Other methods in this family are +warn+ and
130
+ # messages of various levels? Other methods in this family are +warn+ and
130
131
  # +debug+. +add+ is used below to log a message of an arbitrary (perhaps
131
132
  # dynamic) level.
132
133
  #
133
- # 1. Message in block.
134
+ # 1. Message in a block.
134
135
  #
135
136
  # logger.fatal { "Argument 'foo' not given." }
136
137
  #
137
138
  # 2. Message as a string.
138
139
  #
139
- # logger.error "Argument #{ @foo } mismatch."
140
+ # logger.error "Argument #{@foo} mismatch."
140
141
  #
141
142
  # 3. With progname.
142
143
  #
@@ -146,6 +147,20 @@ require 'monitor'
146
147
  #
147
148
  # logger.add(Logger::FATAL) { 'Fatal error!' }
148
149
  #
150
+ # The block form allows you to create potentially complex log messages,
151
+ # but to delay their evaluation until and unless the message is
152
+ # logged. For example, if we have the following:
153
+ #
154
+ # logger.debug { "This is a " + potentially + " expensive operation" }
155
+ #
156
+ # If the logger's level is +INFO+ or higher, no debug messages will be logged,
157
+ # and the entire block will not even be evaluated. Compare to this:
158
+ #
159
+ # logger.debug("This is a " + potentially + " expensive operation")
160
+ #
161
+ # Here, the string concatenation is done every time, even if the log
162
+ # level is not set to show the debug message.
163
+ #
149
164
  # === How to close a logger
150
165
  #
151
166
  # logger.close
@@ -160,8 +175,20 @@ require 'monitor'
160
175
  #
161
176
  # logger.level = Logger::INFO
162
177
  #
163
- # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
178
+ # # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
179
+ #
180
+ # 3. Symbol or String (case insensitive)
164
181
  #
182
+ # logger.level = :info
183
+ # logger.level = 'INFO'
184
+ #
185
+ # # :debug < :info < :warn < :error < :fatal < :unknown
186
+ #
187
+ # 4. Constructor
188
+ #
189
+ # Logger.new(logdev, level: Logger::INFO)
190
+ # Logger.new(logdev, level: :info)
191
+ # Logger.new(logdev, level: 'INFO')
165
192
  #
166
193
  # == Format
167
194
  #
@@ -169,65 +196,123 @@ require 'monitor'
169
196
  # default. The default format and a sample are shown below:
170
197
  #
171
198
  # Log format:
172
- # SeverityID, [Date Time mSec #pid] SeverityLabel -- ProgName: message
199
+ # SeverityID, [DateTime #pid] SeverityLabel -- ProgName: message
173
200
  #
174
201
  # Log sample:
175
- # I, [Wed Mar 03 02:34:24 JST 1999 895701 #19074] INFO -- Main: info.
202
+ # I, [1999-03-03T02:34:24.895701 #19074] INFO -- Main: info.
176
203
  #
177
- # You may change the date and time format in this manner:
204
+ # You may change the date and time format via #datetime_format=.
178
205
  #
179
- # logger.datetime_format = "%Y-%m-%d %H:%M:%S"
206
+ # logger.datetime_format = '%Y-%m-%d %H:%M:%S'
180
207
  # # e.g. "2004-01-03 00:54:26"
181
208
  #
182
- # You may change the overall format with Logger#formatter= method.
209
+ # or via the constructor.
183
210
  #
184
- # logger.formatter = proc { |severity, datetime, progname, msg|
211
+ # Logger.new(logdev, datetime_format: '%Y-%m-%d %H:%M:%S')
212
+ #
213
+ # Or, you may change the overall format via the #formatter= method.
214
+ #
215
+ # logger.formatter = proc do |severity, datetime, progname, msg|
185
216
  # "#{datetime}: #{msg}\n"
186
- # }
187
- # # e.g. "Thu Sep 22 08:51:08 GMT+9:00 2005: hello world"
217
+ # end
218
+ # # e.g. "2005-09-22 08:51:08 +0900: hello world"
219
+ #
220
+ # or via the constructor.
221
+ #
222
+ # Logger.new(logdev, formatter: proc {|severity, datetime, progname, msg|
223
+ # "#{datetime}: #{msg}\n"
224
+ # })
188
225
  #
189
-
190
-
191
226
  class Logger
192
- VERSION = "1.2.8.1"
193
- ProgName = "#{File.basename(__FILE__)}/#{VERSION}"
227
+ VERSION = "1.3.0"
228
+ _, name, rev = %w$Id$
229
+ if name
230
+ name = name.chomp(",v")
231
+ else
232
+ name = File.basename(__FILE__)
233
+ end
234
+ rev ||= "v#{VERSION}"
235
+ ProgName = "#{name}/#{rev}".freeze
194
236
 
195
- class Error < RuntimeError; end
196
- class ShiftingError < Error; end # not used after 1.2.7. just for compat.
237
+ class Error < RuntimeError # :nodoc:
238
+ end
239
+ # not used after 1.2.7. just for compat.
240
+ class ShiftingError < Error # :nodoc:
241
+ end
197
242
 
198
243
  # Logging severity.
199
244
  module Severity
245
+ # Low-level information, mostly for developers.
200
246
  DEBUG = 0
247
+ # Generic (useful) information about system operation.
201
248
  INFO = 1
249
+ # A warning.
202
250
  WARN = 2
251
+ # A handleable error condition.
203
252
  ERROR = 3
253
+ # An unhandleable error that results in a program crash.
204
254
  FATAL = 4
255
+ # An unknown message that should always be logged.
205
256
  UNKNOWN = 5
206
257
  end
207
258
  include Severity
208
259
 
209
260
  # Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
210
- attr_accessor :level
261
+ attr_reader :level
262
+
263
+ # Set logging severity threshold.
264
+ #
265
+ # +severity+:: The Severity of the log message.
266
+ def level=(severity)
267
+ if severity.is_a?(Integer)
268
+ @level = severity
269
+ else
270
+ case severity.to_s.downcase
271
+ when 'debug'
272
+ @level = DEBUG
273
+ when 'info'
274
+ @level = INFO
275
+ when 'warn'
276
+ @level = WARN
277
+ when 'error'
278
+ @level = ERROR
279
+ when 'fatal'
280
+ @level = FATAL
281
+ when 'unknown'
282
+ @level = UNKNOWN
283
+ else
284
+ raise ArgumentError, "invalid log level: #{severity}"
285
+ end
286
+ end
287
+ end
211
288
 
212
- # Logging program name.
289
+ # Program name to include in log messages.
213
290
  attr_accessor :progname
214
291
 
215
- # Logging date-time format (string passed to +strftime+).
292
+ # Set date-time format.
293
+ #
294
+ # +datetime_format+:: A string suitable for passing to +strftime+.
216
295
  def datetime_format=(datetime_format)
217
296
  @default_formatter.datetime_format = datetime_format
218
297
  end
219
298
 
220
- # Returns the date format (string passed to +strftime+) being used (it's set
221
- # using datetime_format=)
299
+ # Returns the date format being used. See #datetime_format=
222
300
  def datetime_format
223
301
  @default_formatter.datetime_format
224
302
  end
225
303
 
226
- # Logging formatter. formatter#call is invoked with 4 arguments; severity,
227
- # time, progname and msg for each log. Bear in mind that time is a Time and
228
- # msg is an Object that user passed and it could not be a String. It is
229
- # expected to return a logdev#write-able Object. Default formatter is used
230
- # when no formatter is set.
304
+ # Logging formatter, as a +Proc+ that will take four arguments and
305
+ # return the formatted message. The arguments are:
306
+ #
307
+ # +severity+:: The Severity of the log message.
308
+ # +time+:: A Time instance representing when the message was logged.
309
+ # +progname+:: The #progname configured, or passed to the logger method.
310
+ # +msg+:: The _Object_ the user passed to the log message; not necessarily a
311
+ # String.
312
+ #
313
+ # The block should return an Object that can be written to the logging
314
+ # device via +write+. The default formatter is used when no formatter is
315
+ # set.
231
316
  attr_accessor :formatter
232
317
 
233
318
  alias sev_threshold level
@@ -254,10 +339,13 @@ class Logger
254
339
  def fatal?; @level <= FATAL; end
255
340
 
256
341
  #
257
- # === Synopsis
258
- #
259
- # Logger.new(name, shift_age = 7, shift_size = 1048576)
260
- # Logger.new(name, shift_age = 'weekly')
342
+ # :call-seq:
343
+ # Logger.new(logdev, shift_age = 0, shift_size = 1048576)
344
+ # Logger.new(logdev, shift_age = 'weekly')
345
+ # Logger.new(logdev, level: :info)
346
+ # Logger.new(logdev, progname: 'progname')
347
+ # Logger.new(logdev, formatter: formatter)
348
+ # Logger.new(logdev, datetime_format: '%Y-%m-%d %H:%M:%S')
261
349
  #
262
350
  # === Args
263
351
  #
@@ -266,29 +354,65 @@ class Logger
266
354
  # +STDOUT+, +STDERR+, or an open file).
267
355
  # +shift_age+::
268
356
  # Number of old log files to keep, *or* frequency of rotation (+daily+,
269
- # +weekly+ or +monthly+).
357
+ # +weekly+ or +monthly+). Default value is 0.
270
358
  # +shift_size+::
271
- # Maximum logfile size (only applies when +shift_age+ is a number).
359
+ # Maximum logfile size in bytes (only applies when +shift_age+ is a number).
360
+ # Defaults to +1048576+ (1MB).
361
+ # +level+::
362
+ # Logging severity threshold. Default values is Logger::DEBUG.
363
+ # +progname+::
364
+ # Program name to include in log messages. Default value is nil.
365
+ # +formatter+::
366
+ # Logging formatter. Default values is an instance of Logger::Formatter.
367
+ # +datetime_format+::
368
+ # Date and time format. Default value is '%Y-%m-%d %H:%M:%S'.
369
+ # +shift_period_suffix+::
370
+ # The log file suffix format for +daily+, +weekly+ or +monthly+ rotation.
371
+ # Default is '%Y%m%d'.
272
372
  #
273
373
  # === Description
274
374
  #
275
375
  # Create an instance.
276
376
  #
277
- def initialize(logdev, shift_age = 0, shift_size = 1048576)
278
- @progname = nil
279
- @level = DEBUG
377
+ def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG,
378
+ progname: nil, formatter: nil, datetime_format: nil,
379
+ shift_period_suffix: '%Y%m%d')
380
+ self.level = level
381
+ self.progname = progname
280
382
  @default_formatter = Formatter.new
281
- @formatter = nil
383
+ self.datetime_format = datetime_format
384
+ self.formatter = formatter
282
385
  @logdev = nil
283
386
  if logdev
284
387
  @logdev = LogDevice.new(logdev, :shift_age => shift_age,
285
- :shift_size => shift_size)
388
+ :shift_size => shift_size,
389
+ :shift_period_suffix => shift_period_suffix)
286
390
  end
287
391
  end
288
392
 
289
393
  #
290
- # === Synopsis
394
+ # :call-seq:
395
+ # Logger#reopen
396
+ # Logger#reopen(logdev)
397
+ #
398
+ # === Args
399
+ #
400
+ # +logdev+::
401
+ # The log device. This is a filename (String) or IO object (typically
402
+ # +STDOUT+, +STDERR+, or an open file). reopen the same filename if
403
+ # it is +nil+, do nothing for IO. Default is +nil+.
404
+ #
405
+ # === Description
406
+ #
407
+ # Reopen a log device.
291
408
  #
409
+ def reopen(logdev = nil)
410
+ @logdev.reopen(logdev)
411
+ self
412
+ end
413
+
414
+ #
415
+ # :call-seq:
292
416
  # Logger#add(severity, message = nil, progname = nil) { ... }
293
417
  #
294
418
  # === Args
@@ -306,10 +430,8 @@ class Logger
306
430
  #
307
431
  # === Return
308
432
  #
309
- # +true+ if successful, +false+ otherwise.
310
- #
311
- # When the given severity is not high enough (for this particular logger), log
312
- # no message, and return +true+.
433
+ # When the given severity is not high enough (for this particular logger),
434
+ # log no message, and return +true+.
313
435
  #
314
436
  # === Description
315
437
  #
@@ -328,14 +450,16 @@ class Logger
328
450
  #
329
451
  # * Logfile is not locked.
330
452
  # * Append open does not need to lock file.
331
- # * But on the OS which supports multi I/O, records possibly be mixed.
453
+ # * If the OS supports multi I/O, records possibly may be mixed.
332
454
  #
333
- def add(severity, message = nil, progname = nil, &block)
455
+ def add(severity, message = nil, progname = nil)
334
456
  severity ||= UNKNOWN
335
457
  if @logdev.nil? or severity < @level
336
458
  return true
337
459
  end
338
- progname ||= @progname
460
+ if progname.nil?
461
+ progname = @progname
462
+ end
339
463
  if message.nil?
340
464
  if block_given?
341
465
  message = yield
@@ -355,9 +479,7 @@ class Logger
355
479
  # device exists, return +nil+.
356
480
  #
357
481
  def <<(msg)
358
- unless @logdev.nil?
359
- @logdev.write(msg)
360
- end
482
+ @logdev&.write(msg)
361
483
  end
362
484
 
363
485
  #
@@ -369,12 +491,20 @@ class Logger
369
491
  add(DEBUG, nil, progname, &block)
370
492
  end
371
493
 
494
+ #
495
+ # :call-seq:
496
+ # info(message)
497
+ # info(progname, &block)
372
498
  #
373
499
  # Log an +INFO+ message.
374
500
  #
375
- # The message can come either from the +progname+ argument or the +block+. If
376
- # both are provided, then the +block+ is used as the message, and +progname+
377
- # is used as the program name.
501
+ # +message+:: The message to log; does not need to be a String.
502
+ # +progname+:: In the block form, this is the #progname to use in the
503
+ # log message. The default can be set with #progname=.
504
+ # +block+:: Evaluates to the message to log. This is not evaluated unless
505
+ # the logger's level is sufficient to log the message. This
506
+ # allows you to create potentially expensive logging messages that
507
+ # are only called when the logger is configured to show them.
378
508
  #
379
509
  # === Examples
380
510
  #
@@ -385,7 +515,7 @@ class Logger
385
515
  # logger.info { "User typed #{input}" }
386
516
  #
387
517
  # You'll probably stick to the second form above, unless you want to provide a
388
- # program name (which you can do with <tt>Logger#progname=</tt> as well).
518
+ # program name (which you can do with #progname= as well).
389
519
  #
390
520
  # === Return
391
521
  #
@@ -423,8 +553,8 @@ class Logger
423
553
  end
424
554
 
425
555
  #
426
- # Log an +UNKNOWN+ message. This will be printed no matter what the logger
427
- # level.
556
+ # Log an +UNKNOWN+ message. This will be printed no matter what the logger's
557
+ # level is.
428
558
  #
429
559
  # See #info for more information.
430
560
  #
@@ -436,13 +566,13 @@ class Logger
436
566
  # Close the logging device.
437
567
  #
438
568
  def close
439
- @logdev.close if @logdev
569
+ @logdev&.close
440
570
  end
441
571
 
442
572
  private
443
573
 
444
- # Severity label for logging. (max 5 char)
445
- SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY)
574
+ # Severity label for logging (max 5 chars).
575
+ SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY).each(&:freeze).freeze
446
576
 
447
577
  def format_severity(severity)
448
578
  SEV_LABEL[severity] || 'ANY'
@@ -453,8 +583,9 @@ private
453
583
  end
454
584
 
455
585
 
586
+ # Default formatter for log messages.
456
587
  class Formatter
457
- Format = "%s, [%s#%d] %5s -- %s: %s\n"
588
+ Format = "%s, [%s#%d] %5s -- %s: %s\n".freeze
458
589
 
459
590
  attr_accessor :datetime_format
460
591
 
@@ -470,11 +601,7 @@ private
470
601
  private
471
602
 
472
603
  def format_datetime(time)
473
- if @datetime_format.nil?
474
- time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
475
- else
476
- time.strftime(@datetime_format)
477
- end
604
+ time.strftime(@datetime_format || "%Y-%m-%dT%H:%M:%S.%6N ".freeze)
478
605
  end
479
606
 
480
607
  def msg2str(msg)
@@ -490,32 +617,73 @@ private
490
617
  end
491
618
  end
492
619
 
620
+ module Period
621
+ module_function
493
622
 
494
- class LogDevice
495
- attr_reader :dev
496
- attr_reader :filename
623
+ SiD = 24 * 60 * 60
497
624
 
498
- class LogDeviceMutex
499
- include MonitorMixin
625
+ def next_rotate_time(now, shift_age)
626
+ case shift_age
627
+ when 'daily'
628
+ t = Time.mktime(now.year, now.month, now.mday) + SiD
629
+ when 'weekly'
630
+ t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday)
631
+ when 'monthly'
632
+ t = Time.mktime(now.year, now.month, 1) + SiD * 32
633
+ return Time.mktime(t.year, t.month, 1)
634
+ else
635
+ return now
636
+ end
637
+ if t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero?
638
+ hour = t.hour
639
+ t = Time.mktime(t.year, t.month, t.mday)
640
+ t += SiD if hour > 12
641
+ end
642
+ t
500
643
  end
501
644
 
502
- def initialize(log = nil, opt = {})
503
- @dev = @filename = @shift_age = @shift_size = nil
504
- @mutex = LogDeviceMutex.new
505
- if log.respond_to?(:write) and log.respond_to?(:close)
506
- @dev = log
645
+ def previous_period_end(now, shift_age)
646
+ case shift_age
647
+ when 'daily'
648
+ t = Time.mktime(now.year, now.month, now.mday) - SiD / 2
649
+ when 'weekly'
650
+ t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2)
651
+ when 'monthly'
652
+ t = Time.mktime(now.year, now.month, 1) - SiD / 2
507
653
  else
508
- @dev = open_logfile(log)
509
- @dev.sync = true
510
- @filename = log
511
- @shift_age = opt[:shift_age] || 7
512
- @shift_size = opt[:shift_size] || 1048576
654
+ return now
655
+ end
656
+ Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
657
+ end
658
+ end
659
+
660
+ # Device used for logging messages.
661
+ class LogDevice
662
+ include Period
663
+
664
+ attr_reader :dev
665
+ attr_reader :filename
666
+ include MonitorMixin
667
+
668
+ def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil)
669
+ @dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil
670
+ mon_initialize
671
+ set_dev(log)
672
+ if @filename
673
+ @shift_age = shift_age || 7
674
+ @shift_size = shift_size || 1048576
675
+ @shift_period_suffix = shift_period_suffix || '%Y%m%d'
676
+
677
+ unless @shift_age.is_a?(Integer)
678
+ base_time = @dev.respond_to?(:stat) ? @dev.stat.mtime : Time.now
679
+ @next_rotate_time = next_rotate_time(base_time, @shift_age)
680
+ end
513
681
  end
514
682
  end
515
683
 
516
684
  def write(message)
517
685
  begin
518
- @mutex.synchronize do
686
+ synchronize do
519
687
  if @shift_age and @dev.respond_to?(:stat)
520
688
  begin
521
689
  check_shift_log
@@ -536,7 +704,7 @@ private
536
704
 
537
705
  def close
538
706
  begin
539
- @mutex.synchronize do
707
+ synchronize do
540
708
  @dev.close rescue nil
541
709
  end
542
710
  rescue Exception
@@ -544,43 +712,110 @@ private
544
712
  end
545
713
  end
546
714
 
715
+ def reopen(log = nil)
716
+ # reopen the same filename if no argument, do nothing for IO
717
+ log ||= @filename if @filename
718
+ if log
719
+ synchronize do
720
+ if @filename and @dev
721
+ @dev.close rescue nil # close only file opened by Logger
722
+ @filename = nil
723
+ end
724
+ set_dev(log)
725
+ end
726
+ end
727
+ self
728
+ end
729
+
547
730
  private
548
731
 
549
- def open_logfile(filename)
550
- if (FileTest.exist?(filename))
551
- open(filename, (File::WRONLY | File::APPEND))
732
+ def set_dev(log)
733
+ if log.respond_to?(:write) and log.respond_to?(:close)
734
+ @dev = log
552
735
  else
736
+ @dev = open_logfile(log)
737
+ @dev.sync = true
738
+ @filename = log
739
+ end
740
+ end
741
+
742
+ def open_logfile(filename)
743
+ begin
744
+ File.open(filename, (File::WRONLY | File::APPEND))
745
+ rescue Errno::ENOENT
553
746
  create_logfile(filename)
554
747
  end
555
748
  end
556
749
 
557
750
  def create_logfile(filename)
558
- logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
559
- logdev.sync = true
560
- add_log_header(logdev)
751
+ begin
752
+ logdev = File.open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL))
753
+ logdev.flock(File::LOCK_EX)
754
+ logdev.sync = true
755
+ add_log_header(logdev)
756
+ logdev.flock(File::LOCK_UN)
757
+ rescue Errno::EEXIST
758
+ # file is created by another process
759
+ logdev = open_logfile(filename)
760
+ logdev.sync = true
761
+ end
561
762
  logdev
562
763
  end
563
764
 
564
765
  def add_log_header(file)
565
766
  file.write(
566
767
  "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
567
- )
768
+ ) if file.size == 0
568
769
  end
569
770
 
570
- SiD = 24 * 60 * 60
571
-
572
771
  def check_shift_log
573
772
  if @shift_age.is_a?(Integer)
574
773
  # Note: always returns false if '0'.
575
774
  if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
576
- shift_log_age
775
+ lock_shift_log { shift_log_age }
577
776
  end
578
777
  else
579
778
  now = Time.now
580
- period_end = previous_period_end(now)
581
- if @dev.stat.mtime <= period_end
582
- shift_log_period(period_end)
779
+ if now >= @next_rotate_time
780
+ @next_rotate_time = next_rotate_time(now, @shift_age)
781
+ lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) }
782
+ end
783
+ end
784
+ end
785
+
786
+ if /mswin|mingw/ =~ RUBY_PLATFORM
787
+ def lock_shift_log
788
+ yield
789
+ end
790
+ else
791
+ def lock_shift_log
792
+ retry_limit = 8
793
+ retry_sleep = 0.1
794
+ begin
795
+ File.open(@filename, File::WRONLY | File::APPEND) do |lock|
796
+ lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file
797
+ if File.identical?(@filename, lock) and File.identical?(lock, @dev)
798
+ yield # log shifting
799
+ else
800
+ # log shifted by another process (i-node before locking and i-node after locking are different)
801
+ @dev.close rescue nil
802
+ @dev = open_logfile(@filename)
803
+ @dev.sync = true
804
+ end
805
+ end
806
+ rescue Errno::ENOENT
807
+ # @filename file would not exist right after #rename and before #create_logfile
808
+ if retry_limit <= 0
809
+ warn("log rotation inter-process lock failed. #{$!}")
810
+ else
811
+ sleep retry_sleep
812
+ retry_limit -= 1
813
+ retry_sleep *= 2
814
+ retry
815
+ end
583
816
  end
817
+ rescue
818
+ warn("log rotation inter-process lock failed. #{$!}")
584
819
  end
585
820
  end
586
821
 
@@ -597,15 +832,15 @@ private
597
832
  end
598
833
 
599
834
  def shift_log_period(period_end)
600
- postfix = period_end.strftime("%Y%m%d") # YYYYMMDD
601
- age_file = "#{@filename}.#{postfix}"
835
+ suffix = period_end.strftime(@shift_period_suffix)
836
+ age_file = "#{@filename}.#{suffix}"
602
837
  if FileTest.exist?(age_file)
603
838
  # try to avoid filename crash caused by Timestamp change.
604
839
  idx = 0
605
840
  # .99 can be overridden; avoid too much file search with 'loop do'
606
841
  while idx < 100
607
842
  idx += 1
608
- age_file = "#{@filename}.#{postfix}.#{idx}"
843
+ age_file = "#{@filename}.#{suffix}.#{idx}"
609
844
  break unless FileTest.exist?(age_file)
610
845
  end
611
846
  end
@@ -614,146 +849,5 @@ private
614
849
  @dev = create_logfile(@filename)
615
850
  return true
616
851
  end
617
-
618
- def previous_period_end(now)
619
- case @shift_age
620
- when /^daily$/
621
- eod(now - 1 * SiD)
622
- when /^weekly$/
623
- eod(now - ((now.wday + 1) * SiD))
624
- when /^monthly$/
625
- eod(now - now.mday * SiD)
626
- else
627
- now
628
- end
629
- end
630
-
631
- def eod(t)
632
- Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
633
- end
634
- end
635
-
636
-
637
- #
638
- # == Description
639
- #
640
- # Application -- Add logging support to your application.
641
- #
642
- # == Usage
643
- #
644
- # 1. Define your application class as a sub-class of this class.
645
- # 2. Override 'run' method in your class to do many things.
646
- # 3. Instantiate it and invoke 'start'.
647
- #
648
- # == Example
649
- #
650
- # class FooApp < Application
651
- # def initialize(foo_app, application_specific, arguments)
652
- # super('FooApp') # Name of the application.
653
- # end
654
- #
655
- # def run
656
- # ...
657
- # log(WARN, 'warning', 'my_method1')
658
- # ...
659
- # @log.error('my_method2') { 'Error!' }
660
- # ...
661
- # end
662
- # end
663
- #
664
- # status = FooApp.new(....).start
665
- #
666
- class Application
667
- include Logger::Severity
668
-
669
- # Name of the application given at initialize.
670
- attr_reader :appname
671
-
672
- #
673
- # == Synopsis
674
- #
675
- # Application.new(appname = '')
676
- #
677
- # == Args
678
- #
679
- # +appname+:: Name of the application.
680
- #
681
- # == Description
682
- #
683
- # Create an instance. Log device is +STDERR+ by default. This can be
684
- # changed with #set_log.
685
- #
686
- def initialize(appname = nil)
687
- @appname = appname
688
- @log = Logger.new(STDERR)
689
- @log.progname = @appname
690
- @level = @log.level
691
- end
692
-
693
- #
694
- # Start the application. Return the status code.
695
- #
696
- def start
697
- status = -1
698
- begin
699
- log(INFO, "Start of #{ @appname }.")
700
- status = run
701
- rescue
702
- log(FATAL, "Detected an exception. Stopping ... #{$!} (#{$!.class})\n" << $@.join("\n"))
703
- ensure
704
- log(INFO, "End of #{ @appname }. (status: #{ status.to_s })")
705
- end
706
- status
707
- end
708
-
709
- # Logger for this application. See the class Logger for an explanation.
710
- def logger
711
- @log
712
- end
713
-
714
- #
715
- # Sets the logger for this application. See the class Logger for an explanation.
716
- #
717
- def logger=(logger)
718
- @log = logger
719
- @log.progname = @appname
720
- @log.level = @level
721
- end
722
-
723
- #
724
- # Sets the log device for this application. See <tt>Logger.new</tt> for an explanation
725
- # of the arguments.
726
- #
727
- def set_log(logdev, shift_age = 0, shift_size = 1024000)
728
- @log = Logger.new(logdev, shift_age, shift_size)
729
- @log.progname = @appname
730
- @log.level = @level
731
- end
732
-
733
- def log=(logdev)
734
- set_log(logdev)
735
- end
736
-
737
- #
738
- # Set the logging threshold, just like <tt>Logger#level=</tt>.
739
- #
740
- def level=(level)
741
- @level = level
742
- @log.level = @level
743
- end
744
-
745
- #
746
- # See Logger#add. This application's +appname+ is used.
747
- #
748
- def log(severity, message = nil, &block)
749
- @log.add(severity, message, @appname, &block) if @log
750
- end
751
-
752
- private
753
-
754
- def run
755
- # TODO: should be an NotImplementedError
756
- raise RuntimeError.new('Method run must be defined in the derived class.')
757
- end
758
852
  end
759
853
  end