diff --git a/lib/fluent/log/console_adapter.rb b/lib/fluent/log/console_adapter.rb new file mode 100644 index 0000000000..9c8ecf482e --- /dev/null +++ b/lib/fluent/log/console_adapter.rb @@ -0,0 +1,66 @@ +# +# Fluentd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'console/terminal/logger' + +module Fluent + class Log + # Async gem which is used by http_server helper switched logger mechanism to + # Console gem which isn't complatible with Ruby's standard Logger (since + # v1.17). This class adapts it to Fluentd's logger mechanism. + class ConsoleAdapter < Console::Terminal::Logger + def self.wrap(logger) + _, level = Console::Logger::LEVELS.find { |key, value| + if logger.level <= 0 + key == :debug + else + value == logger.level - 1 + end + } + Console::Logger.new(ConsoleAdapter.new(logger), level: level) + end + + def initialize(logger) + @logger = logger + # When `verbose` is `true`, following items will be added as a prefix or + # suffix of the subject: + # * Severity + # * Object ID + # * PID + # * Time + # Severity and Time are added by Fluentd::Log too so they are redundant. + # PID is the worker's PID so it's also redundant. + # Object ID will be too verbose in usual cases. + # So set it as `false` here to suppress redundant items. + super(StringIO.new, verbose: false) + end + + def call(subject = nil, *arguments, name: nil, severity: 'info', **options, &block) + if LEVEL_TEXT.include?(severity.to_s) + level = severity + else + @logger.warn("Unknown severity: #{severity}") + level = 'warn' + end + + @io.seek(0) + @io.truncate(0) + super + @logger.send(level, @io.string.chomp) + end + end + end +end diff --git a/lib/fluent/plugin_helper/http_server/server.rb b/lib/fluent/plugin_helper/http_server/server.rb index 4266bc639f..549db76cc8 100644 --- a/lib/fluent/plugin_helper/http_server/server.rb +++ b/lib/fluent/plugin_helper/http_server/server.rb @@ -21,6 +21,7 @@ require 'fluent/plugin_helper/http_server/app' require 'fluent/plugin_helper/http_server/router' require 'fluent/plugin_helper/http_server/methods' +require 'fluent/log/console_adapter' module Fluent module PluginHelper @@ -38,7 +39,7 @@ def initialize(addr:, port:, logger:, default_app: nil, tls_context: nil) scheme = tls_context ? 'https' : 'http' @uri = URI("#{scheme}://#{@addr}:#{@port}").to_s @router = Router.new(default_app) - @reactor = Async::Reactor.new(nil, logger: @logger) + @reactor = Async::Reactor.new(nil, logger: Fluent::Log::ConsoleAdapter.wrap(@logger)) opts = if tls_context { ssl_context: tls_context } diff --git a/test/log/test_console_adapter.rb b/test/log/test_console_adapter.rb new file mode 100644 index 0000000000..06d9d900ad --- /dev/null +++ b/test/log/test_console_adapter.rb @@ -0,0 +1,110 @@ +require_relative '../helper' + +require 'fluent/log' +require 'fluent/log/console_adapter' + +class ConsoleAdapterTest < Test::Unit::TestCase + def setup + @timestamp = Time.parse("2023-01-01 15:32:41 +0000") + @timestamp_str = @timestamp.strftime("%Y-%m-%d %H:%M:%S %z") + Timecop.freeze(@timestamp) + + @logdev = Fluent::Test::DummyLogDevice.new + @logger = ServerEngine::DaemonLogger.new(@logdev) + @fluent_log = Fluent::Log.new(@logger) + @console_logger = Fluent::Log::ConsoleAdapter.wrap(@fluent_log) + end + + def teardown + Timecop.return + end + + def test_expected_log_levels + assert_equal({debug: 0, info: 1, warn: 2, error: 3, fatal: 4}, + Console::Logger::LEVELS) + end + + data(trace: [Fluent::Log::LEVEL_TRACE, :debug], + debug: [Fluent::Log::LEVEL_DEBUG, :debug], + info: [Fluent::Log::LEVEL_INFO, :info], + warn: [Fluent::Log::LEVEL_WARN, :warn], + error: [Fluent::Log::LEVEL_ERROR, :error], + fatal: [Fluent::Log::LEVEL_FATAL, :fatal]) + def test_reflect_log_level(data) + level, expected = data + @fluent_log.level = level + console_logger = Fluent::Log::ConsoleAdapter.wrap(@fluent_log) + assert_equal(Console::Logger::LEVELS[expected], + console_logger.level) + end + + data(debug: :debug, + info: :info, + warn: :warn, + error: :error, + fatal: :fatal) + def test_string_subject(level) + @console_logger.send(level, "subject") + assert_equal(["#{@timestamp_str} [#{level}]: 0.0s: subject\n"], + @logdev.logs) + end + + data(debug: :debug, + info: :info, + warn: :warn, + error: :error, + fatal: :fatal) + def test_args(level) + @console_logger.send(level, "subject", 1, 2, 3) + assert_equal([ + "#{@timestamp_str} [#{level}]: 0.0s: subject\n" + + " | 1\n" + + " | 2\n" + + " | 3\n" + ], + @logdev.logs) + end + + data(debug: :debug, + info: :info, + warn: :warn, + error: :error, + fatal: :fatal) + def test_options(level) + @console_logger.send(level, "subject", kwarg1: "opt1", kwarg2: "opt2") + assert_equal([ + "#{@timestamp_str} [#{level}]: 0.0s: subject\n" + + " | {\"kwarg1\":\"opt1\",\"kwarg2\":\"opt2\"}\n" + ], + @logdev.logs) + end + + data(debug: :debug, + info: :info, + warn: :warn, + error: :error, + fatal: :fatal) + def test_block(level) + @console_logger.send(level, "subject") { "block message" } + assert_equal([ + "#{@timestamp_str} [#{level}]: 0.0s: subject\n" + + " | block message\n" + ], + @logdev.logs) + end + + data(debug: :debug, + info: :info, + warn: :warn, + error: :error, + fatal: :fatal) + def test_multiple_entries(level) + @console_logger.send(level, "subject1") + @console_logger.send(level, "line2") + assert_equal([ + "#{@timestamp_str} [#{level}]: 0.0s: subject1\n", + "#{@timestamp_str} [#{level}]: 0.0s: line2\n" + ], + @logdev.logs) + end +end diff --git a/test/plugin_helper/test_http_server_helper.rb b/test/plugin_helper/test_http_server_helper.rb index 6691c9182d..4fd044bb0e 100644 --- a/test/plugin_helper/test_http_server_helper.rb +++ b/test/plugin_helper/test_http_server_helper.rb @@ -127,7 +127,7 @@ def start_https_request(addr, port, verify: true, cert_path: nil, selfsigned: tr end client = Async::HTTP::Client.new(Async::HTTP::Endpoint.parse("https://#{addr}:#{port}", ssl_context: context)) - reactor = Async::Reactor.new(nil, logger: NULL_LOGGER) + reactor = Async::Reactor.new(nil, logger: Fluent::Log::ConsoleAdapter.wrap(NULL_LOGGER)) resp = nil error = nil