logger 1.2.8.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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