From 6d5bc1f8e2145228bef3f9c1792dc1b1c1c7f058 Mon Sep 17 00:00:00 2001 From: nialljames Date: Sun, 5 Oct 2025 07:46:29 +0100 Subject: [PATCH 1/8] allow prefix colours via config block --- README.md | 46 ++++++++++++++++++++++++ lib/dvla/herodotus.rb | 6 ++-- lib/dvla/herodotus/herodotus_logger.rb | 50 ++++++++++++++++++++++---- lib/dvla/herodotus/multi_writer.rb | 3 +- lib/dvla/herodotus/version.rb | 2 +- 5 files changed, 95 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2fb7ae6..b7fb836 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,52 @@ This would result in logs in the following format: `[SystemName CurrentDate CurrentTime CorrelationId PID] Level : -- Message` +#### Prefix Colourisation +You can colourise the log prefix in several ways: + +**Apply colours to the entire prefix:** +```ruby +config = DVLA::Herodotus.config do |config| + config.prefix_colour = 'blue.bold' +end +logger = DVLA::Herodotus.logger('', config: config) +``` + +**Use an array of colour methods:** + +```ruby +config = DVLA::Herodotus.config do |config| + config.prefix_colour = %w[blue bold underline] +end +logger = DVLA::Herodotus.logger('', config: config) +``` + +**Apply different colours to individual components:** +```ruby +config = DVLA::Herodotus.config do |config| + config.prefix_colour = { + system: 'blue.bold', + date: 'green', + time: 'yellow', + correlation: 'magenta', + pid: 'cyan', + level: 'red.bold', + separator: 'white' + } +end +logger = DVLA::Herodotus.logger('', config: config) +``` + +The hash keys correspond to different parts of the log prefix: +- `system`: The system name +- `date`: The date portion (YYYY-MM-DD) +- `time`: The time portion (HH:MM:SS) +- `correlation`: The correlation ID +- `pid`: The process ID (when display_pid is enabled) +- `level`: The log level (INFO, WARN, etc.) +- `separator`: The "-- :" separator +- `overall`: Applied to the entire prefix after individual components are coloured + ### Syncing logs Herodotus allows you to Sync correlation_ids between instantiated HerodotusLogger objects. diff --git a/lib/dvla/herodotus.rb b/lib/dvla/herodotus.rb index 18ccb59..dee5f43 100644 --- a/lib/dvla/herodotus.rb +++ b/lib/dvla/herodotus.rb @@ -10,7 +10,7 @@ class << self attr_accessor :main_logger end - CONFIG_ATTRIBUTES = %i[display_pid main].freeze + CONFIG_ATTRIBUTES = %i[display_pid main prefix_colour].freeze def self.config config ||= Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true).new @@ -26,10 +26,10 @@ def self.logger(system_name, config: self.config, output_path: nil) if output_path if output_path.is_a? String output_file = File.open(output_path, 'a') - return HerodotusLogger.new(system_name, MultiWriter.new(output_file, $stdout), config: config) + return HerodotusLogger.new(system_name, MultiWriter.new(output_file, $stdout, config: config), config: config) elsif output_path.is_a? Proc proc_writer = ProcWriter.new(output_path) - return HerodotusLogger.new(system_name, MultiWriter.new(proc_writer, $stdout), config: config) + return HerodotusLogger.new(system_name, MultiWriter.new(proc_writer, $stdout, config: config), config: config) else raise ArgumentError.new 'Unexpected output_path provided. Expecting either a string or a proc' end diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb index 32b76f7..68eaec6 100644 --- a/lib/dvla/herodotus/herodotus_logger.rb +++ b/lib/dvla/herodotus/herodotus_logger.rb @@ -3,7 +3,7 @@ module DVLA module Herodotus class HerodotusLogger < Logger - attr_accessor :system_name, :correlation_id, :main, :display_pid, :scenario_id + attr_accessor :system_name, :correlation_id, :main, :display_pid, :scenario_id, :prefix_colour # Initializes the logger # Sets a default correlation_id and creates the formatter @@ -15,6 +15,7 @@ def initialize(system_name, *args, config: DVLA::Herodotus.config, **kwargs) @system_name = system_name @main = config[:main] @display_pid = config[:display_pid] + @prefix_colour = config[:prefix_colour] @correlation_id = SecureRandom.uuid[0, 8] set_formatter @@ -65,19 +66,54 @@ def sync_correlation_ids end # Sets the format of the log. - # Needs to be called each time correlation_id is changed after initialization in-order for the changes to take affect. + # Needs to be called each time correlation_id is changed after initialization in-order for the changes to take effect. def set_formatter self.formatter = proc do |severity, _datetime, _progname, msg| - "[#{@system_name} " \ - "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} " \ - "#{@correlation_id}" \ - "#{' '.concat(Process.pid.to_s) if @display_pid}] " \ - "#{severity} -- : #{msg}\n" + now = Time.now + system = @system_name + date = now.strftime('%Y-%m-%d') + time = now.strftime('%H:%M:%S') + correlation = @correlation_id + pid = @display_pid ? Process.pid.to_s : nil + level = severity + separator = '-- :' + + prefix = case @prefix_colour + # Colourise the whole prefix + when Array, String + bracket_content = [system, date, time, correlation, pid].compact.join(' ') + colourise_text("[#{bracket_content}] #{level} #{separator} ", @prefix_colour) + when Hash + # Colour each component individually and wrap in an overall colour + s = @prefix_colour[:system] ? colourise_text(system, @prefix_colour[:system]) : system + d = @prefix_colour[:date] ? colourise_text(date, @prefix_colour[:date]) : date + t = @prefix_colour[:time] ? colourise_text(time, @prefix_colour[:time]) : time + c = @prefix_colour[:correlation] ? colourise_text(correlation, @prefix_colour[:correlation]) : correlation + p = pid && @prefix_colour[:pid] ? colourise_text(pid, @prefix_colour[:pid]) : pid + l = @prefix_colour[:level] ? colourise_text(level, @prefix_colour[:level]) : level + sep = @prefix_colour[:separator] ? colourise_text(separator, @prefix_colour[:separator]) : separator + bracket_content = [s, d, t, c, p].compact.join(' ') + result = "[#{bracket_content}] #{l} #{sep} " + @prefix_colour[:overall] ? colourise_text(result, @prefix_colour[:overall]) : result + else + # No colourisation + bracket_content = [system, date, time, correlation, pid].compact.join(' ') + "[#{bracket_content}] #{level} #{separator} " + end + + "#{prefix}#{msg}\n" end end private + def colourise_text(text, colour_spec) + return text unless colour_spec + + methods = colour_spec.is_a?(Array) ? colour_spec : colour_spec.to_s.split('.') + methods.reduce(text) { |str, method| str.public_send(method) } + end + def set_proc_writer_scenario if @logdev.dev.is_a?(DVLA::Herodotus::MultiWriter) && @logdev.dev.targets.any?(DVLA::Herodotus::ProcWriter) proc_writers = @logdev.dev.targets.select { |t| t.is_a? DVLA::Herodotus::ProcWriter } diff --git a/lib/dvla/herodotus/multi_writer.rb b/lib/dvla/herodotus/multi_writer.rb index a8eba5b..94ea2a2 100644 --- a/lib/dvla/herodotus/multi_writer.rb +++ b/lib/dvla/herodotus/multi_writer.rb @@ -3,7 +3,8 @@ module Herodotus class MultiWriter attr_reader :targets - def initialize(*targets) + def initialize(*targets, config: nil) + @config = config @targets = *targets end diff --git a/lib/dvla/herodotus/version.rb b/lib/dvla/herodotus/version.rb index 65683fb..1b0197a 100644 --- a/lib/dvla/herodotus/version.rb +++ b/lib/dvla/herodotus/version.rb @@ -1,5 +1,5 @@ module DVLA module Herodotus - VERSION = '2.2.1'.freeze + VERSION = '2.3.0'.freeze end end From c9a943b3b6e9f5d68c514f11cc2cb4f8bb12c937 Mon Sep 17 00:00:00 2001 From: nialljames Date: Sun, 5 Oct 2025 08:22:32 +0100 Subject: [PATCH 2/8] update spec tests --- spec/dvla/herodotus/herodotus_logger_spec.rb | 55 ++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/spec/dvla/herodotus/herodotus_logger_spec.rb b/spec/dvla/herodotus/herodotus_logger_spec.rb index d214464..19bf888 100644 --- a/spec/dvla/herodotus/herodotus_logger_spec.rb +++ b/spec/dvla/herodotus/herodotus_logger_spec.rb @@ -154,4 +154,59 @@ expect(logger2.scenario_id).to eq('blah') end end + + context 'prefix colourisation' do + before(:each) do + allow(Time).to receive(:now).and_return(Time.new(2022)) + allow(SecureRandom).to receive(:uuid).and_return('123e4567-e89b-12d3-a456-426614174000') + end + + it 'colours prefix via string' do + config = DVLA::Herodotus.config { |c| c.prefix_colour = 'blue.bold' } + logger = DVLA::Herodotus.logger('rspec', config: config) + + expect { logger.info('test') }.to output("\e[1m\e[34m[rspec 2022-01-01 00:00:00 123e4567] INFO -- : \e[39m\e[22mtest\n") + .to_stdout_from_any_process + end + + it 'colours prefix with array of strings' do + config = DVLA::Herodotus.config { |c| c.prefix_colour = %w[blue bold] } + logger = DVLA::Herodotus.logger('rspec', config: config) + + expect { logger.info('test') }.to output("\e[1m\e[34m[rspec 2022-01-01 00:00:00 123e4567] INFO -- : \e[39m\e[22mtest\n") + .to_stdout_from_any_process + end + + it 'colours prefix individual components' do + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + system: 'blue.bold', + date: 'green', + time: 'yellow', + correlation: 'magenta', + pid: 'cyan', + level: 'red.bold', + separator: 'white', + } + end + logger = DVLA::Herodotus.logger('rspec', config: config) + + expected_output = "[\e[1m\e[34mrspec\e[39m\e[22m \e[32m2022-01-01\e[39m \e[93m00:00:00\e[39m \e[35m123e4567\e[39m] \e[1m\e[31mINFO\e[39m\e[22m \e[97m-- :\e[39m test\n" + expect { logger.info('test') }.to output(expected_output).to_stdout_from_any_process + end + + it 'only colourises its own prefix' do + main_config = DVLA::Herodotus.config { |c| c.main = true } + main_logger = DVLA::Herodotus.logger('main', config: main_config) + + secondary_config = DVLA::Herodotus.config { |c| c.prefix_colour = 'red' } + secondary_logger = DVLA::Herodotus.logger('secondary', config: secondary_config) + + expect { main_logger.info('main test') }.to output("[main 2022-01-01 00:00:00 123e4567] INFO -- : main test\n") + .to_stdout_from_any_process + + expect { secondary_logger.info('secondary test') }.to output("\e[31m[secondary 2022-01-01 00:00:00 123e4567] INFO -- : \e[39msecondary test\n") + .to_stdout_from_any_process + end + end end From 33fee62149942c198243f17fde134dd1e3cfc2eb Mon Sep 17 00:00:00 2001 From: nialljames Date: Sun, 5 Oct 2025 08:22:46 +0100 Subject: [PATCH 3/8] update change log --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217b59f..e9cdc0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog All notable changes to this project will be documented in this file. +## [2.3.0] - 2025-10-05 +- Config block can now accept prefix colour options. Can be applied to the whole prefix or configure individual components. + ## [2.2.1] - 2025-09-15 - Fixed issue with ANSI exit codes breaking on string interpolation - Added strip_colour method to String which we now call when sending logs to file From e86e5dd9e9498474cc276bb41c2bdbbf1bc044d8 Mon Sep 17 00:00:00 2001 From: nialljames Date: Mon, 6 Oct 2025 07:47:39 +0100 Subject: [PATCH 4/8] refactor --- lib/dvla/herodotus/herodotus_logger.rb | 102 ++++++++++++++++--------- 1 file changed, 67 insertions(+), 35 deletions(-) diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb index 68eaec6..7262829 100644 --- a/lib/dvla/herodotus/herodotus_logger.rb +++ b/lib/dvla/herodotus/herodotus_logger.rb @@ -16,6 +16,7 @@ def initialize(system_name, *args, config: DVLA::Herodotus.config, **kwargs) @main = config[:main] @display_pid = config[:display_pid] @prefix_colour = config[:prefix_colour] + @colour_methods = build_colour_methods(@prefix_colour) @correlation_id = SecureRandom.uuid[0, 8] set_formatter @@ -70,48 +71,79 @@ def sync_correlation_ids def set_formatter self.formatter = proc do |severity, _datetime, _progname, msg| now = Time.now - system = @system_name - date = now.strftime('%Y-%m-%d') - time = now.strftime('%H:%M:%S') - correlation = @correlation_id - pid = @display_pid ? Process.pid.to_s : nil - level = severity - separator = '-- :' - - prefix = case @prefix_colour - # Colourise the whole prefix - when Array, String - bracket_content = [system, date, time, correlation, pid].compact.join(' ') - colourise_text("[#{bracket_content}] #{level} #{separator} ", @prefix_colour) - when Hash - # Colour each component individually and wrap in an overall colour - s = @prefix_colour[:system] ? colourise_text(system, @prefix_colour[:system]) : system - d = @prefix_colour[:date] ? colourise_text(date, @prefix_colour[:date]) : date - t = @prefix_colour[:time] ? colourise_text(time, @prefix_colour[:time]) : time - c = @prefix_colour[:correlation] ? colourise_text(correlation, @prefix_colour[:correlation]) : correlation - p = pid && @prefix_colour[:pid] ? colourise_text(pid, @prefix_colour[:pid]) : pid - l = @prefix_colour[:level] ? colourise_text(level, @prefix_colour[:level]) : level - sep = @prefix_colour[:separator] ? colourise_text(separator, @prefix_colour[:separator]) : separator - bracket_content = [s, d, t, c, p].compact.join(' ') - result = "[#{bracket_content}] #{l} #{sep} " - @prefix_colour[:overall] ? colourise_text(result, @prefix_colour[:overall]) : result - else - # No colourisation - bracket_content = [system, date, time, correlation, pid].compact.join(' ') - "[#{bracket_content}] #{level} #{separator} " - end - + components = { + system: @system_name, + date: now.strftime('%Y-%m-%d'), + time: now.strftime('%H:%M:%S'), + correlation: @correlation_id, + pid: @display_pid ? Process.pid.to_s : nil, + level: severity, + separator: '-- :', + } + + prefix = apply_prefix_colors(components) "#{prefix}#{msg}\n" end end private - def colourise_text(text, colour_spec) - return text unless colour_spec + VALID_METHODS = %w[ + white black red green brown yellow blue magenta cyan gray grey + bright_red bright_green bright_blue bright_magenta bright_cyan + bg_black bg_red bg_green bg_brown bg_yellow bg_blue bg_magenta bg_cyan bg_gray bg_grey bg_white + bg_bright_red bg_bright_green bg_bright_blue bg_bright_magenta bg_bright_cyan + bold dim italic underline reverse_colour reverse_color + ].freeze + + def build_colour_methods(colour_spec) + case colour_spec + when Array, String + methods = colour_spec.is_a?(Array) ? colour_spec : colour_spec.split('.') + return {} unless methods.all? { |m| VALID_METHODS.include?(m) } + + { prefix: methods.map(&:to_sym) } + when Hash + colour_spec.transform_values do |spec| + next unless spec + + methods = spec.is_a?(Array) ? spec : spec.split('.') + methods.map(&:to_sym) if methods.all? { |m| VALID_METHODS.include?(m) } + end.compact + else + {} + end + end + + def apply_colors(text, color_methods) + return text unless color_methods - methods = colour_spec.is_a?(Array) ? colour_spec : colour_spec.to_s.split('.') - methods.reduce(text) { |str, method| str.public_send(method) } + color_methods.reduce(text) { |str, method| str.public_send(method) } + end + + def apply_prefix_colors(components) + if @colour_methods[:prefix] + bracket_content = [components[:system], components[:date], components[:time], + components[:correlation], components[:pid]].compact.join(' ') + text = "[#{bracket_content}] #{components[:level]} #{components[:separator]} " + apply_colors(text, @colour_methods[:prefix]) + elsif @colour_methods.is_a?(Hash) && @colour_methods.any? + system = apply_colors(components[:system], @colour_methods[:system]) + date = apply_colors(components[:date], @colour_methods[:date]) + time = apply_colors(components[:time], @colour_methods[:time]) + correlation = apply_colors(components[:correlation], @colour_methods[:correlation]) + pid = components[:pid] && apply_colors(components[:pid], @colour_methods[:pid]) + level = apply_colors(components[:level], @colour_methods[:level]) + separator = apply_colors(components[:separator], @colour_methods[:separator]) + + bracket_content = [system, date, time, correlation, pid].compact.join(' ') + result = "[#{bracket_content}] #{level} #{separator} " + apply_colors(result, @colour_methods[:overall]) || result + else + bracket_content = [components[:system], components[:date], components[:time], + components[:correlation], components[:pid]].compact.join(' ') + "[#{bracket_content}] #{components[:level]} #{components[:separator]} " + end end def set_proc_writer_scenario From f32968e36e38f96f35c12480e1682339afbe1857 Mon Sep 17 00:00:00 2001 From: nialljames Date: Mon, 6 Oct 2025 09:10:55 +0100 Subject: [PATCH 5/8] simplify prefix config --- README.md | 67 ++++++----------- lib/dvla/herodotus/herodotus_logger.rb | 77 ++++++++------------ spec/dvla/herodotus/herodotus_logger_spec.rb | 76 ++++++++++++++----- 3 files changed, 109 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index b7fb836..6673766 100644 --- a/README.md +++ b/README.md @@ -63,50 +63,24 @@ This would result in logs in the following format: `[SystemName CurrentDate CurrentTime CorrelationId PID] Level : -- Message` #### Prefix Colourisation -You can colourise the log prefix in several ways: +You can colourise different parts of the log prefix by providing a hash with an array of strings to style each component: -**Apply colours to the entire prefix:** -```ruby -config = DVLA::Herodotus.config do |config| - config.prefix_colour = 'blue.bold' -end -logger = DVLA::Herodotus.logger('', config: config) -``` - -**Use an array of colour methods:** - -```ruby -config = DVLA::Herodotus.config do |config| - config.prefix_colour = %w[blue bold underline] -end -logger = DVLA::Herodotus.logger('', config: config) -``` - -**Apply different colours to individual components:** ```ruby config = DVLA::Herodotus.config do |config| config.prefix_colour = { - system: 'blue.bold', - date: 'green', - time: 'yellow', - correlation: 'magenta', - pid: 'cyan', - level: 'red.bold', - separator: 'white' + system: %w[blue bold], + date: %w[green], + time: %w[yellow], + correlation: %w[magenta], + pid: %w[cyan], + level: %w[red bold], + separator: %w[white], + overall: %w[underline] } end -logger = DVLA::Herodotus.logger('', config: config) ``` - -The hash keys correspond to different parts of the log prefix: -- `system`: The system name -- `date`: The date portion (YYYY-MM-DD) -- `time`: The time portion (HH:MM:SS) -- `correlation`: The correlation ID -- `pid`: The process ID (when display_pid is enabled) -- `level`: The log level (INFO, WARN, etc.) -- `separator`: The "-- :" separator -- `overall`: Applied to the entire prefix after individual components are coloured +Each key is optional, and you can simply use the `overall` key to style the whole prefix. +--- ### Syncing logs @@ -128,6 +102,7 @@ You can call `new_scenario` with the identifier just before each scenario to cre logger.new_scenario('Scenario Id') ``` +--- ### Strings Also included is a series of additional methods on `String` that allow you to modify the colour and style of logs. @@ -140,14 +115,14 @@ You can stack multiple method calls to add additional styling and use string int #### Available String Methods -| Type | Examples | -|------|----------| -| Text Styles | **bold** dim *italic* underline | -| Colors | black red green brown yellow blue magenta cyan gray white | -| Bright Colors | bright_red bright_green bright_blue bright_magenta bright_cyan | -| Background Colors | bg_black bg_red bg_green bg_brown bg_yellow bg_blue bg_magenta bg_cyan bg_gray bg_white | -| Bright Background Colors | bg_bright_red bg_bright_green bg_bright_blue bg_bright_magenta bg_bright_cyan | -| Utility | strip_colour reverse_colour | +| Type | Examples | +|---------------------------|----------| +| Text Styles | **bold** dim *italic* underline | +| Colours | black red green brown yellow blue magenta cyan gray white | +| Bright Colours | bright_red bright_green bright_blue bright_magenta bright_cyan | +| Background Colours | bg_black bg_red bg_green bg_brown bg_yellow bg_blue bg_magenta bg_cyan bg_gray bg_white | +| Bright Background Colours | bg_bright_red bg_bright_green bg_bright_blue bg_bright_magenta bg_bright_cyan | +| Utility | strip_colour reverse_colour | #### To handle differences in spelling the following methods have been given aliases: | Alias | Original | @@ -158,6 +133,8 @@ You can stack multiple method calls to add additional styling and use string int | reverse_color | reverse_colour | | strip_color | strip_colour | +--- + ## Development Herodotus is very lightweight. Currently, all code to generate a new logger can be found in `herodotus.rb` and the code for the logger is in `herodotus_logger.rb` so that is the best place to start with any modifications diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb index 7262829..6d0d3e6 100644 --- a/lib/dvla/herodotus/herodotus_logger.rb +++ b/lib/dvla/herodotus/herodotus_logger.rb @@ -15,9 +15,8 @@ def initialize(system_name, *args, config: DVLA::Herodotus.config, **kwargs) @system_name = system_name @main = config[:main] @display_pid = config[:display_pid] - @prefix_colour = config[:prefix_colour] - @colour_methods = build_colour_methods(@prefix_colour) - + @prefix_colour = config[:prefix_colour] || {} + validate_colour_config if @prefix_colour.any? @correlation_id = SecureRandom.uuid[0, 8] set_formatter @@ -81,14 +80,14 @@ def set_formatter separator: '-- :', } - prefix = apply_prefix_colors(components) + prefix = @prefix_colour.any? ? build_prefix_with_colour(components) : build_prefix(components) "#{prefix}#{msg}\n" end end private - VALID_METHODS = %w[ + VALID_COLOUR_METHODS = %w[ white black red green brown yellow blue magenta cyan gray grey bright_red bright_green bright_blue bright_magenta bright_cyan bg_black bg_red bg_green bg_brown bg_yellow bg_blue bg_magenta bg_cyan bg_gray bg_grey bg_white @@ -96,54 +95,36 @@ def set_formatter bold dim italic underline reverse_colour reverse_color ].freeze - def build_colour_methods(colour_spec) - case colour_spec - when Array, String - methods = colour_spec.is_a?(Array) ? colour_spec : colour_spec.split('.') - return {} unless methods.all? { |m| VALID_METHODS.include?(m) } - - { prefix: methods.map(&:to_sym) } - when Hash - colour_spec.transform_values do |spec| - next unless spec - - methods = spec.is_a?(Array) ? spec : spec.split('.') - methods.map(&:to_sym) if methods.all? { |m| VALID_METHODS.include?(m) } - end.compact - else - {} - end + VALID_PREFIX_KEYS = %i[system date time correlation pid level separator overall].freeze + + def validate_colour_config + raise ArgumentError, 'Invalid prefix colour config' unless @prefix_colour.is_a?(Hash) && @prefix_colour.keys.all? { |key| VALID_PREFIX_KEYS.include?(key) } + raise ArgumentError, 'Invalid colours in prefix colour config' unless @prefix_colour.values.flatten.all? { |key| VALID_COLOUR_METHODS.include?(key) } end - def apply_colors(text, color_methods) - return text unless color_methods + def apply_colours(text, colour_spec) + return text unless colour_spec - color_methods.reduce(text) { |str, method| str.public_send(method) } + colour_spec.reduce(text) { |str, method| str.public_send(method) } end - def apply_prefix_colors(components) - if @colour_methods[:prefix] - bracket_content = [components[:system], components[:date], components[:time], - components[:correlation], components[:pid]].compact.join(' ') - text = "[#{bracket_content}] #{components[:level]} #{components[:separator]} " - apply_colors(text, @colour_methods[:prefix]) - elsif @colour_methods.is_a?(Hash) && @colour_methods.any? - system = apply_colors(components[:system], @colour_methods[:system]) - date = apply_colors(components[:date], @colour_methods[:date]) - time = apply_colors(components[:time], @colour_methods[:time]) - correlation = apply_colors(components[:correlation], @colour_methods[:correlation]) - pid = components[:pid] && apply_colors(components[:pid], @colour_methods[:pid]) - level = apply_colors(components[:level], @colour_methods[:level]) - separator = apply_colors(components[:separator], @colour_methods[:separator]) - - bracket_content = [system, date, time, correlation, pid].compact.join(' ') - result = "[#{bracket_content}] #{level} #{separator} " - apply_colors(result, @colour_methods[:overall]) || result - else - bracket_content = [components[:system], components[:date], components[:time], - components[:correlation], components[:pid]].compact.join(' ') - "[#{bracket_content}] #{components[:level]} #{components[:separator]} " - end + def build_prefix_with_colour(components) + system = apply_colours(components[:system], @prefix_colour[:system]) + date = apply_colours(components[:date], @prefix_colour[:date]) + time = apply_colours(components[:time], @prefix_colour[:time]) + correlation = apply_colours(components[:correlation], @prefix_colour[:correlation]) + pid = components[:pid] && apply_colours(components[:pid], @prefix_colour[:pid]) + level = apply_colours(components[:level], @prefix_colour[:level]) + separator = apply_colours(components[:separator], @prefix_colour[:separator]) + + bracket_content = [system, date, time, correlation, pid].compact.join(' ') + result = "[#{bracket_content}] #{level} #{separator} " + apply_colours(result, @prefix_colour[:overall]) || result + end + + def build_prefix(components) + bracket_content = [components[:system], components[:date], components[:time], components[:correlation], components[:pid]].compact.join(' ') + "[#{bracket_content}] #{components[:level]} #{components[:separator]} " end def set_proc_writer_scenario diff --git a/spec/dvla/herodotus/herodotus_logger_spec.rb b/spec/dvla/herodotus/herodotus_logger_spec.rb index 19bf888..7b2b8a3 100644 --- a/spec/dvla/herodotus/herodotus_logger_spec.rb +++ b/spec/dvla/herodotus/herodotus_logger_spec.rb @@ -161,16 +161,8 @@ allow(SecureRandom).to receive(:uuid).and_return('123e4567-e89b-12d3-a456-426614174000') end - it 'colours prefix via string' do - config = DVLA::Herodotus.config { |c| c.prefix_colour = 'blue.bold' } - logger = DVLA::Herodotus.logger('rspec', config: config) - - expect { logger.info('test') }.to output("\e[1m\e[34m[rspec 2022-01-01 00:00:00 123e4567] INFO -- : \e[39m\e[22mtest\n") - .to_stdout_from_any_process - end - - it 'colours prefix with array of strings' do - config = DVLA::Herodotus.config { |c| c.prefix_colour = %w[blue bold] } + it 'colours entire prefix with overall key' do + config = DVLA::Herodotus.config { |c| c.prefix_colour = { overall: %w[blue bold] } } logger = DVLA::Herodotus.logger('rspec', config: config) expect { logger.info('test') }.to output("\e[1m\e[34m[rspec 2022-01-01 00:00:00 123e4567] INFO -- : \e[39m\e[22mtest\n") @@ -180,13 +172,13 @@ it 'colours prefix individual components' do config = DVLA::Herodotus.config do |c| c.prefix_colour = { - system: 'blue.bold', - date: 'green', - time: 'yellow', - correlation: 'magenta', - pid: 'cyan', - level: 'red.bold', - separator: 'white', + system: %w[blue bold], + date: %w[green], + time: %w[yellow], + correlation: %w[magenta], + pid: %w[cyan], + level: %w[red bold], + separator: %w[white], } end logger = DVLA::Herodotus.logger('rspec', config: config) @@ -199,7 +191,7 @@ main_config = DVLA::Herodotus.config { |c| c.main = true } main_logger = DVLA::Herodotus.logger('main', config: main_config) - secondary_config = DVLA::Herodotus.config { |c| c.prefix_colour = 'red' } + secondary_config = DVLA::Herodotus.config { |c| c.prefix_colour = { overall: %w[red] } } secondary_logger = DVLA::Herodotus.logger('secondary', config: secondary_config) expect { main_logger.info('main test') }.to output("[main 2022-01-01 00:00:00 123e4567] INFO -- : main test\n") @@ -208,5 +200,53 @@ expect { secondary_logger.info('secondary test') }.to output("\e[31m[secondary 2022-01-01 00:00:00 123e4567] INFO -- : \e[39msecondary test\n") .to_stdout_from_any_process end + + it 'ignores pid colour when display_pid is false' do + config = DVLA::Herodotus.config do |c| + c.display_pid = false + c.prefix_colour = { pid: %w[cyan] } + end + logger = DVLA::Herodotus.logger('rspec', config: config) + + expect { logger.info('test') }.to output("[rspec 2022-01-01 00:00:00 123e4567] INFO -- : test\n") + .to_stdout_from_any_process + end + + it 'allows partial colouring with optional keys' do + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + system: %w[blue bold], + level: %w[red], + # date, time, correlation, separator not specified + } + end + logger = DVLA::Herodotus.logger('rspec', config: config) + + expected_output = "[\e[1m\e[34mrspec\e[39m\e[22m 2022-01-01 00:00:00 123e4567] \e[31mINFO\e[39m -- : test\n" + expect { logger.info('test') }.to output(expected_output).to_stdout_from_any_process + end + + it 'raises ArgumentError for invalid colour methods' do + expect { + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + system: %w[blue invalid_method], + level: %w[module_eval], + } + end + DVLA::Herodotus.logger('rspec', config: config) + }.to raise_error(ArgumentError, /Invalid colours in prefix colour config/) + end + + it 'raises ArgumentError for invalid prefix option' do + expect { + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + unknown: %w[blue], + } + end + DVLA::Herodotus.logger('rspec', config: config) + }.to raise_error(ArgumentError, /Invalid prefix colour config/) + end end end From 751393e471db825e19c4f84996e7a6aa8d2c9f95 Mon Sep 17 00:00:00 2001 From: nialljames Date: Mon, 6 Oct 2025 09:15:48 +0100 Subject: [PATCH 6/8] remove unnecessary changes --- lib/dvla/herodotus.rb | 4 ++-- lib/dvla/herodotus/herodotus_logger.rb | 2 +- lib/dvla/herodotus/multi_writer.rb | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/dvla/herodotus.rb b/lib/dvla/herodotus.rb index dee5f43..33c9d28 100644 --- a/lib/dvla/herodotus.rb +++ b/lib/dvla/herodotus.rb @@ -26,10 +26,10 @@ def self.logger(system_name, config: self.config, output_path: nil) if output_path if output_path.is_a? String output_file = File.open(output_path, 'a') - return HerodotusLogger.new(system_name, MultiWriter.new(output_file, $stdout, config: config), config: config) + return HerodotusLogger.new(system_name, MultiWriter.new(output_file, $stdout), config: config) elsif output_path.is_a? Proc proc_writer = ProcWriter.new(output_path) - return HerodotusLogger.new(system_name, MultiWriter.new(proc_writer, $stdout, config: config), config: config) + return HerodotusLogger.new(system_name, MultiWriter.new(proc_writer, $stdout), config: config) else raise ArgumentError.new 'Unexpected output_path provided. Expecting either a string or a proc' end diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb index 6d0d3e6..6dc0369 100644 --- a/lib/dvla/herodotus/herodotus_logger.rb +++ b/lib/dvla/herodotus/herodotus_logger.rb @@ -3,7 +3,7 @@ module DVLA module Herodotus class HerodotusLogger < Logger - attr_accessor :system_name, :correlation_id, :main, :display_pid, :scenario_id, :prefix_colour + attr_accessor :system_name, :correlation_id, :main, :display_pid, :scenario_id # Initializes the logger # Sets a default correlation_id and creates the formatter diff --git a/lib/dvla/herodotus/multi_writer.rb b/lib/dvla/herodotus/multi_writer.rb index 94ea2a2..a8eba5b 100644 --- a/lib/dvla/herodotus/multi_writer.rb +++ b/lib/dvla/herodotus/multi_writer.rb @@ -3,8 +3,7 @@ module Herodotus class MultiWriter attr_reader :targets - def initialize(*targets, config: nil) - @config = config + def initialize(*targets) @targets = *targets end From 0c58a81191f01932e84fbf7320ed464043655712 Mon Sep 17 00:00:00 2001 From: nialljames Date: Mon, 6 Oct 2025 09:46:35 +0100 Subject: [PATCH 7/8] add prefix_colour back to attr_accessor --- lib/dvla/herodotus/herodotus_logger.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb index 6dc0369..6d0d3e6 100644 --- a/lib/dvla/herodotus/herodotus_logger.rb +++ b/lib/dvla/herodotus/herodotus_logger.rb @@ -3,7 +3,7 @@ module DVLA module Herodotus class HerodotusLogger < Logger - attr_accessor :system_name, :correlation_id, :main, :display_pid, :scenario_id + attr_accessor :system_name, :correlation_id, :main, :display_pid, :scenario_id, :prefix_colour # Initializes the logger # Sets a default correlation_id and creates the formatter From 7a4757ab353088da3c080958174513eeab125f81 Mon Sep 17 00:00:00 2001 From: nialljames Date: Mon, 20 Oct 2025 08:32:00 +0100 Subject: [PATCH 8/8] allow strings, symbols or arrays for prefix config --- README.md | 10 +-- lib/dvla/herodotus/herodotus_logger.rb | 12 ++- spec/dvla/herodotus/herodotus_logger_spec.rb | 83 ++++++++++++++------ 3 files changed, 75 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 6673766..7460581 100644 --- a/README.md +++ b/README.md @@ -63,17 +63,17 @@ This would result in logs in the following format: `[SystemName CurrentDate CurrentTime CorrelationId PID] Level : -- Message` #### Prefix Colourisation -You can colourise different parts of the log prefix by providing a hash with an array of strings to style each component: +You can colourise different parts of the log prefix by providing a hash to style each component. It accepts strings, symbols or arrays of either: ```ruby config = DVLA::Herodotus.config do |config| config.prefix_colour = { system: %w[blue bold], - date: %w[green], - time: %w[yellow], - correlation: %w[magenta], + date: 'green', + time: :yellow, + correlation: %w[magenta italic], pid: %w[cyan], - level: %w[red bold], + level: %i[red bold], separator: %w[white], overall: %w[underline] } diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb index 6d0d3e6..76d9634 100644 --- a/lib/dvla/herodotus/herodotus_logger.rb +++ b/lib/dvla/herodotus/herodotus_logger.rb @@ -87,7 +87,7 @@ def set_formatter private - VALID_COLOUR_METHODS = %w[ + VALID_COLOUR_METHODS = %i[ white black red green brown yellow blue magenta cyan gray grey bright_red bright_green bright_blue bright_magenta bright_cyan bg_black bg_red bg_green bg_brown bg_yellow bg_blue bg_magenta bg_cyan bg_gray bg_grey bg_white @@ -99,7 +99,15 @@ def set_formatter def validate_colour_config raise ArgumentError, 'Invalid prefix colour config' unless @prefix_colour.is_a?(Hash) && @prefix_colour.keys.all? { |key| VALID_PREFIX_KEYS.include?(key) } - raise ArgumentError, 'Invalid colours in prefix colour config' unless @prefix_colour.values.flatten.all? { |key| VALID_COLOUR_METHODS.include?(key) } + + @prefix_colour.each_value do |value| + raise ArgumentError, 'Colour values must be strings or symbols' unless valid_colour_type?(value) + raise ArgumentError, 'Invalid colours in prefix colour config' unless Array(value).map(&:to_sym).all? { |c| VALID_COLOUR_METHODS.include?(c) } + end + end + + def valid_colour_type?(value) + value.is_a?(String) || value.is_a?(Symbol) || (value.is_a?(Array) && value.all? { |v| v.is_a?(String) || v.is_a?(Symbol) }) end def apply_colours(text, colour_spec) diff --git a/spec/dvla/herodotus/herodotus_logger_spec.rb b/spec/dvla/herodotus/herodotus_logger_spec.rb index 7b2b8a3..a989279 100644 --- a/spec/dvla/herodotus/herodotus_logger_spec.rb +++ b/spec/dvla/herodotus/herodotus_logger_spec.rb @@ -161,6 +161,66 @@ allow(SecureRandom).to receive(:uuid).and_return('123e4567-e89b-12d3-a456-426614174000') end + + it 'raises ArgumentError for invalid colour methods' do + expect { + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + system: %w[blue invalid_method], + level: %w[module_eval], + } + end + DVLA::Herodotus.logger('rspec', config: config) + }.to raise_error(ArgumentError, /Invalid colours in prefix colour config/) + end + + it 'raises ArgumentError for invalid prefix option' do + expect { + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + unknown: %w[blue], + } + end + DVLA::Herodotus.logger('rspec', config: config) + }.to raise_error(ArgumentError, /Invalid prefix colour config/) + end + + it 'raises ArgumentError for invalid prefix colour type' do + expect { + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + system: %r[blue], + } + end + DVLA::Herodotus.logger('rspec', config: config) + }.to raise_error(ArgumentError, /Colour values must be strings or symbols/) + end + + it 'raises ArgumentError for invalid prefix colour' do + expect { + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + system: 'blellow', + } + end + DVLA::Herodotus.logger('rspec', config: config) + }.to raise_error(ArgumentError, /Invalid colours in prefix colour config/) + end + + it 'accepts mixed format colour configuration' do + config = DVLA::Herodotus.config do |c| + c.prefix_colour = { + system: 'blue', + date: %w[green bold], + time: %i[yellow italic], + correlation: %w[magenta], + level: :red, + separator: %i[white dim], + } + end + expect { DVLA::Herodotus.logger('rspec', config: config) }.to_not raise_error + end + it 'colours entire prefix with overall key' do config = DVLA::Herodotus.config { |c| c.prefix_colour = { overall: %w[blue bold] } } logger = DVLA::Herodotus.logger('rspec', config: config) @@ -225,28 +285,5 @@ expected_output = "[\e[1m\e[34mrspec\e[39m\e[22m 2022-01-01 00:00:00 123e4567] \e[31mINFO\e[39m -- : test\n" expect { logger.info('test') }.to output(expected_output).to_stdout_from_any_process end - - it 'raises ArgumentError for invalid colour methods' do - expect { - config = DVLA::Herodotus.config do |c| - c.prefix_colour = { - system: %w[blue invalid_method], - level: %w[module_eval], - } - end - DVLA::Herodotus.logger('rspec', config: config) - }.to raise_error(ArgumentError, /Invalid colours in prefix colour config/) - end - - it 'raises ArgumentError for invalid prefix option' do - expect { - config = DVLA::Herodotus.config do |c| - c.prefix_colour = { - unknown: %w[blue], - } - end - DVLA::Herodotus.logger('rspec', config: config) - }.to raise_error(ArgumentError, /Invalid prefix colour config/) - end end end