From 5b7396ae196e48aaf23863be07ad5a08837b0821 Mon Sep 17 00:00:00 2001 From: Lee Patterson Date: Wed, 29 Jul 2026 16:51:26 +0100 Subject: [PATCH 1/2] QEB-1447 Add semantic logging methods with configurable colours --- CHANGELOG.md | 6 ++ README.md | 51 +++++++++++++ lib/dvla/herodotus.rb | 1 + lib/dvla/herodotus/semantic_colours.rb | 37 +++++++++ lib/dvla/herodotus/string.rb | 23 ++++++ lib/dvla/herodotus/version.rb | 2 +- spec/dvla/herodotus/semantic_colours_spec.rb | 79 ++++++++++++++++++++ spec/dvla/herodotus/string_spec.rb | 55 ++++++++++++++ 8 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 lib/dvla/herodotus/semantic_colours.rb create mode 100644 spec/dvla/herodotus/semantic_colours_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 63e58ab..ce274e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog All notable changes to this project will be documented in this file. +## [2.4.0] - 2026-07-29 +- Added semantic logging methods: `.config`, `.step`, `.success`, `.warning`, `.data` +- Semantic methods apply standard colours based on message type for consistent logging +- Added `DVLA::Herodotus::SemanticColours.configure` to allow teams to customise semantic colours +- Added `DVLA::Herodotus::SemanticColours.reset!` to restore default colours + ## [2.3.2] - 2025-12-23 - Add 'spawn_child_logger' method to allow other processes to inherit the current logger's configuration diff --git a/README.md b/README.md index 23c3c4e..6277a6b 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,57 @@ You can stack multiple method calls to add additional styling and use string int --- +### Semantic Logging Methods + +Herodotus provides semantic methods that associate message types with standard colours. This promotes consistent colour usage across teams and makes logs easier to read. + +```ruby +LOG.info { "Environment: #{env}".config } # brown - configuration/environment +LOG.info { "Checking image status".step } # magenta - step execution/progress +LOG.info { "Application completed".success } # green - successful operations +LOG.info { "Retrying request".warning } # yellow - warnings/non-fatal issues +LOG.info { "Response: #{payload}".data } # blue - data values/payloads +``` + +#### Default Colours + +| Method | Default Colour | Usage | +|-----------|----------------|-------------------------------------| +| `.config` | brown | Configuration, environment settings | +| `.step` | magenta | Method calls, checking, polling | +| `.success`| green | Successful operations, completions | +| `.warning`| yellow | Warnings, non-fatal issues | +| `.data` | blue | Data values, responses, payloads | + +#### Custom Colour Configuration + +Teams can override the default colours to match their preferences: + +```ruby +DVLA::Herodotus::SemanticColours.configure do |c| + c.config = :cyan + c.step = :blue + c.success = :bright_green + c.warning = :red + c.data = :magenta +end +``` + +To reset to defaults: + +```ruby +DVLA::Herodotus::SemanticColours.reset! +``` + +Semantic methods can be chained with other styles: + +```ruby +LOG.info { "COMPLETED".success.bold } +LOG.info { "ERROR: #{msg}".warning.bg_red } +``` + +--- + ## 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 95a91a8..a1e0395 100644 --- a/lib/dvla/herodotus.rb +++ b/lib/dvla/herodotus.rb @@ -3,6 +3,7 @@ require_relative 'herodotus/herodotus_logger' require_relative 'herodotus/multi_writer' require_relative 'herodotus/proc_writer' +require_relative 'herodotus/semantic_colours' require_relative 'herodotus/string' module DVLA diff --git a/lib/dvla/herodotus/semantic_colours.rb b/lib/dvla/herodotus/semantic_colours.rb new file mode 100644 index 0000000..e15e5c8 --- /dev/null +++ b/lib/dvla/herodotus/semantic_colours.rb @@ -0,0 +1,37 @@ +module DVLA + module Herodotus + module SemanticColours + DEFAULT_COLOURS = { + config: :brown, + step: :magenta, + success: :green, + warning: :yellow, + data: :blue + }.freeze + + class << self + def colours + @colours ||= DEFAULT_COLOURS.dup + end + + def configure + yield(self) if block_given? + end + + def reset! + @colours = DEFAULT_COLOURS.dup + end + + DEFAULT_COLOURS.keys.each do |semantic_type| + define_method(semantic_type) do + colours[semantic_type] + end + + define_method("#{semantic_type}=") do |colour| + colours[semantic_type] = colour + end + end + end + end + end +end diff --git a/lib/dvla/herodotus/string.rb b/lib/dvla/herodotus/string.rb index 49ca61e..c8a1e70 100644 --- a/lib/dvla/herodotus/string.rb +++ b/lib/dvla/herodotus/string.rb @@ -1,3 +1,5 @@ +require_relative 'semantic_colours' + class String def colourise(code, reset_code = 39) # Making sure we align the correct reset codes @@ -88,4 +90,25 @@ def strip_colour alias grey gray alias reverse_color reverse_colour alias strip_color strip_colour + + # Semantic logging methods - apply colours based on message type + def config + send(DVLA::Herodotus::SemanticColours.config) + end + + def step + send(DVLA::Herodotus::SemanticColours.step) + end + + def success + send(DVLA::Herodotus::SemanticColours.success) + end + + def warning + send(DVLA::Herodotus::SemanticColours.warning) + end + + def data + send(DVLA::Herodotus::SemanticColours.data) + end end diff --git a/lib/dvla/herodotus/version.rb b/lib/dvla/herodotus/version.rb index 300c6ec..91c29a4 100644 --- a/lib/dvla/herodotus/version.rb +++ b/lib/dvla/herodotus/version.rb @@ -1,5 +1,5 @@ module DVLA module Herodotus - VERSION = '2.3.2'.freeze + VERSION = '2.4.0'.freeze end end diff --git a/spec/dvla/herodotus/semantic_colours_spec.rb b/spec/dvla/herodotus/semantic_colours_spec.rb new file mode 100644 index 0000000..93f3b01 --- /dev/null +++ b/spec/dvla/herodotus/semantic_colours_spec.rb @@ -0,0 +1,79 @@ +require 'dvla/herodotus/semantic_colours' + +RSpec.describe DVLA::Herodotus::SemanticColours do + after(:each) do + described_class.reset! + end + + describe 'default colours' do + it 'has brown as the default config colour' do + expect(described_class.config).to eq(:brown) + end + + it 'has magenta as the default step colour' do + expect(described_class.step).to eq(:magenta) + end + + it 'has green as the default success colour' do + expect(described_class.success).to eq(:green) + end + + it 'has yellow as the default warning colour' do + expect(described_class.warning).to eq(:yellow) + end + + it 'has blue as the default data colour' do + expect(described_class.data).to eq(:blue) + end + end + + describe '.configure' do + it 'allows overriding the config colour' do + described_class.configure do |c| + c.config = :cyan + end + + expect(described_class.config).to eq(:cyan) + end + + it 'allows overriding multiple colours' do + described_class.configure do |c| + c.step = :blue + c.warning = :red + end + + expect(described_class.step).to eq(:blue) + expect(described_class.warning).to eq(:red) + end + + it 'preserves unmodified colours' do + described_class.configure do |c| + c.success = :bright_green + end + + expect(described_class.config).to eq(:brown) + expect(described_class.step).to eq(:magenta) + expect(described_class.success).to eq(:bright_green) + end + end + + describe '.reset!' do + it 'restores all colours to defaults' do + described_class.configure do |c| + c.config = :red + c.step = :blue + c.success = :cyan + c.warning = :magenta + c.data = :green + end + + described_class.reset! + + expect(described_class.config).to eq(:brown) + expect(described_class.step).to eq(:magenta) + expect(described_class.success).to eq(:green) + expect(described_class.warning).to eq(:yellow) + expect(described_class.data).to eq(:blue) + end + end +end diff --git a/spec/dvla/herodotus/string_spec.rb b/spec/dvla/herodotus/string_spec.rb index b1cceb7..ae7009a 100644 --- a/spec/dvla/herodotus/string_spec.rb +++ b/spec/dvla/herodotus/string_spec.rb @@ -74,4 +74,59 @@ expect('Test'.red.bg_yellow.reverse_colour.bold).to eq "\e[1m\e[7m\e[103m\e[31mTest\e[39m\e[49m\e[27m\e[22m" end end + + describe 'semantic logging methods' do + after(:each) do + DVLA::Herodotus::SemanticColours.reset! + end + + describe 'with default colours' do + it 'applies brown for config' do + expect('Test'.config).to eq("\e[33mTest\e[39m") + end + + it 'applies magenta for step' do + expect('Test'.step).to eq("\e[35mTest\e[39m") + end + + it 'applies green for success' do + expect('Test'.success).to eq("\e[32mTest\e[39m") + end + + it 'applies yellow for warning' do + expect('Test'.warning).to eq("\e[93mTest\e[39m") + end + + it 'applies blue for data' do + expect('Test'.data).to eq("\e[34mTest\e[39m") + end + end + + describe 'with custom colours' do + it 'uses configured colour for config' do + DVLA::Herodotus::SemanticColours.configure { |c| c.config = :red } + expect('Test'.config).to eq("\e[31mTest\e[39m") + end + + it 'uses configured colour for step' do + DVLA::Herodotus::SemanticColours.configure { |c| c.step = :cyan } + expect('Test'.step).to eq("\e[36mTest\e[39m") + end + + it 'uses configured colour for success' do + DVLA::Herodotus::SemanticColours.configure { |c| c.success = :bright_green } + expect('Test'.success).to eq("\e[92mTest\e[39m") + end + end + + describe 'chaining with other styles' do + it 'can chain semantic methods with styles' do + expect('Test'.config.bold).to eq("\e[1m\e[33mTest\e[39m\e[22m") + end + + it 'can chain semantic methods with background colours' do + expect('Test'.success.bg_black).to eq("\e[40m\e[32mTest\e[39m\e[49m") + end + end + end end From 68bc4b2f3114b521b7a06fb8f21874eac1500a6f Mon Sep 17 00:00:00 2001 From: Lee Patterson Date: Mon, 10 Aug 2026 08:50:22 +0100 Subject: [PATCH 2/2] Add semantic colours to main configure block for simpler consumer setup --- CHANGELOG.md | 5 +- README.md | 10 ++-- lib/dvla/herodotus.rb | 27 ++++++--- lib/dvla/herodotus/config.rb | 23 ++++++++ lib/dvla/herodotus/herodotus_logger.rb | 6 +- spec/dvla/herodotus_spec.rb | 81 ++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 lib/dvla/herodotus/config.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index ce274e1..ac9a58d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ All notable changes to this project will be documented in this file. ## [2.4.0] - 2026-07-29 - Added semantic logging methods: `.config`, `.step`, `.success`, `.warning`, `.data` - Semantic methods apply standard colours based on message type for consistent logging -- Added `DVLA::Herodotus::SemanticColours.configure` to allow teams to customise semantic colours -- Added `DVLA::Herodotus::SemanticColours.reset!` to restore default colours +- Semantic colours can be configured in the main `DVLA::Herodotus.configure` block alongside logger options +- Added `DVLA::Herodotus.reset_configuration!` to reset both logger options and semantic colours +- `DVLA::Herodotus::SemanticColours.configure` and `.reset!` still available for standalone colour configuration ## [2.3.2] - 2025-12-23 - Add 'spawn_child_logger' method to allow other processes to inherit the current logger's configuration diff --git a/README.md b/README.md index 6277a6b..d4e2ed1 100644 --- a/README.md +++ b/README.md @@ -175,10 +175,12 @@ LOG.info { "Response: #{payload}".data } # blue - data values/payloads #### Custom Colour Configuration -Teams can override the default colours to match their preferences: +You can configure semantic colours alongside other logger options in a single configure block: ```ruby -DVLA::Herodotus::SemanticColours.configure do |c| +DVLA::Herodotus.configure do |c| + c.display_pid = true + c.prefix_colour = { overall: :blue } c.config = :cyan c.step = :blue c.success = :bright_green @@ -187,10 +189,10 @@ DVLA::Herodotus::SemanticColours.configure do |c| end ``` -To reset to defaults: +To reset all configuration to defaults: ```ruby -DVLA::Herodotus::SemanticColours.reset! +DVLA::Herodotus.reset_configuration! ``` Semantic methods can be chained with other styles: diff --git a/lib/dvla/herodotus.rb b/lib/dvla/herodotus.rb index a1e0395..f64cb2d 100644 --- a/lib/dvla/herodotus.rb +++ b/lib/dvla/herodotus.rb @@ -4,23 +4,36 @@ require_relative 'herodotus/multi_writer' require_relative 'herodotus/proc_writer' require_relative 'herodotus/semantic_colours' +require_relative 'herodotus/config' require_relative 'herodotus/string' module DVLA module Herodotus class << self attr_accessor :main_logger - end - CONFIG_ATTRIBUTES = %i[display_pid main prefix_colour].freeze + def configuration + @configuration ||= Config.new + end + + def configure + yield(configuration) if block_given? + configuration + end - def self.config - config ||= Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true).new - yield(config) if block_given? - config + def config + c = Config.new + yield(c) if block_given? + c + end + + def reset_configuration! + @configuration = Config.new + SemanticColours.reset! + end end - def self.logger(system_name, config: self.config, output_path: nil) + def self.logger(system_name, config: self.configuration, output_path: nil) create_logger(system_name, config, output_path) end diff --git a/lib/dvla/herodotus/config.rb b/lib/dvla/herodotus/config.rb new file mode 100644 index 0000000..136c415 --- /dev/null +++ b/lib/dvla/herodotus/config.rb @@ -0,0 +1,23 @@ +module DVLA + module Herodotus + class Config + LOGGER_ATTRIBUTES = %i[display_pid main prefix_colour].freeze + + attr_accessor(*LOGGER_ATTRIBUTES) + + SemanticColours::DEFAULT_COLOURS.keys.each do |semantic_type| + define_method(semantic_type) do + SemanticColours.send(semantic_type) + end + + define_method("#{semantic_type}=") do |colour| + SemanticColours.send("#{semantic_type}=", colour) + end + end + + def reset_colours! + SemanticColours.reset! + end + end + end +end diff --git a/lib/dvla/herodotus/herodotus_logger.rb b/lib/dvla/herodotus/herodotus_logger.rb index 298578b..b566634 100644 --- a/lib/dvla/herodotus/herodotus_logger.rb +++ b/lib/dvla/herodotus/herodotus_logger.rb @@ -13,9 +13,9 @@ def initialize(system_name, *args, config: DVLA::Herodotus.config, **kwargs) super(*args, **kwargs) @system_name = system_name - @main = config[:main] - @display_pid = config[:display_pid] - @prefix_colour = config[:prefix_colour] || {} + @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 diff --git a/spec/dvla/herodotus_spec.rb b/spec/dvla/herodotus_spec.rb index a83cbe6..d1734f6 100644 --- a/spec/dvla/herodotus_spec.rb +++ b/spec/dvla/herodotus_spec.rb @@ -5,6 +5,87 @@ DVLA::Herodotus.main_logger = nil end + describe '.configure' do + after(:each) do + DVLA::Herodotus.reset_configuration! + end + + it 'allows setting logger options' do + DVLA::Herodotus.configure do |c| + c.display_pid = true + c.main = true + end + + expect(DVLA::Herodotus.configuration.display_pid).to eq(true) + expect(DVLA::Herodotus.configuration.main).to eq(true) + end + + it 'allows setting semantic colours' do + DVLA::Herodotus.configure do |c| + c.success = :cyan + c.warning = :red + end + + expect(DVLA::Herodotus.configuration.success).to eq(:cyan) + expect(DVLA::Herodotus.configuration.warning).to eq(:red) + end + + it 'allows setting both logger options and semantic colours in one block' do + DVLA::Herodotus.configure do |c| + c.display_pid = true + c.prefix_colour = { overall: :blue } + c.success = :bright_green + c.data = :cyan + end + + expect(DVLA::Herodotus.configuration.display_pid).to eq(true) + expect(DVLA::Herodotus.configuration.prefix_colour).to eq({ overall: :blue }) + expect(DVLA::Herodotus.configuration.success).to eq(:bright_green) + expect(DVLA::Herodotus.configuration.data).to eq(:cyan) + end + + it 'persists configuration across calls' do + DVLA::Herodotus.configure { |c| c.display_pid = true } + DVLA::Herodotus.configure { |c| c.success = :cyan } + + expect(DVLA::Herodotus.configuration.display_pid).to eq(true) + expect(DVLA::Herodotus.configuration.success).to eq(:cyan) + end + + it 'returns the configuration object' do + result = DVLA::Herodotus.configure { |c| c.main = true } + + expect(result).to be_a(DVLA::Herodotus::Config) + expect(result.main).to eq(true) + end + end + + describe '.reset_configuration!' do + it 'resets logger options to nil' do + DVLA::Herodotus.configure do |c| + c.display_pid = true + c.main = true + end + + DVLA::Herodotus.reset_configuration! + + expect(DVLA::Herodotus.configuration.display_pid).to be_nil + expect(DVLA::Herodotus.configuration.main).to be_nil + end + + it 'resets semantic colours to defaults' do + DVLA::Herodotus.configure do |c| + c.success = :cyan + c.warning = :red + end + + DVLA::Herodotus.reset_configuration! + + expect(DVLA::Herodotus.configuration.success).to eq(:green) + expect(DVLA::Herodotus.configuration.warning).to eq(:yellow) + end + end + it 'returns a logger' do expect(DVLA::Herodotus.logger('rspec')).to be_a(Logger) end