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