diff options
| author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 | 
|---|---|---|
| committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 | 
| commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
| tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/logger.rb | |
Fresh start
Diffstat (limited to 'jni/ruby/lib/logger.rb')
| -rw-r--r-- | jni/ruby/lib/logger.rb | 737 | 
1 files changed, 737 insertions, 0 deletions
| diff --git a/jni/ruby/lib/logger.rb b/jni/ruby/lib/logger.rb new file mode 100644 index 0000000..a5d88c5 --- /dev/null +++ b/jni/ruby/lib/logger.rb @@ -0,0 +1,737 @@ +# logger.rb - simple logging utility +# Copyright (C) 2000-2003, 2005, 2008, 2011  NAKAMURA, Hiroshi <nahi@ruby-lang.org>. +# +# Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair +# License:: +#   You can redistribute it and/or modify it under the same terms of Ruby's +#   license; either the dual license version in 2003, or any later version. +# Revision:: $Id: logger.rb 47272 2014-08-25 04:03:33Z nobu $ +# +# A simple system for logging messages.  See Logger for more documentation. + +require 'monitor' + +# == Description +# +# The Logger class provides a simple but sophisticated logging utility that +# you can use to output messages. +# +# The messages have associated levels, such as +INFO+ or +ERROR+ that indicate +# their importance.  You can then give the Logger a level, and only messages +# at that level or higher will be printed. +# +# The levels are: +# +# +UNKNOWN+:: An unknown message that should always be logged. +# +FATAL+:: An unhandleable error that results in a program crash. +# +ERROR+:: A handleable error condition. +# +WARN+::  A warning. +# +INFO+::  Generic (useful) information about system operation. +# +DEBUG+:: Low-level information for developers. +# +# For instance, in a production system, you may have your Logger set to +# +INFO+ or even +WARN+. +# When you are developing the system, however, you probably +# want to know about the program's internal state, and would set the Logger to +# +DEBUG+. +# +# *Note*: Logger does not escape or sanitize any messages passed to it. +# Developers should be aware of when potentially malicious data (user-input) +# is passed to Logger, and manually escape the untrusted data: +# +#   logger.info("User-input: #{input.dump}") +#   logger.info("User-input: %p" % input) +# +# You can use #formatter= for escaping all data. +# +#   original_formatter = Logger::Formatter.new +#   logger.formatter = proc { |severity, datetime, progname, msg| +#     original_formatter.call(severity, datetime, progname, msg.dump) +#   } +#   logger.info(input) +# +# === Example +# +# This creates a Logger that outputs to the standard output stream, with a +# level of +WARN+: +# +#   require 'logger' +# +#   logger = Logger.new(STDOUT) +#   logger.level = Logger::WARN +# +#   logger.debug("Created logger") +#   logger.info("Program started") +#   logger.warn("Nothing to do!") +# +#   path = "a_non_existent_file" +# +#   begin +#     File.foreach(path) do |line| +#       unless line =~ /^(\w+) = (.*)$/ +#         logger.error("Line in wrong format: #{line.chomp}") +#       end +#     end +#   rescue => err +#     logger.fatal("Caught exception; exiting") +#     logger.fatal(err) +#   end +# +# Because the Logger's level is set to +WARN+, only the warning, error, and +# fatal messages are recorded.  The debug and info messages are silently +# discarded. +# +# === Features +# +# There are several interesting features that Logger provides, like +# auto-rolling of log files, setting the format of log messages, and +# specifying a program name in conjunction with the message.  The next section +# shows you how to achieve these things. +# +# +# == HOWTOs +# +# === How to create a logger +# +# The options below give you various choices, in more or less increasing +# complexity. +# +# 1. Create a logger which logs messages to STDERR/STDOUT. +# +#      logger = Logger.new(STDERR) +#      logger = Logger.new(STDOUT) +# +# 2. Create a logger for the file which has the specified name. +# +#      logger = Logger.new('logfile.log') +# +# 3. Create a logger for the specified file. +# +#      file = File.open('foo.log', File::WRONLY | File::APPEND) +#      # To create new (and to remove old) logfile, add File::CREAT like: +#      # file = File.open('foo.log', File::WRONLY | File::APPEND | File::CREAT) +#      logger = Logger.new(file) +# +# 4. Create a logger which ages the logfile once it reaches a certain size. +#    Leave 10 "old" log files where each file is about 1,024,000 bytes. +# +#      logger = Logger.new('foo.log', 10, 1024000) +# +# 5. Create a logger which ages the logfile daily/weekly/monthly. +# +#      logger = Logger.new('foo.log', 'daily') +#      logger = Logger.new('foo.log', 'weekly') +#      logger = Logger.new('foo.log', 'monthly') +# +# === How to log a message +# +# Notice the different methods (+fatal+, +error+, +info+) being used to log +# messages of various levels?  Other methods in this family are +warn+ and +# +debug+.  +add+ is used below to log a message of an arbitrary (perhaps +# dynamic) level. +# +# 1. Message in a block. +# +#      logger.fatal { "Argument 'foo' not given." } +# +# 2. Message as a string. +# +#      logger.error "Argument #{@foo} mismatch." +# +# 3. With progname. +# +#      logger.info('initialize') { "Initializing..." } +# +# 4. With severity. +# +#      logger.add(Logger::FATAL) { 'Fatal error!' } +# +# The block form allows you to create potentially complex log messages, +# but to delay their evaluation until and unless the message is +# logged.  For example, if we have the following: +# +#     logger.debug { "This is a " + potentially + " expensive operation" } +# +# If the logger's level is +INFO+ or higher, no debug messages will be logged, +# and the entire block will not even be evaluated.  Compare to this: +# +#     logger.debug("This is a " + potentially + " expensive operation") +# +# Here, the string concatenation is done every time, even if the log +# level is not set to show the debug message. +# +# === How to close a logger +# +#      logger.close +# +# === Setting severity threshold +# +# 1. Original interface. +# +#      logger.sev_threshold = Logger::WARN +# +# 2. Log4r (somewhat) compatible interface. +# +#      logger.level = Logger::INFO +# +#      # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN +# +# == Format +# +# Log messages are rendered in the output stream in a certain format by +# default.  The default format and a sample are shown below: +# +# Log format: +#   SeverityID, [DateTime #pid] SeverityLabel -- ProgName: message +# +# Log sample: +#   I, [1999-03-03T02:34:24.895701 #19074]  INFO -- Main: info. +# +# You may change the date and time format via #datetime_format=. +# +#   logger.datetime_format = '%Y-%m-%d %H:%M:%S' +#         # e.g. "2004-01-03 00:54:26" +# +# Or, you may change the overall format via the #formatter= method. +# +#   logger.formatter = proc do |severity, datetime, progname, msg| +#     "#{datetime}: #{msg}\n" +#   end +#   # e.g. "2005-09-22 08:51:08 +0900: hello world" +# +class Logger +  VERSION = "1.2.7" +  _, name, rev = %w$Id: logger.rb 47272 2014-08-25 04:03:33Z nobu $ +  if name +    name = name.chomp(",v") +  else +    name = File.basename(__FILE__) +  end +  rev ||= "v#{VERSION}" +  ProgName = "#{name}/#{rev}" + +  class Error < RuntimeError # :nodoc: +  end +  # not used after 1.2.7. just for compat. +  class ShiftingError < Error # :nodoc: +  end + +  # Logging severity. +  module Severity +    # Low-level information, mostly for developers. +    DEBUG = 0 +    # Generic (useful) information about system operation. +    INFO = 1 +    # A warning. +    WARN = 2 +    # A handleable error condition. +    ERROR = 3 +    # An unhandleable error that results in a program crash. +    FATAL = 4 +    # An unknown message that should always be logged. +    UNKNOWN = 5 +  end +  include Severity + +  # Logging severity threshold (e.g. <tt>Logger::INFO</tt>). +  attr_accessor :level + +  # Program name to include in log messages. +  attr_accessor :progname + +  # Set date-time format. +  # +  # +datetime_format+:: A string suitable for passing to +strftime+. +  def datetime_format=(datetime_format) +    @default_formatter.datetime_format = datetime_format +  end + +  # Returns the date format being used.  See #datetime_format= +  def datetime_format +    @default_formatter.datetime_format +  end + +  # Logging formatter, as a +Proc+ that will take four arguments and +  # return the formatted message. The arguments are: +  # +  # +severity+:: The Severity of the log message. +  # +time+:: A Time instance representing when the message was logged. +  # +progname+:: The #progname configured, or passed to the logger method. +  # +msg+:: The _Object_ the user passed to the log message; not necessarily a +  #         String. +  # +  # The block should return an Object that can be written to the logging +  # device via +write+.  The default formatter is used when no formatter is +  # set. +  attr_accessor :formatter + +  alias sev_threshold level +  alias sev_threshold= level= + +  # Returns +true+ iff the current severity level allows for the printing of +  # +DEBUG+ messages. +  def debug?; @level <= DEBUG; end + +  # Returns +true+ iff the current severity level allows for the printing of +  # +INFO+ messages. +  def info?; @level <= INFO; end + +  # Returns +true+ iff the current severity level allows for the printing of +  # +WARN+ messages. +  def warn?; @level <= WARN; end + +  # Returns +true+ iff the current severity level allows for the printing of +  # +ERROR+ messages. +  def error?; @level <= ERROR; end + +  # Returns +true+ iff the current severity level allows for the printing of +  # +FATAL+ messages. +  def fatal?; @level <= FATAL; end + +  # +  # :call-seq: +  #   Logger.new(logdev, shift_age = 7, shift_size = 1048576) +  #   Logger.new(logdev, shift_age = 'weekly') +  # +  # === Args +  # +  # +logdev+:: +  #   The log device.  This is a filename (String) or IO object (typically +  #   +STDOUT+, +STDERR+, or an open file). +  # +shift_age+:: +  #   Number of old log files to keep, *or* frequency of rotation (+daily+, +  #   +weekly+ or +monthly+). +  # +shift_size+:: +  #   Maximum logfile size (only applies when +shift_age+ is a number). +  # +  # === Description +  # +  # Create an instance. +  # +  def initialize(logdev, shift_age = 0, shift_size = 1048576) +    @progname = nil +    @level = DEBUG +    @default_formatter = Formatter.new +    @formatter = nil +    @logdev = nil +    if logdev +      @logdev = LogDevice.new(logdev, :shift_age => shift_age, +        :shift_size => shift_size) +    end +  end + +  # +  # :call-seq: +  #   Logger#add(severity, message = nil, progname = nil) { ... } +  # +  # === Args +  # +  # +severity+:: +  #   Severity.  Constants are defined in Logger namespace: +DEBUG+, +INFO+, +  #   +WARN+, +ERROR+, +FATAL+, or +UNKNOWN+. +  # +message+:: +  #   The log message.  A String or Exception. +  # +progname+:: +  #   Program name string.  Can be omitted.  Treated as a message if no +  #   +message+ and +block+ are given. +  # +block+:: +  #   Can be omitted.  Called to get a message string if +message+ is nil. +  # +  # === Return +  # +  # When the given severity is not high enough (for this particular logger), +  # log no message, and return +true+. +  # +  # === Description +  # +  # Log a message if the given severity is high enough.  This is the generic +  # logging method.  Users will be more inclined to use #debug, #info, #warn, +  # #error, and #fatal. +  # +  # <b>Message format</b>: +message+ can be any object, but it has to be +  # converted to a String in order to log it.  Generally, +inspect+ is used +  # if the given object is not a String. +  # A special case is an +Exception+ object, which will be printed in detail, +  # including message, class, and backtrace.  See #msg2str for the +  # implementation if required. +  # +  # === Bugs +  # +  # * Logfile is not locked. +  # * Append open does not need to lock file. +  # * If the OS supports multi I/O, records possibly may be mixed. +  # +  def add(severity, message = nil, progname = nil, &block) +    severity ||= UNKNOWN +    if @logdev.nil? or severity < @level +      return true +    end +    progname ||= @progname +    if message.nil? +      if block_given? +        message = yield +      else +        message = progname +        progname = @progname +      end +    end +    @logdev.write( +      format_message(format_severity(severity), Time.now, progname, message)) +    true +  end +  alias log add + +  # +  # Dump given message to the log device without any formatting.  If no log +  # device exists, return +nil+. +  # +  def <<(msg) +    unless @logdev.nil? +      @logdev.write(msg) +    end +  end + +  # +  # Log a +DEBUG+ message. +  # +  # See #info for more information. +  # +  def debug(progname = nil, &block) +    add(DEBUG, nil, progname, &block) +  end + +  # +  # :call-seq: +  #   info(message) +  #   info(progname, &block) +  # +  # Log an +INFO+ message. +  # +  # +message+:: The message to log; does not need to be a String. +  # +progname+:: In the block form, this is the #progname to use in the +  #              log message.  The default can be set with #progname=. +  # +block+:: Evaluates to the message to log.  This is not evaluated unless +  #           the logger's level is sufficient to log the message.  This +  #           allows you to create potentially expensive logging messages that +  #           are only called when the logger is configured to show them. +  # +  # === Examples +  # +  #   logger.info("MainApp") { "Received connection from #{ip}" } +  #   # ... +  #   logger.info "Waiting for input from user" +  #   # ... +  #   logger.info { "User typed #{input}" } +  # +  # You'll probably stick to the second form above, unless you want to provide a +  # program name (which you can do with #progname= as well). +  # +  # === Return +  # +  # See #add. +  # +  def info(progname = nil, &block) +    add(INFO, nil, progname, &block) +  end + +  # +  # Log a +WARN+ message. +  # +  # See #info for more information. +  # +  def warn(progname = nil, &block) +    add(WARN, nil, progname, &block) +  end + +  # +  # Log an +ERROR+ message. +  # +  # See #info for more information. +  # +  def error(progname = nil, &block) +    add(ERROR, nil, progname, &block) +  end + +  # +  # Log a +FATAL+ message. +  # +  # See #info for more information. +  # +  def fatal(progname = nil, &block) +    add(FATAL, nil, progname, &block) +  end + +  # +  # Log an +UNKNOWN+ message.  This will be printed no matter what the logger's +  # level is. +  # +  # See #info for more information. +  # +  def unknown(progname = nil, &block) +    add(UNKNOWN, nil, progname, &block) +  end + +  # +  # Close the logging device. +  # +  def close +    @logdev.close if @logdev +  end + +private + +  # Severity label for logging (max 5 chars). +  SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY) + +  def format_severity(severity) +    SEV_LABEL[severity] || 'ANY' +  end + +  def format_message(severity, datetime, progname, msg) +    (@formatter || @default_formatter).call(severity, datetime, progname, msg) +  end + + +  # Default formatter for log messages. +  class Formatter +    Format = "%s, [%s#%d] %5s -- %s: %s\n" + +    attr_accessor :datetime_format + +    def initialize +      @datetime_format = nil +    end + +    def call(severity, time, progname, msg) +      Format % [severity[0..0], format_datetime(time), $$, severity, progname, +        msg2str(msg)] +    end + +  private + +    def format_datetime(time) +      time.strftime(@datetime_format || "%Y-%m-%dT%H:%M:%S.%6N ".freeze) +    end + +    def msg2str(msg) +      case msg +      when ::String +        msg +      when ::Exception +        "#{ msg.message } (#{ msg.class })\n" << +          (msg.backtrace || []).join("\n") +      else +        msg.inspect +      end +    end +  end + +  module Period +    module_function + +    SiD = 24 * 60 * 60 + +    def next_rotate_time(now, shift_age) +      case shift_age +      when /^daily$/ +        t = Time.mktime(now.year, now.month, now.mday) + SiD +      when /^weekly$/ +        t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday) +      when /^monthly$/ +        t = Time.mktime(now.year, now.month, 1) + SiD * 31 +        mday = (1 if t.mday > 1) +      else +        return now +      end +      if mday or t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero? +        t = Time.mktime(t.year, t.month, mday || (t.mday + (t.hour > 12 ? 1 : 0))) +      end +      t +    end + +    def previous_period_end(now, shift_age) +      case shift_age +      when /^daily$/ +        t = Time.mktime(now.year, now.month, now.mday) - SiD / 2 +      when /^weekly$/ +        t = Time.mktime(now.year, now.month, now.mday) - (SiD * (now.wday + 1) + SiD / 2) +      when /^monthly$/ +        t = Time.mktime(now.year, now.month, 1) - SiD / 2 +      else +        return now +      end +      Time.mktime(t.year, t.month, t.mday, 23, 59, 59) +    end +  end + +  # Device used for logging messages. +  class LogDevice +    include Period + +    attr_reader :dev +    attr_reader :filename + +    class LogDeviceMutex +      include MonitorMixin +    end + +    def initialize(log = nil, opt = {}) +      @dev = @filename = @shift_age = @shift_size = nil +      @mutex = LogDeviceMutex.new +      if log.respond_to?(:write) and log.respond_to?(:close) +        @dev = log +      else +        @dev = open_logfile(log) +        @dev.sync = true +        @filename = log +        @shift_age = opt[:shift_age] || 7 +        @shift_size = opt[:shift_size] || 1048576 +        @next_rotate_time = next_rotate_time(Time.now, @shift_age) unless @shift_age.is_a?(Integer) +      end +    end + +    def write(message) +      begin +        @mutex.synchronize do +          if @shift_age and @dev.respond_to?(:stat) +            begin +              check_shift_log +            rescue +              warn("log shifting failed. #{$!}") +            end +          end +          begin +            @dev.write(message) +          rescue +            warn("log writing failed. #{$!}") +          end +        end +      rescue Exception => ignored +        warn("log writing failed. #{ignored}") +      end +    end + +    def close +      begin +        @mutex.synchronize do +          @dev.close rescue nil +        end +      rescue Exception +        @dev.close rescue nil +      end +    end + +  private + +    def open_logfile(filename) +      begin +        open(filename, (File::WRONLY | File::APPEND)) +      rescue Errno::ENOENT +        create_logfile(filename) +      end +    end + +    def create_logfile(filename) +      begin +        logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL)) +        logdev.flock(File::LOCK_EX) +        logdev.sync = true +        add_log_header(logdev) +        logdev.flock(File::LOCK_UN) +      rescue Errno::EEXIST +        # file is created by another process +        logdev = open_logfile(filename) +        logdev.sync = true +      end +      logdev +    end + +    def add_log_header(file) +      file.write( +        "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName] +      ) if file.size == 0 +    end + +    def check_shift_log +      if @shift_age.is_a?(Integer) +        # Note: always returns false if '0'. +        if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size) +          lock_shift_log { shift_log_age } +        end +      else +        now = Time.now +        if now >= @next_rotate_time +          @next_rotate_time = next_rotate_time(now, @shift_age) +          lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) } +        end +      end +    end + +    if /mswin|mingw/ =~ RUBY_PLATFORM +      def lock_shift_log +        yield +      end +    else +      def lock_shift_log +        retry_limit = 8 +        retry_sleep = 0.1 +        begin +          File.open(@filename, File::WRONLY | File::APPEND) do |lock| +            lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file +            if File.identical?(@filename, lock) and File.identical?(lock, @dev) +              yield # log shifting +            else +              # log shifted by another process (i-node before locking and i-node after locking are different) +              @dev.close rescue nil +              @dev = open_logfile(@filename) +              @dev.sync = true +            end +          end +        rescue Errno::ENOENT +          # @filename file would not exist right after #rename and before #create_logfile +          if retry_limit <= 0 +            warn("log rotation inter-process lock failed. #{$!}") +          else +            sleep retry_sleep +            retry_limit -= 1 +            retry_sleep *= 2 +            retry +          end +        end +      rescue +        warn("log rotation inter-process lock failed. #{$!}") +      end +    end + +    def shift_log_age +      (@shift_age-3).downto(0) do |i| +        if FileTest.exist?("#{@filename}.#{i}") +          File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}") +        end +      end +      @dev.close rescue nil +      File.rename("#{@filename}", "#{@filename}.0") +      @dev = create_logfile(@filename) +      return true +    end + +    def shift_log_period(period_end) +      postfix = period_end.strftime("%Y%m%d") # YYYYMMDD +      age_file = "#{@filename}.#{postfix}" +      if FileTest.exist?(age_file) +        # try to avoid filename crash caused by Timestamp change. +        idx = 0 +        # .99 can be overridden; avoid too much file search with 'loop do' +        while idx < 100 +          idx += 1 +          age_file = "#{@filename}.#{postfix}.#{idx}" +          break unless FileTest.exist?(age_file) +        end +      end +      @dev.close rescue nil +      File.rename("#{@filename}", age_file) +      @dev = create_logfile(@filename) +      return true +    end +  end +end | 
