From d7f194c2adf45031ae6fc4549c6ffbe4fc57c222 Mon Sep 17 00:00:00 2001 From: Dorian Sawa Date: Fri, 31 Jul 2020 09:55:31 +0200 Subject: [PATCH] Feature/tagged logging (#1) (#2) * Add tagged logging support Implement tagged logging similar to ActiveSupport::TaggedLogging. * Use log_tags attribute on logger Don't use the default_options to pass the tags into the logger. This causes the tags field to be sent over to graylog as an array. Instead pass the tag names into an attribute called log_tags that matches with the Rails standard of config.log_tags. * Replace Thread#[] with Thread#thread_variable_get/set * Skip log tags tests for Ruby 1.9 * Fix CI for JRuby Co-authored-by: Mark Glenn Co-authored-by: Marcus Ilgner Co-authored-by: Vladimir Kitaev Co-authored-by: Mark Glenn Co-authored-by: Marcus Ilgner Co-authored-by: Vladimir Kitaev --- Gemfile.lock | 3 --- lib/gelf/logger.rb | 36 +++++++++++++++++++++++++++++++++--- test/test_logger.rb | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6b18067..75ad8fe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,3 @@ DEPENDENCIES rack (< 2.0) shoulda (~> 2.11.3) test-unit (~> 3.2.0) - -BUNDLED WITH - 1.14.6 diff --git a/lib/gelf/logger.rb b/lib/gelf/logger.rb index 6377cd6..4405d3c 100644 --- a/lib/gelf/logger.rb +++ b/lib/gelf/logger.rb @@ -2,7 +2,7 @@ module GELF # Methods for compatibility with Ruby Logger. module LoggerCompatibility - attr_accessor :formatter + attr_accessor :formatter, :log_tags # Use it like Logger#add... or better not to use at all. def add(level, message = nil, progname = nil, &block) @@ -28,9 +28,14 @@ def add(level, message = nil, progname = nil, &block) message_hash.merge!(self.class.extract_hash_from_exception(message)) end - if message_hash.key?('short_message') && !message_hash['short_message'].empty? - notify_with_level(level, message_hash) + return if !message_hash.key?('short_message') || message_hash['short_message'].empty? + + # Include tags in message hash + Array(log_tags).each_with_index do |tag_name, index| + message_hash.merge!("_#{tag_name}" => current_tags[index]) if current_tags[index] end + + notify_with_level(level, message_hash) end # Redefines methods in +Notifier+. @@ -51,12 +56,37 @@ def add(level, message = nil, progname = nil, &block) def <<(message) notify_with_level(GELF::UNKNOWN, 'short_message' => message) end + + def tagged(*tags) + new_tags = push_tags(*tags) + yield self + ensure + current_tags.pop(new_tags.size) + end + + def push_tags(*tags) + tags.flatten.reject{ |t| t.respond_to?(:empty?) ? !!t.empty? : !t }.tap do |new_tags| + current_tags.concat new_tags + end + end + + def current_tags + val = Thread.current.thread_variable_get(:gelf_tagged_logging_tags) + return val unless val.nil? + Thread.current.thread_variable_set(:gelf_tagged_logging_tags, []) + end end # Graylog2 notifier, compatible with Ruby Logger. # You can use it with Rails like this: # config.logger = GELF::Logger.new("localhost", 12201, "WAN", { :facility => "appname" }) # config.colorize_logging = false + # + # Tagged logging (with tags from rack middleware) (order of tags is important) + # Adds custom gelf messages: { '_uuid_name' => , '_remote_ip_name' => } + # config.logger = GELF::Logger.new("localhost", 12201, "LAN", { :facility => "appname" }) + # config.log_tags = [:uuid, :remote_ip] + # config.logger.log_tags = [:uuid_name, :remote_ip_name] # Same order as config.log_tags class Logger < Notifier include LoggerCompatibility end diff --git a/test/test_logger.rb b/test/test_logger.rb index 7498199..b78672c 100644 --- a/test/test_logger.rb +++ b/test/test_logger.rb @@ -237,6 +237,42 @@ class TestLogger < Test::Unit::TestCase @logger.formatter end + # Supports only Ruby 2.0 or higher + if RUBY_VERSION[0, 1].to_i >= 2 + context "#tagged" do + # logger.tagged("TAG") { logger.info "Message" } + should "support tagged method" do + @logger.expects(:notify_with_level!).with do |level, hash| + level == GELF::INFO && + hash['short_message'] == 'Message' && + hash['facility'] == 'gelf-rb' + end + + str = "TAG" + str.stubs(:blank?).returns(true) + + @logger.tagged(str) { @logger.info "Message" } + end + + should "set custom gelf message with tag name and tag content" do + # I want the first tag with name 'test_tag' + @logger.log_tags = [:test_tag] + + @logger.expects(:notify_with_level!).with do |level, hash| + level == GELF::INFO && + hash['short_message'] == 'Message' && + hash['facility'] == 'gelf-rb' && + hash['_test_tag'] == 'TAG' # TAG should be in the hash + end + + str = "TAG" + str.stubs(:blank?).returns(false) + + @logger.tagged(str) { @logger.info "Message" } + end + end + end + context "close" do should "close socket" do @sender.expects(:close).once