David Moles
David Moles

Reputation: 51093

Stop ActiveSupport::TaggedLogging from interfering with structured logs

ActiveSupport::TaggedLogging works by appending ActiveSupport::TaggedLogging::Formatter to the inheritance chain of the current formatter via Object#extend.

Unfortunately, ActiveSupport::TaggedLogging::Formatter#call assumes the msg argument is always a string, resulting in garbage when it prepends tags to a hash (e.g. with Lograge and Ougai). And certain standard libraries, like Webpacker, insist on injecting TaggedLogging.

I've come up with the metaprogramming hack below to prevent ActiveSupport::TaggedLogging::Formatter#call from overriding the superclass implementation (in this case from Ougai):

class MyBunyanFormatter < Ougai::Formatters::Bunyan
  def extend(mod)
    return super unless mod.method_defined?(:call)

    call_original = self.method(:call)            # 1. preserve original
    super(mod)                                    # 2. overridden here
    define_singleton_method(:call, call_original) # 3. de-overridden here
  end
end

But it's clunky and counterintuitive, and I'm not excited about it.

The other alternative, at least in the case of Webpacker, seems to be to write a custom logger that implements tagged():

class MyLogger < Ougai::Logger
  def tagged(*tags)
    # do something with tags
    yield self
  end
end

This at least doesn't involve any metaprogramming shenanigans, but I wonder if I can count on other libraries besides Webpacker not to inject TaggedLogging anyway.

Surely I'm not the first person to have this problem since people started trying to do structured logging from Rails apps. What's the right solution?

Upvotes: 3

Views: 764

Answers (1)

David Moles
David Moles

Reputation: 51093

The solution given in the Ougai docs is to monkey-patch TaggedLogging to eliminate this behavior.

module ActiveSupport::TaggedLogging::Formatter
  def call(severity, time, progname, data)
    data = { msg: data.to_s } unless data.is_a?(Hash)
    tags = current_tags
    data[:tags] = tags if tags.present?
    _call(severity, time, progname, data)
  end
end

Note that _call only works if for Ougai-based Formatters.

Upvotes: 2

Related Questions