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
diff --git a/README.md b/README.md
index 2fb7ae6..7460581 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,26 @@ 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 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: 'green',
+ time: :yellow,
+ correlation: %w[magenta italic],
+ pid: %w[cyan],
+ level: %i[red bold],
+ separator: %w[white],
+ overall: %w[underline]
+ }
+end
+```
+Each key is optional, and you can simply use the `overall` key to style the whole prefix.
+---
+
### Syncing logs
Herodotus allows you to Sync correlation_ids between instantiated HerodotusLogger objects.
@@ -82,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.
@@ -94,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 |
@@ -112,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.rb b/lib/dvla/herodotus.rb
index 18ccb59..33c9d28 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
diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb
index 32b76f7..76d9634 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,7 +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] || {}
+ validate_colour_config if @prefix_colour.any?
@correlation_id = SecureRandom.uuid[0, 8]
set_formatter
@@ -65,19 +66,75 @@ 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
+ 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 = @prefix_colour.any? ? build_prefix_with_colour(components) : build_prefix(components)
+ "#{prefix}#{msg}\n"
end
end
private
+ 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
+ bg_bright_red bg_bright_green bg_bright_blue bg_bright_magenta bg_bright_cyan
+ bold dim italic underline reverse_colour reverse_color
+ ].freeze
+
+ 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) }
+
+ @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)
+ return text unless colour_spec
+
+ colour_spec.reduce(text) { |str, method| str.public_send(method) }
+ 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
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/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
diff --git a/spec/dvla/herodotus/herodotus_logger_spec.rb b/spec/dvla/herodotus/herodotus_logger_spec.rb
index d214464..a989279 100644
--- a/spec/dvla/herodotus/herodotus_logger_spec.rb
+++ b/spec/dvla/herodotus/herodotus_logger_spec.rb
@@ -154,4 +154,136 @@
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 '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)
+
+ 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: %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)
+
+ 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 = { 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")
+ .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
+
+ 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
+ end
end