• Simplistic logging, my hand-knitted logging modul

    From Michael Uplawski@21:1/5 to All on Sun Dec 31 16:37:01 2023
    Good afternoon.

    I want to replace the Logger from Ruby core by my own simplistic Module “BasicLogging”, after years, when I had to admit that I am anyway never using
    all the features of the Logger class: It is the same formater for everything and I had never use for object- or class-specific configurations. This facilitates the configuration of the logger from an application-wide configuration. I can still “mute” individual objects/classes to reduce the output.

    The whole module is attached below this post.

    Chances are, I oversee a bunch of problems with my code. Programming is
    just a hobby. Are there obvious glitches, which I must address? In one of
    my programs, my module appears to work just as I like it to.

    Here a usage example for a class-level logger:
    ------------
    Array.extend(BasicLogging)
    Array.set_level(BasicLogging::INFO)
    Array.info('TEST')
    -----------
    output:
    Array [class] info 16:22:26:662619: TEST

    Object-level:
    ------------
    ar = Array.new
    ar.extend(BasicLogging)
    # --- no output, default level is UNKNOWN :
    l = __LINE__
    ar.debug(l.next.to_s << ': debug-test 0')
    # output on level debug
    ar.set_level(BasicLogging::DEBUG)
    l = __LINE__
    ar.debug(l.next.to_s << ': debug-test 1')
    --------------
    output:
    Array debug 16:22:26:662894: 124: debug-test 1

    The file basic_logging.rb contains more test-code at the bottom. Thank you
    for any comments. Shoot at will.

    ---------------------- basic_logging.rb -------------------
    #!/bin/env ruby
    #encoding: UTF-8
    =begin /***************************************************************************
    * ©2023-2023, Michael Uplawski <news:michael.uplawski@uplawski.eu> *
    * *
    * This program is free software; you can redistribute it and/or modify *
    * it under the terms of the WTFPL 2.0 or later, see *
    * <http://www.wtfpl.net/about/> *
    * *
    * This program is distributed in the hope that it will be useful, *
    * but WITHOUT ANY WARRANTY; without even the implied warranty of *
    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
    * *
    ***************************************************************************/ =end

    #
    # Simplified logging.
    # See example code at the bottom of this file.
    # Execute this file to see the output.

    module BasicLogging

    DEBUG = 0
    INFO = 1
    WARN = 2
    ERROR = 3
    FATAL = 4
    UNKNOWN = nil

    # this is mainly for the translation of method calls into log levels
    Levels = {:debug => DEBUG, :info => INFO, :warn => WARN, :error => ERROR,
    :fatal => FATAL, :unknown => UNKNOWN}

    @@log_level = UNKNOWN
    @@target = STDOUT
    @@muted = []

    # do not log, if caller is obj (class or instance)
    def self.mute(obj)
    name = obj.class == Class ? obj.name.dup : obj.class.name
    @@muted << name
    end

    def self.is_muted?(obj)
    name = obj.class == Class ? obj.name.dup : obj.class.name
    @@muted.include?(name)
    end

    # set the log level
    def set_level(lv)
    if lv.respond_to?(:to_str)
    lv = Levels[lv.to_sym]
    end
    if(!lv || (lv.respond_to?(:to_int) && lv >= DEBUG && lv <= FATAL) )
    @@log_level = lv
    else
    STDERR.puts __FILE__.dup << ": ERROR : invalid log level \"" << lv.to_s << "\""
    STDERR.puts "Keepinng old log level " << Levels.keys.detect {| k| Levels[k] == @@log_level}.to_s
    end
    end

    # set the log target
    def set_target(tg)
    if tg.respond_to?(:to_io)
    @@target = tg
    elsif(!File::exist?(tg) || ( File.file?(tg) && File.writable?(tg) ) )
    @@target = File.open(tg, 'w+')
    else
    STDERR.puts __FILE__.dup << ': ERROR : target ' << tg << ' cannot be set'
    STDERR.puts "Keeping old target " << @@target.inspect
    return
    end
    end

    # Output of log messages, depending on the log level
    # and the name of the alias method which is actually called.
    def log(message)
    if !BasicLogging.is_muted?(self)
    # how has this method been called?
    mlevel = __callee__
    if Levels.has_key?(mlevel) && Levels[mlevel] <= FATAL
    # output only for levels equal or above the value that corresponds to
    # the calling alias.
    format_log( message, mlevel) if @@log_level && Levels[mlevel] >= @@log_level
    else
    STDERR.puts __FILE__.dup << ": ERROR : invalid log level \"" << mlevel.to_s << "\""
    end
    end
    end

    alias :debug :log
    alias :info :log
    alias :warn :log
    alias :error :log
    alias :fatal :log

    private

    # 1 format for all loggers.
    def format_log(message, mlevel)
    # indicate if a class or an instance of a class is calling.
    name = self.class == Class ? self.name.dup << ' [class]' : self.class.name
    @@target.puts '' << name << ' ' << mlevel.to_s << ' ' << Time.now.strftime("%H:%M:%S:%6N") << ': ' << message
    end
    end
    #---------test: execute file----------
    if $0 == __FILE__
    Array.extend(BasicLogging)
    Array.set_level(BasicLogging::INFO)
    Array.info('TEST')
    ar = Array.new
    ar.extend(BasicLogging)
    # --- no output :
    l = __LINE__
    ar.debug(l.next.to_s << ': debug-test 0')
    # output
    ar.set_level(BasicLogging::DEBUG)
    l = __LINE__
    ar.debug(l.next.to_s << ': debug-test 1')

    obj = Object.new
    obj.extend(BasicLogging)
    obj.set_level(BasicLogging::DEBUG)
    puts "--------debug-----------"
    obj.debug('debug')
    obj.info('info')
    obj.warn('warn')
    obj.error('error')
    obj.fatal('fatal')
    puts "--------info-----------"
    obj.set_level("info")
    obj.debug('debug')
    obj.info('info')
    obj.warn('warn')
    obj.error('error')
    obj.fatal('fatal')
    puts "--------fatal-----------"
    obj.set_level("fatal")
    obj.debug('debug')
    obj.info('info')
    obj.warn('warn')
    obj.error('error')
    obj.fatal('fatal')
    puts "--------UNKNOWN-----------"
    obj.set_level(nil)
    obj.debug('debug')
    obj.info('info')
    obj.warn('warn')
    obj.error('error')
    obj.fatal('fatal')
    puts " ------ Output into file ----"
    obj.set_target "/tmp/test_log.log"
    puts " ------ INFO -----------"
    obj.set_level BasicLogging::INFO
    obj.info('info output')

    obj.info('info output 2')
    puts "---------- invalid -------"
    obj.set_target "/dev/sr0"
    obj.set_level "power"
    end

    # EOF

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Uplawski@21:1/5 to Michael Uplawski on Tue Jan 2 08:16:25 2024
    Michael Uplawski wrote in comp.lang.ruby:

    Chances are, I oversee a bunch of problems with my code. Programming is
    just a hobby. Are there obvious glitches, which I must address? In one of
    my programs, my module appears to work just as I like it to.

    The project in question is here: <https://www.rubydoc.info/gems/cremefraiche/1.2>

    I plan to use the same “BasicLogging" in my other scripts and programs.

    Cheerio

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)