A piggy bank of commands, fixes, succinct reviews, some mini articles and technical opinions from a (mostly) Perl developer.

Jump to

Quick reference

Showing posts with label logging. Show all posts
Showing posts with label logging. Show all posts

Logging IRC

Notes

irssi setup
  • Digital Ocean droplet
  • screen -s chat
  • irssi
    • basic usage
    • /win list
    • /win 1
  • # connect
    • /connect irc.perl.org
    • /server add -auto -network PERL irc.perl.org 6667

  • # log - foreach channel
    • irc.perl.org
      • #dbix-class
      • #moose
      • #perl-help
      • #catalyst
    • chat.freenode.net
      • #mojo
      • #perl
  • # do
    • LOGDIR=~/logs
    • /IGNORE -channels #channel * JOINS PARTS QUITS NICKS
    • /LOG OPEN -targets #channel $LOGDIR/channel-%Y-%m-%d.txt
    • /LOG START #channel
  • /SET autolog ON # does this cover all of the above?
Publish logs
  • nginx
    • enable autoindex in config
  • WEBDIR=/var/www/irclogs
  • mkdir $WEBDIR
  • chmod g+s $WEBDIR
  • crontab
    • cp $LOGDIR/*/*-`date +"\%Y-\%m-\%d"`.txt $WEBDIR
    • chmod g+r $WEBDIR/*-`date +"\%Y-\%m-\%d"`.txt
  • password protect the logs, so you don't break IRC terms of service

Software logging philosophy

Ideas:

  • Always set up logging from the start of the application, even if there is nothing to log.
    • Imagine in future there is an error reported in the front-end.
    • To investigate, there could be more error information in the logs.
    • And even if not, to investigate further, more debug info could be added to the log temporarily, even for a specific client.
    • If that requires you to set up logging infrastructure, it's much less likely to be done at all
    • So it's important to have a logging system in place for that reason.
  • Distinguish types of logging (see other post):
    • Event - this can include debug, info, warn, error. It is logged and that's it.
    • Exception - unexpected error that requires action to resolve. A human should be notified.
    • Request - every request made (higher/framework level)
  • ...

How to disable Mojo debug logging

When setting MOJO_LOG_LEVEL does not work... Make the following hacks:

removing debug output

~ ============================================================================ Tuesday 12th February 2019

~~~ FIXES FOR OLD VERSION OF MOJO (Supervillain 8.03)

../local/lib/perl5/Mojolicious.pm
132:    $self->log->debug(qq{$method "$path" ($id)}) if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};

../local/lib/perl5/Mojolicious/Controller.pm
209:      $app->log->debug("$code $msg (${elapsed}s, $rps/s)") if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};

../local/lib/perl5/Mojolicious/Plugin/EPLRenderer.pm
18:    $c->app->log->debug("Rendering cached @{[$mt->name]}") if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};
30:      $c->app->log->debug(qq{Rendering inline template "$name"}) if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};
40:        $c->app->log->debug(qq{Rendering template "$name"}) if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};
46:        $c->app->log->debug(qq{Rendering template "$name" from DATA section}) if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};

../local/lib/perl5/Mojolicious/Routes.pm
95:  $app->log->debug('Routing to a callback') if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};
150:    $log->debug(qq{Routing to application "$class"}) if $ENV{BB_DEBUG} || $ENV{MOJO_DEBUG};
164:      $log->debug(qq{Routing to controller "$class" and action "$method"}) if $ENV{MOJO_DEBUG} || $ENV{BB_DEBUG};

~ ============================================================================ Thursday 7th March 2019

local/lib/perl5/Mojolicious.pm
123:          #return unless $ENV{MOJO_DEBUG}; # WS hack

local/lib/perl5/Mojolicious/Controller.pm
187:      return unless $ENV{MOJO_DEBUG};

local/lib/perl5/Mojolicious/Routes.pm
103:  $app->log->debug('Routing to a callback') if $ENV{MOJO_DEBUG};
170:      $log->debug(qq{Routing to controller "$class" and action "$method"}) if $ENV{MOJO_DEBUG};

x
491:>> /Users/will/dev/ats-broadcast-hub/local/lib/perl5/Mojolicious/Routes.pm:103:   $app->log->debug('Routing to a callback') if $ENV{MOJO_DEBUG};

~~~~ AND ~~~~

To fix CODE(0x7fde03335ca0) now:

vi local/lib/perl5/Mojolicious.pm +122

~~~ OLD VERSION OF MOJO

local/lib/perl5/Mojolicious.pm

  60 our $CODENAME = 'Supervillain';
  61 our $VERSION  = '8.03';

 119   # Start timer (ignore static files)
 120   my $stash = $c->stash;
 121   unless ($stash->{'mojo.static'} || $stash->{'mojo.started'}) {
 122     my $req    = $c->req;
 123     my $method = $req->method;
 124     my $path   = $req->url->path->to_abs_string;
 125     my $id     = $req->request_id;
 126     $self->log->debug(qq{$method "$path" ($id)});
 127     $c->helpers->timing->begin('mojo.timer');
 128   }

~~~ NEW VERSION OF MOJO

 119   # Start timer (ignore static files)
 120   my $stash = $c->stash;
 121   $self->log->debug(sub {
 122     my $req    = $c->req;
 123     my $method = $req->method;
 124     my $path   = $req->url->path->to_abs_string;
 125     my $id     = $req->request_id;
 126     $c->helpers->timing->begin('mojo.timer');
 127     return qq{$method "$path" ($id)};
 128   }) unless $stash->{'mojo.static'};
 129 

~~~ FIX FOR NEW VERSION (8.12)

vi local/lib/perl5/Mojolicious.pm

  61 our $CODENAME = 'Supervillain';
  62 our $VERSION  = '8.12';

 119   # Start timer (ignore static files)
 120   my $stash = $c->stash;
 121     my $req    = $c->req;
 122     my $method = $req->method;
 123     my $path   = $req->url->path->to_abs_string;
 124     my $id     = $req->request_id;
 125     $c->helpers->timing->begin('mojo.timer');
 126 if ($ENV{MOJO_DEBUG}) {
 127     $self->log->debug(qq{$method "$path" ($id)}) unless $stash->{'mojo.static'};
 128 }

vi local/lib/perl5/Mojolicious/Controller.pm

184     # Disable auto rendering and stop timer
185     my $app = $self->render_later->app;
186 #    $app->log->debug(sub {
187       my $timing  = $self->helpers->timing;
188       my $elapsed = $timing->elapsed('mojo.timer') // 0;
189       my $rps     = $timing->rps($elapsed) // '??';
190       my $code    = $res->code;
191       my $msg     = $res->message || $res->default_message($code);
192 #      return "$code $msg (${elapsed}s, $rps/s)";
193 #    }) unless $stash->{'mojo.static'};
194 if ($ENV{MOJO_DEBUG}) {
195       $app->log->debug("$code $msg (${elapsed}s, $rps/s)") unless $stash->{'mojo.static'}; # MOJO_DEBUG
196 }



How to do logging and monitoring

A Logging Standard

Format

Use JSON

  • Human readable(ish)
  • Machine readable
  • Abundant tooling
    • jq
    • Document stores like Elastic Seach
    • almost every programming langauge

Common Data Structures

Interoperability enhanced by adopting consistent data structures (detailed below).
Include Provenance IDs.

Provenance IDs

What is a Provenance ID?

  • Provide a Universally Unique ID (but not a UUID)
  • Provide a calling context
    • Clear relationship between a parent ID and its children.

ID Generation

If a provenance ID is provided with a request, use it as the parent of all work.
If no ID is found, generate a new ID from your service's Base ID and use it.

Adoption patterns

As services add support, we will be able to make connections between systems, but no system will fail to track traffic while waiting for upstream services to implement tracking.

Web Service Implementation

HTTP/HTTPS services need to check for the X-Bean-ProvenanceId header.

Structure

  • Base ID 
    • Any alpha-numeric string
    • Something like "SuperProductTypeCGI"
    • One scheme that works well is "Product-Service" like "ABC-Superprod"
  • Unique Element
    • Any string that provides uniqueness
    • UUID is convenient, base64 encoding can keep it shorter.
    • But any string that is unique across multiple systems running with the same base ID.
  • Request Path
    • Each child request adds a segment with a monotonically increasing value.
    • So the first child gets any natural number.  Typically 1
    • Any subsequent children must get a larger number. Typically the previous value, incremented by one.
    • Local and remote children can have separate or shared counters.
    • New items are appended as a comma delimited list.

ABNF Grammar

provenance-id = base-id "." unique-element request-path

base-id = ALPHA *safe-character

unique-element = *safe-character

request-path = ":" request-list
request-path =/ ""

request-path = local-request request-tail
request-path =/ remote-request request-tail

request-tail = "," 
request-tail =/ ""

local-request = number

remote-work = number "r"

number = non-zero-digit *DIGIT

non-zero-digit = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9"

safe-character = ALPHA / DIGIT / safe-symbol

; Any printing, non-alphanumeric character isn't ,: or whitespace
safe-symbol =  "!" / DQUOTE / "#" /"$" / "%" / "^" / "&" / "'" / "(" ")" / "*" / "+"
safe-symbol =/ "-" / "." 
safe-symbol =/ "/"
safe-symbol =/ "<" / "=" / ">" / "?" / "@"
safe-symbol =/ "[" / "\" / "^" / "_" / "`"
safe-symbol =/ "{" / "|" / "}" / "~"

Example

Base ID:  ABC-Superprod
Unique Element: 8ed56f13-bd6c-4f47-a8eb-94604e07e6ac
  • New request from customer: ABC-Superprod.8ed56f13-bd6c-4f47-a8eb-94604e07e6ac
  • Internal call to client config: ABC-Superprod.8ed56f13-bd6c-4f47-a8eb-94604e07e6ac:3
  • Client config looks at User config: ABC-Superprod.8ed56f13-bd6c-4f47-a8eb-94604e07e6ac.3.12r

Log Entry Objects

Log Item Types

Log Entry

[ Log Header, Log Body ]

Log Header

[   ISO8601-Time with milliseconds in UTC,
    Log Type Name, 
    hostname,
    program name,
    process ID, 
    thread ID,
    Provenance ID,
    Context name
]

Log Body

{   Log Type Name => Log Type Data,
    file => name of file where log entry generated,
    line => line number of log entry,
    method => name of method/function that generated the entry,
}

Event

Type Name: EVENT
{    message => A string describing the event
}

Unit Of Work

Type Name: UOW
{   start    => ISO6801 date time stamp with milliseconds
    end      => ISO6801 date time stamp with milliseconds
    duration => duration in milliseconds, integer value
    result   => result type identifier
    metrics  => dictionary, keys are strings values are metric objects
    values   => dictionary, keys are strings, values are strings
}

Metric

{    units => string
     count => integer, the number of times the metric was incremented
     total => sum of all values assigned to the metric
}   

Result Types

NORMAL -  successful completion of work
INVALID - Unit of Work terminated improperly
FAILURE - Unit of Work could not be completed, for example, the requested file does not exist.
EXCEPTION - Unit of Work generated an exception and could not be completed.

What Next?

Early to Mid-November

  • Review Object definitions
  • Prepare JSON Schema for object definitions
  • Prepare enhanced demo.
  • Meet with Tech Teams (US and UK) and cover proposal.

The Future

  • Collate feedback from Tech Teams.
  • Finalize object and document all data structures
  • Adapt Log::Work to work with existing BB logging facilities
  • Work with Operations to ensure that their tools can digest generated logs
  • Work with other teams to get adoption of the standard
  • Create Provenance ID tooling in Node.  
    • Should client side code be able to generate Provenance IDs?
      • Lean strongly towards "no" - risk of duplicate IDs being injected maliciously.  
      • Instead use session IDs as a correlation ID for UI interaction.
      • Extending tracking would be nice, but need a way to do it safely.
  • Set up proxies/firewalls to strip provenance ID headers from outgoing requests/responses.
    • May need exceptions to handle cases where data round trips through customer servers/services.

See Also

^ Thanks to Hypno-Mark for the section above ^
Types of logging events

0. Requests & responses

Code lives at framework level, should be tracked in ElasticSearch ONLY. 
For all apps.

1. Unexpected exception

Caught by framework, i.e. application crashes, should be tracked in Sentry
Fail-safe catch-all, very useful

2. Expected fatal exception

Programmed in deliberately, "should never happen, but just in case", should be tracked in Sentry
Quite rare, but always some action required by devops.

3. Expected non-fatal, actionable error

Programmed in deliberately, "action required by devops", should be tracked in Sentry

4. Expected non-fatal non-actionable error

Programmed in deliberately, "no action required", should NOT go to Sentry, only to ElasticSearch.
3 & 4 are often confused with each other, leading to an alerts system polluted with non-actionable events.

5. Expected WARN or INFO level events

Also desirable to track these. Programmed in deliberately, should NOT go to Sentry, only to ElasticSearch.

Systems

Which systems contain data about events?
  • Grafana - metrics only (i.e. a number not a string, so no error messages here).
  • EKK stack (ElasticSearch, Amazon Kinesis, and Kibana) - receives ndjson files.
  • Sentry - supposed to handle urgent events that require an action, but may be polluted over time with non-actionable events.
  • S3 - some applications may send a complete decoded copy of the request/response to an S3 bucket for future reference

^ Will's work above ^

Elasticsearch basics

Warning: Your Elasticsearch / ELK stack based logging solution may take a huge amount of disk space, and indexing of large amounts of data may also take so long that it can't keep up with the logs being generated.

In short, you need massive/cluster/cloud resources to support Elasticsearch.

Search:

curl '{endpoint}/_search?q=title:jones&size=5&pretty=true'

List indexes:

curl -s '{endpoint}/_cat/indices?v' | sort

Upload a template:

curl -X POST -H "Content-Type: application/json" -d @path/to/template.json 'http://elastic:changeme@localhost:9200/_template/testlog?pretty'

Add a document:

curl -X POST -H "Content-Type: application/json" -d '{ "timestamp": "2019-04-06T14:13:31", "message": "bar baz qux" }' http://elastic:changeme@localhost:9200/testlog/footype?pretty

Range query:

curl -X GET -H "Content-Type: application/json" -d '{ "query": { "range" : { "timestamp" : { "gte" : "2019-04-02T15:13:31", "lte" : "2019-05-09T14:13:31", "boost" : 2.0 } } } }' http://elastic:changeme@localhost:9200/testlog/_search?pretty

Select sub-section of putty buffer only

Are you sick and tired of waiting for your terminal to scroll down when dragging to select a large portion of the scrollback buffer in putty?

The putty developers already thought of that!


  1. Go to Putty Configuration window
  2. Choose "Selection" from category on the left of the window.
  3. Under 'Control use of mouse', choose 'Compromise (Middle extends, Right pastes)' if not already chosen.

(source)

Useful bash flags for scripts verbose trace expand error exit

Useful flags:
set -v # "verbose" - echo commands
set -x # "xtrace" - like verbose but expands commands
set -e # "errexit" - abort script at first error
set -u # "nounset" - strict mode: undefined variable causes an exit

Long format:
set -o verbose

Bash "strict mode":
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

Use Data::Dumper with expensive data in perl

$logger->debug(sub { "large data structure is: ".Dumper( expensive_calculation($foo) ) });

This ensures that expensive_calculation() is not performed unless the log level is DEBUG or below.

Colouring terminal output


Thanks to StackOverflow

UPDATE: You can actually do this yourself with pretty much any programming language that has an ANSI colours library.

Know if output is being captured in Perl

if (-t STDERR) {
    print "standard error is not being captured (it's going to the terminal)\n";
    # email the error
}
else {

    print "standard error IS being captured\n";
    # just print the error
}

Log and email your Apache errors

In the .htaccess at the root of the site:
ErrorDocument 404 http://www.example.com/error_page.php?err=404
ErrorDocument 503 http://www.example.com/error_page.php?err=503
etc.

In error_page.php:


$myemail = "someone@example.com";
$subject = "example.com: $errorNum error";
$message = "$errorNum Error Report:\n";
$message .= "\nHTTP_REFERRER: ".$_SERVER['HTTP_REFERER'];
$message .= "\nREQUEST_URI: ".$_SERVER['REQUEST_URI'];
$message .= "\nHTTP_USER_AGENT: ".$_SERVER['HTTP_USER_AGENT'];
$message .= "\nQUERY_STRING: ".$_SERVER['QUERY_STRING'];
$message .= "\nREMOTE_ADDRESS: ".$_SERVER['REMOTE_ADDR'];


ob_start();
print "\n\nSERVER = "; print_r( $_SERVER );
print "\nGET = "; print_r( $_GET );
print "\nPOST = "; print_r( $_POST );
print "\nFILES = "; print_r( $_FILES );
print "\nREQUEST = "; print_r( $_REQUEST );
print "\nSESSION = "; print_r( $_SESSION );
print "\nENV = "; print_r( $_ENV );
print "\nCOOKIE = "; print_r( $_COOKIE );
print "\nprevious php_errormsg = "; print_r( $php_errormsg );
print "\nargv = "; print_r( $argv );
$output = ob_get_clean();
$message .= "\n\n$output\n";


# email the error
mail($myemail,$subject,$message,"From: support@example.com");


# log the error
$myFile = "logs/error.".date('Y_m_d-H.i.s').".log";
$fh = fopen($myFile, 'w');
$stringData = $message;
fwrite($fh, $stringData);
fclose($fh);
?>

Have unattended scripts look after themselves

# Send all output to a logfile and supress input
typeset LOG="/tmp/${0##*/}.out"
mv $LOG ${LOG}.old >/dev/null 2>&1
[[ -t 1 ]] && echo "Writing to logfile '$LOG'."
exec > $LOG 2>&1
exec < /dev/null 2<&1

Configuring log4j for maven

To debug: Add -Dlog4j.debug to the jvm parameters.

The files log4j.properties or log4j.xml must be on the classpath.

Tailing all the logs files

Truncate all the log files:
find . -name "*.log" -exec cp /dev/null {} \;

Tail all the log files at once:
tail -f $(find . -name "*.log")

Simple log4perl config

log4perl.rootLogger=DEBUG, STDERR
log4perl.appender.STDERR=Log::Log4perl::Appender::Screen
log4perl.appender.STDERR.layout=PatternLayout
log4perl.appender.STDERR.layout.ConversionPattern=[%d] [%p %c] - %m%n

Using an XML configuration file for Log::Log4perl

XML configuration file:

<!--
* General configuration for log4perl
* LOGFILE.filename may be set by the application
* All logging goes to STDOUT (CONSOLE)
-->
<log4perl>
<log4perl.rootlogger>WARN, CONSOLE, LOGFILE</log4perl.rootLogger>

<log4perl.appender.console>Log::Log4perl::Appender::Screen</log4perl.appender.CONSOLE>
<log4perl.appender.console.layout>PatternLayout</log4perl.appender.CONSOLE.layout>
<log4perl.appender.console.layout.conversionpattern>[%d] [%p %c] - %m%n</log4perl.appender.CONSOLE.layout.ConversionPattern>

<log4perl.appender.logfile>Log::Log4perl::Appender::File</log4perl.appender.LOGFILE>
<!-- Let logging come to STDERR on the console, and *pipe* into a log so that unexpected errors are caught
<log4perl.appender.logfile.filename>/optional/absolute/path/to/logfile.log</log4perl.appender.LOGFILE.filename>
-->
<log4perl.appender.logfile.mode>append</log4perl.appender.LOGFILE.mode>
<log4perl.appender.logfile.layout>PatternLayout</log4perl.appender.LOGFILE.layout>
<log4perl.appender.logfile.layout.conversionpattern>[%d] [%p %c] - %m%n</log4perl.appender.LOGFILE.layout.ConversionPattern>

<!-- Add different log levels for specified modules
<log4perl.logger.clickthrough.transform>INFO</log4perl.logger.ClickThrough.Transform>
-->
</log4perl>


How to read the configuration:

my $log4perl = XML::Simple->new(ForceArray=>0, KeyAttr=>[])->XMLin( $log4perl_config_filename );
my $logname = $0;
$logname =~ s/\.pl$/.log/;
$log4perl->{'log4perl.appender.LOGFILE.filename'} = $logname;
Log::Log4perl::init($log4perl);
my $logger = Log::Log4perl->get_logger(__PACKAGE__);

Crontab format (V3)

MAILTO=me@there.co.uk

# minute (0-59),
# | hour (0-23),
# | | day of the month (1-31),
# | | | month of the year (1-12),
# | | | | day of the week (0-6 with 0=Sunday).
# | | | | | commands
# 3 2,3,4 * * 0,6 /command to run on more than one day/hour
# 3 2,4-7 * * 1-5 /another/command to run on a range of days/hours

04 10 * * * /command/here 2>> /path/to/log/prog.$(date +\%Y\%m\%d).log

NOTES:
  • You must escape percent signs with a backslash
  • Putting them in double quotes doesn't work
  • Putting in double quotes and backslashes works, but the backslashes are written as part of the filename. So don't so that.

Different files for log4perl levels

log4perl.rootLogger=DEBUG,CONSOLE,ViewableLog,DebugLog,ErrorLog

###################################################################################
# console - what to watch
log4perl.appender.CONSOLE=Log::Log4perl::Appender::Screen
log4perl.appender.CONSOLE.layout=PatternLayout
log4perl.appender.CONSOLE.layout.ConversionPattern=[%d] [%p %c] - %m%n
log4perl.appender.CONSOLE.Filter = ViewableFilter

  log4perl.filter.ViewableFilter        = sub {    \
       my %p = @_;                           \
       $p{log4p_level} eq "FATAL" or          \
       $p{log4p_level} eq "ERROR" or          \
       $p{log4p_level} eq "INFO" or          \
       $p{log4p_level} eq "WARN"           \
                                          }

###################################################################################
# Viewable log - record of CONSOLE
log4perl.appender.ViewableLog=Log::Log4perl::Appender::File
log4perl.appender.ViewableLog.layout=PatternLayout
log4perl.appender.ViewableLog.layout.ConversionPattern=[%d] [%p %c] - %m%n
log4perl.appender.ViewableLog.filename = logs/output.log
log4perl.appender.ViewableLog.Filter = ViewableFilter

###################################################################################
# Error log - warnings and errors
log4perl.appender.ErrorLog=Log::Log4perl::Appender::File
log4perl.appender.ErrorLog.layout=PatternLayout
log4perl.appender.ErrorLog.layout.ConversionPattern=[%d] [%p %c] - %m%n
log4perl.appender.ErrorLog.filename = logs/error.log
log4perl.appender.ErrorLog.Filter = ErrorFilter

  log4perl.filter.ErrorFilter        = sub {    \
       my %p = @_;                           \
       $p{log4p_level} eq "FATAL" or          \
       $p{log4p_level} eq "ERROR" or          \
       $p{log4p_level} eq "WARN"           \
                                          }

###################################################################################
# Debug Log - everything
 log4perl.appender.DebugLog = Log::Log4perl::Appender::File
 log4perl.appender.DebugLog.layout=PatternLayout
 log4perl.appender.DebugLog.layout.ConversionPattern=[%d] [%p %c] - %m%n
  log4perl.appender.DebugLog.filename = logs/debug.log
  log4perl.appender.DebugLog.Filter = AllFilter

  log4perl.filter.AllFilter        = sub {    \
       my %p = @_;                           \
       $p{log4p_level} eq "FATAL" or          \
       $p{log4p_level} eq "ERROR" or          \
       $p{log4p_level} eq "WARN" or          \
       $p{log4p_level} eq "INFO" or          \
       $p{log4p_level} eq "DEBUG" or          \
       $p{log4p_level} eq "TRACE"           \
                                          }

###################################################################################
# To enable DEBUG-level logging for a particular module (and any modules that subclass it),
# un-comment the following and change e.g. 'TempTopicsAdminTools.ModuleManager' to the your own module.

##log4perl.logger.TempTopicsAdminTools.ModuleManager=DEBUG
#log4perl.logger.TempTopicsAdminTools.PageManager=WARN
#log4perl.logger.TempTopicsAdminTools.CategoryManager=WARN

Coloured log output

Tail the log and pipe it through this:

#!/usr/bin/perl

use warnings;
use strict;
use Term::ANSIColor qw(color);

while( <> ) {
chomp;
s/\t/ /g;
(/ERROR|failed/) && do {
print( color( 'red' ). $_ .color( 'reset' )."\n" );
next;
};
(/WARN/) && do {
print( color( 'magenta' ). $_ .color( 'reset' )."\n" );
next;
};
(/succeeded/) && do {
print( color( 'green' ). $_ .color( 'reset' )."\n" );
next;
};
(/INFO/) && do {
print( color( 'yellow' ). $_ .color( 'reset' )."\n" );
next;
};
(/DEBUG/) && do {
print( color( 'blue' ). $_ .color( 'reset' )."\n" );
next;
};
print $_."\n";
}