diff --git a/lib/SLUB/LZA/Rosetta/TA/Command/log.pm b/lib/SLUB/LZA/Rosetta/TA/Command/log.pm new file mode 100644 index 0000000000000000000000000000000000000000..7810057af4b00214bfb14fba44321025916251d6 --- /dev/null +++ b/lib/SLUB/LZA/Rosetta/TA/Command/log.pm @@ -0,0 +1,192 @@ +package SLUB::LZA::Rosetta::TA::Command::log; +use strict; +use warnings; +use feature qw(say); +use Regexp::Optimizer; +use DateTime; +use DateTime::Format::DateParse; +use Text::CSV_PP; + +use SLUB::LZA::Rosetta::TA -command; + +sub abstract {"grep server log of Rosetta based Archival Information System";} + +my $description=<<"DESCR"; +Searches logfiles of Rosetta-based AIS + +Examples: + + * What are the error messages in last 24 hours? + $0 log --level error --last-24h + * What are error and warning messages between 2022-01-01 and 2022-02-01? + $0 log --level error --level warning --fromdate 2022-ß1-01 --todate 2021-02-ß1 + * Are there lines with regex "match"? + $0 log --match "match" +DESCR + +sub description { + "$description" +} + +sub opt_spec { + return( + ["verbose|v" => "enable verbose output"], + ["outputfilter" => hidden => {one_of => [ + ["colorize|c" => "colorize output"], + ["csv" => "use csv output"], + ]}], + ["datemode" => hidden => {one_of => [ + ["last-24h" => "search within last 24h"], + ["fromdate=s" => "search starting with date"], + ["todate=s" => "search ending with date"], + ] } ], + ["level=s@" => "levels to search for. Levels could be: 'error', 'warn', 'info', debug. You could use multiple levels by repeating"], + ["match=s" => "perl regex to search for" => {default=>".*"}], + ); +} + +sub validate_date { + my $self = shift; + my $datestr = shift; + return ($datestr =~ m/^20[0123][0-9]-[0-1][0-9]-[0-3][0-9]$/); +} + +sub validate_args { + my ($self, $opt, $args) = @_; + # no args allowed but options! + $self->usage_error("No args allowed") if @$args; + if (defined $opt->fromdate + and defined $opt->last_24h + and $opt->last_24h == 1 + ) { + $self->usage_error("--last-24h and --fromdate not combinable"); + } + if (defined $opt->todate + and defined $opt->last_24h + and $opt->last_24h == 1 + ) { + $self->usage_error("--last-24h and --todate not combinable"); + } + # check dates + if (defined $opt->fromdate && !$self->validate_date($opt->fromdate)) { + $self->usage_error("--fromdate $opt->{fromdate} not a valid date"); + } + if (defined $opt->todate && !$self->validate_date($opt->todate)) { + $self->usage_error("--todate $opt->{todate} not a valid date"); + } + # TODO: check levels + 1; +} + +sub create_regex_last24h { + my $dt = DateTime->now; + my $todate = $dt->ymd; + my $fromdate= $dt->subtract( hours => 24)->ymd; + my $rxo = Regexp::Optimizer->new->optimize(qr/$fromdate|$todate/); + return $rxo; +} + +sub create_regex_from_to { + my $from=shift // "2000-01-01"; + my $to=shift // "2059-12-31"; + my $dt_from = DateTime::Format::DateParse->parse_datetime("$from"); + my $dt_to = DateTime::Format::DateParse->parse_datetime("$to"); + my @date_tmo_s; + for (my $dt = $dt_from; $dt->epoch() <= $dt_to->epoch; $dt->add(days => 1) ) { + push @date_tmo_s, $dt->ymd; + } + my $date_rx_string = join("|", @date_tmo_s); + my $rxo = Regexp::Optimizer->new->optimize(qr/$date_rx_string/); + return $rxo; +} +{ + my $bred = "\e[1;31m"; + my $red = "\e[31m"; + my $green = "\e[32m"; + my $blue = "\e[34m"; + my $bblue = "\e[1;34m"; + my $gray = "\e[90m]"; + my $reversed = "\e[7m"; + my $reset = "\e[0m"; + my $back_yellow = "\e[103m"; + my $back_cyan = "\e[45m"; + my $datetime_rx=qr/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d,\d\d\d/; + sub colorize { + my $line = shift; + my $opt = shift; + my $match_rx = shift; + # patterns in common interest: + $line =~ s/^($datetime_rx)/${blue}$1${reset}/; + if ($opt->{match} ne ".*") { + $line =~ s/( (DEBUG|INFO|WARN|ERROR) .*?)($match_rx)/$1${reversed}$3${reset}/; # order important! + } + $line =~ s/ (DEBUG) / ${gray}$1${reset} / + || $line =~ s/ (INFO) / ${green}$1${reset} / + || $line =~ s/ (WARN) / ${red}$1${reset} / + || $line =~ s/ (ERROR) / ${bred}$1${reset} /; + $line =~ s/(SIP ?\d+)/${back_yellow}$1${reset}/; + $line =~ s/(IE ?\d+)/${back_yellow}$1${reset}/; + $line =~ s/(dc.identifier)/${back_cyan}$1${reset}/; + return $line; + } +} + +{ + my $csv; + sub csv { + my $line = shift; + my $opt = shift; + my $match_rx = shift; + my $ret; + if (!defined $csv) { + $csv = Text::CSV_PP->new( + { + sep_char => ";", + + } + ); + $ret=join(";", qw(date time level where msg))."\n"; + } + my $date_rx=qr/\d\d\d\d-\d\d-\d\d/; + my $time_rx=qr/\d\d:\d\d:\d\d,\d\d\d/; + my $level_rx=qr/DEBUG|INFO|WARN|ERROR/; + my $where_rx=qr/\[.*?\]/; + my $msg_rx=qr/.*$/; + $line =~ m/^($date_rx) ($time_rx) ($level_rx) ($where_rx) ($msg_rx)/; + $csv->combine($1, $2, $3, $4, $5); + $ret.= $csv->string; + } +} + +sub execute { + my ($self, $opt, $args) = @_; + # create date_rx if provided by CLI + my $date_rx=qr/[^ ]*/; + if (defined $opt->last_24h and $opt->last_24h == 1) { + $date_rx=create_regex_last24h(); + } elsif (defined $opt->datemode + and ( + $opt->datemode eq "todate" + or $opt->datemode eq "fromdate" + ) + ) { + $date_rx=create_regex_from_to($opt->fromdate, $opt->todate); + } + # create level_rx if multiple levels provided by CLI + my $level_rx=qr/(DEBUG|INFO|WARN|ERROR)/; + if (defined $opt->level) { + my $rx_string = join("|", map {uc} @{ $opt->level }); + $level_rx = Regexp::Optimizer->new->optimize(qr/$rx_string/); + } + my $match_rx=qr{$opt->{match}}; + # prepare output filter + my $output_filter=sub { $_[0]; }; + if (defined $opt->colorize) { + $output_filter = sub { colorize($_[0], $opt, $match_rx); }; + } elsif (defined $opt->csv) { + $output_filter = sub { csv($_[0], $opt, $match_rx); }; + } + SLUB::LZA::Rosetta::TA::scan_log($date_rx, $level_rx, $match_rx, $output_filter); +} + +1; \ No newline at end of file