unit module LogP6::WriterConf::Pattern; use LogP6::Level; role PatternPart { method show($) { ... } } class Trait does PatternPart { method show($context) { $context.trait } } role TraitParam does PatternPart { # not need eviction. if we use trait ones then we use it all program lifetime has %!cache = %(); method calculate($trait) { ... } method show($context) { my $trait = $context.trait; %!cache{$trait} //= self.calculate($trait); } } class TraitShort does TraitParam { has $.separator; has $.minus; has $.length; has $.abreviature; method calculate($trait) { my $parts = $trait.split('::').List; my $elems = $parts.elems; if $!length >= $elems || $!length == 0 { return $parts.join($!separator); } my $middle = $!minus ?? $!length !! $elems - $!length; return $parts.kv.map(-> $i, $p { if $i < $middle { $!abreviature ?? substr($p, 0, $!abreviature) !! Any; } else { $p; } }).grep(*.defined).join($!separator); } } class TraitSprintf does TraitParam { has $.placeholder; method calculate($trait) { sprintf($!placeholder, $trait); } } class Tid does PatternPart { method show($context) { $context.tid } } class Tname does PatternPart { method show($context) { $context.tname // '' } } class Ndc does PatternPart { method show($context) { $context.ndc.join: ' ' } } class Msg does PatternPart { method show($context) { $context.msg } } class Mdc does PatternPart { has $.key is required; method show($context) { $context.mdc{$!key} // '' } } class Glue does PatternPart { has $.glue is required; method new($str) { self.bless(glue => $str) } method show($context) { $.glue } } class X does PatternPart { has $.pieces; method show($context) { with $context.x() { return ($!pieces>>.show($_)).join; } return ''; } } class XMsg does PatternPart { method show($x) { $x.message } } class XName does PatternPart { method show($x) { $x.^name } } class XTrace does PatternPart { method show($x) { $x.backtrace } } # use eager to avoid laziness which can lead to concurrency issues my $digits = ('00'..'99').eager; my $months = <0 Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>.List; class Date does PatternPart { has $.pieces; method show($context) { with $context.date() { return ($!pieces>>.show($_)).join; } return ''; } } class DateYearFour does PatternPart { method show($d) { $d.year } } class DateYearTwo does PatternPart { method show($d) { $digits[$d.year % 100] } } class DateMonthWord does PatternPart { method show($d) { $months[$d.month] } } class DateMonthNum does PatternPart { method show($d) { $digits[$d.month] } } class DateDay does PatternPart { method show($d) { $digits[$d.day] } } class DateHour does PatternPart { method show($d) { $digits[$d.hour] } } class DateMinute does PatternPart { method show($d) { $digits[$d.minute] } } class DateSecond does PatternPart { method show($d) { $digits[$d.whole-second] } } class DateMSecond does PatternPart { method show($d) { round(($d.second - $d.whole-second) * 1000) } } class DateZone does PatternPart { method show($d) { $d.timezone } } class FrameFile does PatternPart { method show($context) { $context.callframe.file } } class FrameLine does PatternPart { method show($context) { $context.callframe.line } } class FrameName does PatternPart { method show($context) { $context.callframe.code.name } } my $lnames = []; $lnames[LogP6::Level::trace.Int] = 'TRACE'; $lnames[LogP6::Level::debug.Int] = 'DEBUG'; $lnames[LogP6::Level::info.Int] = 'INFO'; $lnames[LogP6::Level::warn.Int] = 'WARN'; $lnames[LogP6::Level::error.Int] = 'ERROR'; $lnames .= List; my $color = []; $color[LogP6::Level::trace.Int] = "33"; # yellow $color[LogP6::Level::debug.Int] = "32"; # green; $color[LogP6::Level::info.Int] = "34"; # blue; $color[LogP6::Level::warn.Int] = "35"; # magenta; $color[LogP6::Level::error.Int] = "31"; # red; $color .= List; my $code = %(:33yellow, :32green, :34blue, :35magenta, :31red); class LevelName does PatternPart { has $.levels; method new($conf) { my $levels = $lnames.clone.Array; my $length = $conf // 0; for 1..5 -> $i { $levels[$i] = $conf{$i} // $levels[$i]; $levels[$i] = sprintf('%-*.*s', $length, $length, $levels[$i]) if $length > 0; } self.bless(levels => $levels.List); } method show($context) { $!levels[$context.level]; } } class Color { ... } class ColorReset { ... } role ColorFactory { method create($conf) { return ColorReset if $conf{'reset'}; my $colors = $color.clone.Array; for 1..5 -> $i { $colors[$i] = $conf{$i} // $colors[$i]; $colors[$i] = "\e[" ~ $colors[$i] ~ 'm'; } Color.new(colors => $colors.List); } } class ColorReset does ColorFactory does PatternPart { method show($context) { "\e[0m" } } class Color does ColorFactory does PatternPart { has $.colors; method show($context) { $!colors[$context.level]; } } grammar Grammar is export { token TOP { * } proto token item { * } # %trait{short=[delimiter]number printf=%6s} - logger name (trait) token item:sym { '%trait'? } token trait-params { \{ \} } proto rule trait-param { * } rule trait-param:sym { 'short' '=' '[' $=<-[\]]>+ ']' } rule trait-param:sym { 'sprintf' '=' } # %tid - thread id token item:sym { '%tid' } # %tname - thread name token item:sym { '%tname' } # %msg - message token item:sym { '%msg' } # %ndc - ndc-stack token item:sym { '%ndc' } # %mdc{key} - mdc-value token item:sym { '%mdc' } token mdc-param { \{ \} } # glue between items token item:sym { $=<-[%]>+ } # %x{$msg $name $trace} - exception {message class-name backtrace} token item:sym { '%x'? } token x-params { \{ + \} } proto token x-param { * } token x-param:sym { '$msg' } token x-param:sym { '$name' } token x-param:sym { '$trace' } token x-param:sym { $=<-[$}]>+ } # %date{$yyyy-$yy-$MM-$MMM-$dd $hh:$mm:$ss:$mss $z} - date and time token item:sym { '%date'? } token date-params { \{ + \} } proto token date-param { * } token date-param:sym { '$' $='yy' ** 1..2 } token date-param:sym { '$' $=M ** 2..3 } token date-param:sym { '$dd' } token date-param:sym { '$hh' } token date-param:sym { '$mm' } token date-param:sym { '$ss' } token date-param:sym { '$mss' } token date-param:sym { '$z' } token date-param:sym { $=<-[$}]>+ } # %level{WARN=W DEBUG=D ERROR=E TRACE=T INFO=I length=2} token item:sym { '%level'? } token level-params { \{ + \} } proto rule level-param { * } rule level-param:sym { 'TRACE' '=' } rule level-param:sym { 'DEBUG' '=' } rule level-param:sym { 'INFO' '=' } rule level-param:sym { 'WARN' '=' } rule level-param:sym { 'ERROR' '=' } rule level-param:sym { 'length' '=' } # %framefile - frame file path token item:sym { '%framefile' } # %frameline - frame file line token item:sym { '%frameline' } # %framename - frame code name token item:sym { '%framename' } # %color{TRACE=yellow DEBUG=green INFO=blue WARN=magenta ERROR=red} # %color{reset} %creset token item:sym { '%color'? } token item:sym { '%creset' } token color-params { \{ \} } proto token color-param { * } token color-param:sym { + } token color-param:sym { 'reset' } proto rule color-level-param { * } rule color-level-param:sym { 'TRACE' '=' } rule color-level-param:sym { 'DEBUG' '=' } rule color-level-param:sym { 'INFO' '=' } rule color-level-param:sym { 'WARN' '=' } rule color-level-param:sym { 'ERROR' '=' } proto token color { * } token color:sym { $=('black' | 'white' | 'yellow' | 'green' | 'blue' | 'magenta' | 'red') } token color:sym { (';')* } token word { $=<-[\s}]>+ } token minus { '-' } token num { $=\d+ } token fract { '.' } token real-num { ??} } class Actions is export { method TOP($/) { my $items = $>>.made.List; my $first = $items.reverse.first(* ~~ ColorFactory); with $first { if $first ~~ ColorReset { make $items; } else { make (|$items, ColorReset).List; } } else { make $items; } } method item:sym($/) { with $ { make $.made } else { make Trait } } method trait-params($/) { make $.made } method trait-param:sym($/) { make TraitShort.new(:separator($.Str), |$.made); } method trait-param:sym($/) { make TraitSprintf.new(:placeholder($.Str)) } method item:sym($/) { make Tid } method item:sym($/) { make Tname } method item:sym($/) { make Msg } method item:sym($/) { make Ndc } method item:sym($/) { make Mdc.new(key => $.made) } method mdc-param($/) { make $.made } method item:sym($/) { make Glue.new($.Str) } method word($/) { make $.Str } method item:sym($/) { with $ { make X.new(pieces => $.made); } else { make X.new(pieces => (Glue.new("Exception "), XName, Glue.new(': '), XMsg, Glue.new("\n"), XTrace)); } } method x-params($/) { make $>>.made.List } method x-param:sym($/) { make XMsg } method x-param:sym($/) { make XName } method x-param:sym($/) { make XTrace.new } method x-param:sym($/) { make Glue.new($.Str) } method item:sym($/) { with $ { make Date.new(pieces => $.made); } else { make Date.new(pieces => (DateHour, Glue.new(':'), DateMinute, Glue.new(':'), DateSecond, Glue.new(':'), DateMSecond)); } } method date-params($/) { make $>>.made.List } method date-param:sym($/) { make $.chars == 4 ?? DateYearFour !! DateYearTwo; } method date-param:sym($/) { make $.chars == 2 ?? DateMonthNum !! DateMonthWord; } method date-param:sym($/) { make DateDay } method date-param:sym($/) { make DateHour } method date-param:sym($/) { make DateMinute } method date-param:sym($/) { make DateSecond } method date-param:sym($/) { make DateMSecond } method date-param:sym($/) { make DateZone } method date-param:sym($/) { make Glue.new($.Str) } method item:sym($/) { with $ { make LevelName.new($.made); } else { make LevelName.new(%()); } } method level-params($/) { make $>>.made.hash } method level-param:sym($/) { make Level::trace.Int => $.Str } method level-param:sym($/) { make Level::debug.Int => $.Str } method level-param:sym($/) { make Level::info.Int => $.Str } method level-param:sym($/) { make Level::warn.Int => $.Str } method level-param:sym($/) { make Level::error.Int => $.Str } method level-param:sym($/) { make 'length' => $.made.Str } method item:sym($/) { make FrameFile } method item:sym($/) { make FrameLine } method item:sym($/) { make FrameName } method item:sym($/) { with $ { make ColorFactory.create($.made); } else { make ColorFactory.create(%()); } } method item:sym($/) { make ColorReset } method color-params($/) { make $.made } method color-param:sym($/) { make $>>.made.hash } method color-param:sym($/) { make 'reset' => True } method color-level-param:sym($/) { make Level::trace.Int => $.made } method color-level-param:sym($/) { make Level::debug.Int => $.made } method color-level-param:sym($/) { make Level::info.Int => $.made } method color-level-param:sym($/) { make Level::warn.Int => $.made } method color-level-param:sym($/) { make Level::error.Int => $.made } method color:sym($/) { make $code{$.Str} } method color:sym($/) { make $/.Str } method minus($/) { make True } method num($/) { make $.Int } method fract($/) { make $.made } method real-num($/) { make %( :length($.made), :abreviature($.made // 0), :minus($.made // False) ); } }