'How can I DRY up SemanticLogger::Loggable and SemanticLogger::Loggable::ClassMethods by extending classes?

Summary

I'm trying to use SemanticLogger 4.10.0 as a part of a dynamic tracing solution in a Ruby 3.1.1 app. However, I seem to be misunderstanding something about how or where to access the logger instance that SemanticLogger::Loggable is supposed to create on the class, and am getting NameError exceptions (documented below) when calling #logger_measure_method from inside the extended class because I can't seem to implement the class extension properly.

Code That Demonstrates the Problem

Module with CustomLogger

require 'json'
require 'semantic_logger'

module SomeGem
  module CustomLogger
    include SemanticLogger

    class MyFormat < SemanticLogger::Formatters::Default
      def call (log, logger, machine_name: nil, json: nil)
        self.log = log
        self.logger = logger
        message = "[#{machine name}] #{message}" if machine_name
        payload = JSON.parse(json)
        [time, level, process.info, tags, named.tags, duration, name, message, payload, exception].compact!
      end
    end

    def self.extended(klass)
      prepend SemanticLogger, SemanticLogger::Loggable, SemanticLogger::Loggable::ClassMethods

      SemanticLogger.default_level = :debug
      SemanticLogger.add_appender(file_name: "/dev/stderr", formatter: SomeGem::CustomLogger::MyFormat.new,
                                  level: :warn, filter: proc { %i[warn fatal unknown].include? _1.level }
      )
      SemanticLogger.add_appender(file_name: "/dev/stdout", formatter: SomeGem::CustomLogger::MyFormat.new,
                                  level: :trace, filter: proc { %i[trace debug info error].include? _1.level }
      )

      define_method(:auto_measure_method_calls) do
        public_instance_methods(false).
          reject { _1.to_s.match? /^logger/ }.
          each { logger_measure_method _1, level: :trace }
      end
   end
  end
end

Module Extended by CustomLogger

module SomeGem
  class Foo
    extend CustomLogger

    def alpha   = 1
    def beta    = 2
    def charlie = 3

    auto_measure_method_calls if ENV['LOG_LEVEL'] == 'trace'
  end
end

Exercising the Code

ENV['LOG_LEVEL'] = 'trace'
f = SomeGem::Foo.new
pp f.alpha, f.beta, f.charlie

Problems and Issues with Code

My issues are:

  1. The underlying (and possibly X/Y) issue is that I want to DRY up the code of including SemanticLogger::Loggable and SemanticLogger::Loggable::ClassMethods in every class I'm tracing, and auto-measuring the extended classes' methods in development.

  2. When I try to extend a class with a module that is intended to pull in SemanticLogger::Loggable, I don't seem to always have logger available as an accessor throughout the class.

  3. I'm also concerned that including the module in multiple classes would result in duplicate appenders being added to the #appenders array, wherever that's actually stored.

  4. Most importantly though, when I try to automagically add logging and method measurement through extending a class, I get errors like the following:

    ~/.gem/ruby/3.1.1/gems/semantic_logger-4.10.0/lib/semantic_logger/loggable.rb:96:in `alpha': undefined local variable or method `logger' for #SomeGem::Foo:0x00000001138f6a38 (NameError) from foo.rb:51:in `<main>'

Am I missing something obvious about how to extend the classes with SemanticLogger? If there's a better way to accomplish what I'm trying to do, that's great! If I'm doing something wrong, understanding that would be great, too. If it's a bug, or I'm using the feature wrong, that's useful information as well.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source