Skip to content

Commit 47c70c0

Browse files
committed
New feature to allow adding extra information to log
- ### Context It's quite common to add extra info to each log messages to make debugging easier. For instance, adding a request ID to trace the request more easily if an exception occurs. Or add user information for various needs. Right now, the easiest way to implement this is to create a custom formatter. ### Problem I believe formatting the log and adding extra information are two distincts features that should be handled with different objects. Moreover, because libraries or frameworks usually provide this feature, it makes things hard for them to not conflict with each others. As well as conflicting with the potential formatter set by an application. It ends up in a cascade of monkeypatch and doesn't allow much flexibility for the end user. ### Solution I'd like to introduce the concept of a log processor who's interface is similar to the formatter but its responsibility is to only add extra info to the message. More importantly, a logger can have multiple processors, each defining the logic within their scope. This should allow for a lot more flexibility (applications can now easily re-order processors, remove or add them on the fly, change their formatter afterward without breaking libraries functionalities). It will also make things easier for libraries and provide a cleaner way to hook into the logger. This change is fully backward compatible.
1 parent d5e9dba commit 47c70c0

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

lib/logger.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,51 @@ def datetime_format
473473
#
474474
attr_accessor :formatter
475475

476+
# Sets or retrieves the logger entries processors.
477+
#
478+
# A processor allows to add extra information to your log. For instance, to trace a
479+
# request more easily, a processor can be added to add a request ID on each log.
480+
# Multiple processors can be added to a logger, each passing the processed log
481+
# to the next one.
482+
#
483+
# A processor can either be a proc or a class that responds to `call` and gets
484+
# called with four arguments:
485+
#
486+
# - +severity+: The severity of the entry.
487+
# - +time+: A Time object representing the entry's timestamp.
488+
# - +progname+: The program name for the entry.
489+
# - +msg+: The message for the entry (string or string-convertible object).
490+
#
491+
# A processor should return a string containing the processed entry.
492+
#
493+
# Example to add extra information of the request and user to each log.
494+
#
495+
# class UserLogProcessor
496+
# def call(severity, time, progname, msg)
497+
# "[User login: #{user.login}] #{msg}"
498+
# end
499+
#
500+
# def user
501+
# # Implement to get the user for the current session
502+
# end
503+
# end
504+
#
505+
# class RequestLogProcessor
506+
# def call(severity, time, progname, msg)
507+
# "[#{request.uuid}] [#{request.ip}] #{msg}"
508+
# end
509+
# end
510+
#
511+
# logger = Logger.new($stdout)
512+
# logger.processors << RequestLogProcessor.new << UserLogprocessor.new
513+
# logger.add(Logger::INFO, "hello \n ''")
514+
#
515+
# Output:
516+
#
517+
# I, [2022-05-13T13:16:29.637488 #8492] INFO -- : [John] [0000-1111] [192.168.0.1] hello
518+
#
519+
attr_accessor :processors
520+
476521
alias sev_threshold level
477522
alias sev_threshold= level=
478523

@@ -584,6 +629,7 @@ def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG,
584629
@default_formatter = Formatter.new
585630
self.datetime_format = datetime_format
586631
self.formatter = formatter
632+
self.processors = []
587633
@logdev = nil
588634
@level_override = {}
589635
if logdev && logdev != File::NULL
@@ -665,6 +711,7 @@ def add(severity, message = nil, progname = nil)
665711
progname = @progname
666712
end
667713
end
714+
message = process_message(format_severity(severity), Time.now, progname, message)
668715
@logdev.write(
669716
format_message(format_severity(severity), Time.now, progname, message))
670717
true
@@ -742,6 +789,14 @@ def format_severity(severity)
742789
SEV_LABEL[severity] || 'ANY'
743790
end
744791

792+
def process_message(severity, datetime, progname, msg)
793+
(processors || []).reverse_each do |processor|
794+
msg = processor.call(severity, datetime, progname, msg)
795+
end
796+
797+
msg
798+
end
799+
745800
def format_message(severity, datetime, progname, msg)
746801
(@formatter || @default_formatter).call(severity, datetime, progname, msg)
747802
end

test/logger/test_logger.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,37 @@ def o.call(severity, timestamp, progname, msg)
161161
assert_equal("<""<INFO-foo>>\n", line)
162162
end
163163

164+
def test_processors
165+
dummy = STDERR
166+
logger = Logger.new(dummy)
167+
# default
168+
log = log(logger, :info, "foo")
169+
assert_equal("foo\n", log.msg)
170+
# config
171+
user_info_processor = proc { |severity, timestamp, progname, msg|
172+
"[User ID: 123] [User Login: John] #{msg}\n"
173+
}
174+
logger.processors << user_info_processor
175+
log = log(logger, :info, "foo")
176+
assert_equal("[User ID: 123] [User Login: John] foo\n\n", log.msg)
177+
# again
178+
request_info_processor = Object.new
179+
def request_info_processor.call(severity, timestamp, progname, msg)
180+
"[Request ID: 1234] #{msg}"
181+
end
182+
logger.processors << request_info_processor
183+
log = log(logger, :info, "foo")
184+
assert_equal("[User ID: 123] [User Login: John] [Request ID: 1234] foo\n\n", log.msg)
185+
# reset
186+
logger.processors.clear
187+
log = log(logger, :info, "foo")
188+
assert_equal("foo\n", log.msg)
189+
# again
190+
logger.processors = nil
191+
log = log(logger, :info, "foo")
192+
assert_equal("foo\n", log.msg)
193+
end
194+
164195
def test_initialize
165196
logger = Logger.new(STDERR)
166197
assert_nil(logger.progname)

0 commit comments

Comments
 (0)