Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
39 changes: 31 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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** <span style="opacity:0.6">dim</span> *italic* <u>underline</u> |
| Colors | <span style="color:black">black</span> <span style="color:red">red</span> <span style="color:green">green</span> <span style="color:#B8860B">brown</span> <span style="color:#ffff00">yellow</span> <span style="color:blue">blue</span> <span style="color:magenta">magenta</span> <span style="color:cyan">cyan</span> <span style="color:grey">gray</span> <span style="color:white">white</span> |
| Bright Colors | <span style="color:#ff5555">bright_red</span> <span style="color:#55ff55">bright_green</span> <span style="color:#5555ff">bright_blue</span> <span style="color:#ff55ff">bright_magenta</span> <span style="color:#55ffff">bright_cyan</span> |
| Background Colors | <span style="background:black;color:white">bg_black</span> <span style="background:red;color:white">bg_red</span> <span style="background:green;color:white">bg_green</span> <span style="background:#B8860B;color:white">bg_brown</span> <span style="background:#ffff00;color:black">bg_yellow</span> <span style="background:blue;color:white">bg_blue</span> <span style="background:magenta;color:white">bg_magenta</span> <span style="background:cyan;color:black">bg_cyan</span> <span style="background:grey;color:white">bg_gray</span> <span style="background:white;color:black">bg_white</span> |
| Bright Background Colors | <span style="background:#ff5555;color:white">bg_bright_red</span> <span style="background:#55ff55;color:black">bg_bright_green</span> <span style="background:#5555ff;color:white">bg_bright_blue</span> <span style="background:#ff55ff;color:white">bg_bright_magenta</span> <span style="background:#55ffff;color:black">bg_bright_cyan</span> |
| Utility | strip_colour reverse_colour |
| Type | Examples |
|---------------------------|----------|
| Text Styles | **bold** <span style="opacity:0.6">dim</span> *italic* <u>underline</u> |
| Colours | <span style="color:black">black</span> <span style="color:red">red</span> <span style="color:green">green</span> <span style="color:#B8860B">brown</span> <span style="color:#ffff00">yellow</span> <span style="color:blue">blue</span> <span style="color:magenta">magenta</span> <span style="color:cyan">cyan</span> <span style="color:grey">gray</span> <span style="color:white">white</span> |
| Bright Colours | <span style="color:#ff5555">bright_red</span> <span style="color:#55ff55">bright_green</span> <span style="color:#5555ff">bright_blue</span> <span style="color:#ff55ff">bright_magenta</span> <span style="color:#55ffff">bright_cyan</span> |
| Background Colours | <span style="background:black;color:white">bg_black</span> <span style="background:red;color:white">bg_red</span> <span style="background:green;color:white">bg_green</span> <span style="background:#B8860B;color:white">bg_brown</span> <span style="background:#ffff00;color:black">bg_yellow</span> <span style="background:blue;color:white">bg_blue</span> <span style="background:magenta;color:white">bg_magenta</span> <span style="background:cyan;color:black">bg_cyan</span> <span style="background:grey;color:white">bg_gray</span> <span style="background:white;color:black">bg_white</span> |
| Bright Background Colours | <span style="background:#ff5555;color:white">bg_bright_red</span> <span style="background:#55ff55;color:black">bg_bright_green</span> <span style="background:#5555ff;color:white">bg_bright_blue</span> <span style="background:#ff55ff;color:white">bg_bright_magenta</span> <span style="background:#55ffff;color:black">bg_bright_cyan</span> |
| Utility | strip_colour reverse_colour |

#### To handle differences in spelling the following methods have been given aliases:
| Alias | Original |
Expand All @@ -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
2 changes: 1 addition & 1 deletion lib/dvla/herodotus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 65 additions & 8 deletions lib/dvla/herodotus/herodotus_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion lib/dvla/herodotus/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module DVLA
module Herodotus
VERSION = '2.2.1'.freeze
VERSION = '2.3.0'.freeze
end
end
132 changes: 132 additions & 0 deletions spec/dvla/herodotus/herodotus_logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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