#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html

package xCAT::MsgUtils;

# if AIX - make sure we include perl 5.8.2 in INC path.
#       Needed to find perl dependencies shipped in deps tarball.
if ($^O =~ /^aix/i) {
    unshift(@INC, qw(/usr/opt/perl5/lib/5.8.2/aix-thread-multi /usr/opt/perl5/lib/5.8.2 /usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi /usr/opt/perl5/lib/site_perl/5.8.2));
}

use strict;
use Sys::Syslog;
use xCAT::Utils;

#use locale;
use Socket;
use File::Path;
use constant PERF_LOG => "/var/log/xcat/perf.log";

my $host = "";
my $isSN = xCAT::Utils->isServiceNode();
$::NOK = -1;
$::OK  = 0;

#--------------------------------------------------------------------------------

=head1    xCAT::MsgUtils


=head2    Package Description


This program module file, supports the xcat messaging and logging



=cut

#--------------------------------------------------------------------------------

=head2    Package Dependancies

    use strict;
    use Fcntl qw(:flock);
    use File::Basename;
    use File::Find;
    use File::Path;    # Provides mkpath()


=cut

#--------------------------------------------------------------------------------

=head1    Subroutines

=cut

=head3    message

    Display a msg  STDOUT,STDERR,log a msg and/or return to callback function.

    Arguments:
        The arguments of the message() function are:


			If address of the callback is provided,
			then the message will be returned either
			as data to the client's callback routine or to the
			xcat daemon or Client.pm ( bypass) for display/logging.
			See flags below.

			If address of the callback is not provide, then
			the message will be displayed to STDERR or STDOUT or
			added to SYSLOG.  See flags below.

        	If logging (L) is requested, the message structure
			must be a simple string. The routine will convert
			it to the appropriate callback structure, if a callback
        	is provided.
	        Note for logging xCAT:MsgUtils->start_logging and
	        xCAT:MstUtils->stop_logging must be used to
			open and close the log.

			For compatibility with existing code, the message routine will
			move the data into the appropriate callback structure, if required.
			See example below, if the input to the message routine
			has the "data" structure  filled in for an error message, then
			the message routine will move the $rsp->{data}->[0] to
			$rsp->{error}->[0]. This will allow xcatd/Client.pm will process
			all but "data" messages.

			The current client code should not have to change.

		      my %rsp;
		         $rsp->{data}->[0] = "Job did not run. \n";
	             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);

               Here the message routine will move $rsp->{data}->[0] to
			   $rsp->{error}->[0], to match the "E"message code.
			   Note the message
			   routine will only check for the data to either exist in
			   $rsp->{error}->[0] already, or to exist in $rsp->{data}->[0].

            Here's the meaning of the 1st character, if a callback specified:

                D - DATA this is returned to the client callback routine
                N - Node Data this is returned to the client callback routine
                E - error this is displayed/logged by daemon/Client.pm.
                I - informational this is displayed/logged by daemon/Client.pm.
                S - Message will be logged to syslog ( severe error)
					 syslog  facily (local4) and priority (err) will be used.
					 See /etc/syslog.conf file for the destination of the
					 messages.
                     Note S can be combined with other flags for example
					 SE logs message to syslog to also display the
					 message by daemon/ Client.pm.
                V - verbose.  This flag is not valid, the calling routine
				should check for verbose mode before calling the message
				routine and only use the I flag for the message.
				If V flag is detected, it will be changed to an I flag.
                W - warning this is displayed/logged by daemon/Client.pm.
                L - Log error to xCAT Log on the local machine.
					Routine must have setup log by calling
					MsgUtils->start_log routine which returns
					$::LOG_FILE_HANDLE.  Log is closed with
					MsgUtils->stop_log routine. Note can be combined with
					other flags:
                    LS - Log to xCAT Log and Syslog
                    LSE/LSI - Log to xCAT Log and Syslog  and display
					if this option is used the message must be a simple
					string. The message routine will format for callback
					based on the (D,E,I,W) flag.


            Here's the meaning of the 1st character, if no callback specified:

                D - DATA  goes to STDOUT
                E - error.  This type of message will be sent to STDERR.
                si - informational status info (sinfo)
                I - informational  goes to STDOUT
                N - Node informational  goes to STDOUT
                S - Message will be logged to syslog ( severe error)
                     Note S can be combined with other flags for example
		            SE logs message to syslog and is sent to STDERR.
			        SA logs message to syslog and to the auditlog DB table. (only xcatd)
		        A logs message auditlog DB table only. (only for xcatd)
                V - verbose.  This flag is not valid, the calling routine
				should check for verbose mode before calling the message

				routine and only use the I flag for the message.
				If V flag is detected, it will be changed to an I flag.
                W - warning goes to STDOUT.
                L - log  goes to /var/log/xcat/<logname>
				   ( see MsgUtils->start_log)
					Routine must have setup log by calling
					MsgUtils->start_log routine which returns
					$::LOG_FILE_HANDLE.  Log is closed with
					MsgUtils->stop_log routine. Note can be combined with
					other flags:
                    LS - Log to xCAT Log and Syslog
                    LSE/LSI - Log to xCAT Log and Syslog  and display

    Returns:
        none

    Error:
        none

    Example:

    Use with no callback
		# Message to STDOUT
        xCAT::MsgUtils->message('I', "Operation $value1 succeeded\n");
        xCAT::MsgUtils->message('N', "Node:$node failed\n");
		
		# Message to STDERR
        xCAT::MsgUtils->message('E', "Operation $value1 failed\n");
		
        # Message to Syslog
        xCAT::MsgUtils->message('S', "Host $host not responding\n");
		
        # Message to Syslog and auditlog table (only used by xcatd)
		# see tabdump -d auditlog
        my $rsp = {};
		$rsp->{syslogdata}->[0] = "$host not responding\n"; # for syslog
		# the next data is for auditlog table,  audittime added below
		$rsp->{userid} ->[0] = $user;
		$rsp->{clientname} -> [0] = $client;
		$rsp->{clienttype} -> [0] = $clienttype;
		$rsp->{command} -> [0] = $command;
		$rsp->{noderange} -> [0] = $noderange;
		$rsp->{args} -> [0] = $arguments;
		$rsp->{status} -> [0] = $status;
        xCAT::MsgUtils->message('SA', $rsp);
		
        # Message to only  auditlog table (only used by xcatd)
		# see tabdump -d auditlog
        my $rsp = {};
		$rsp->{userid} ->[0] = $user;
		$rsp->{clientname} -> [0] = $client;
		$rsp->{clienttype} -> [0] = $clienttype;
		$rsp->{command} -> [0] = $command;
		$rsp->{noderange} -> [0] = $noderange;
		$rsp->{args} -> [0] = $arguments;
		$rsp->{status} -> [0] = $status;
        xCAT::MsgUtils->message('A', $rsp);
		
        # Message to Log and Syslog
        xCAT::MsgUtils->message('LS', "Host $host not responding\n");
		
        # Message to Log
        xCAT::MsgUtils->message('L', "Host $host not responding\n");

    Use with callback
        # Message to callback
        my $rsp = {};
		$rsp->{data}->[0] = "Job did not run. \n";
	    xCAT::MsgUtils->message("D", $rsp, $::CALLBACK);

        my $rsp = {};
		$rsp->{error}->[0] = "No hosts in node list\n";
	    xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);

        my $rsp = {};
        $rsp->{node}->[0]->{name}->[0] ="mynode";
        $rsp->{node}->[0]->{data}->[0] ="mydata";
        xCAT::MsgUtils->message("N", $rsp, $callback);

         my $rsp = {};
		$rsp->{info}->[0] = "No hosts in node list\n";
	    xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);

        my $rsp = {};
		$rsp->{sinfo}->[0] = "No hosts in node list\n";
	    xCAT::MsgUtils->message("IS", $rsp, $::CALLBACK);


        my $rsp = {};
		$rsp->{warning}->[0] = "No hosts in node list\n";
	    xCAT::MsgUtils->message("W", $rsp, $::CALLBACK);

        my $rsp = {};
		$rsp->{error}->[0] = "Host not responding\n";
	    xCAT::MsgUtils->message("S", $rsp, $::CALLBACK);


        # Message to Syslog and callback
        my $rsp = {};
		$rsp->{error}->[0] = "Host not responding\n";
	    xCAT::MsgUtils->message("SE", $rsp, $::CALLBACK);

        # Message to Syslog and callback
        my $rsp = {};
		$rsp->{info}->[0] = "Host not responding\n";
	    xCAT::MsgUtils->message("SI", $rsp, $::CALLBACK);

        # Message to Log, Syslog and callback
		my $msg;
		$msg = "Host not responding\n";
	    xCAT::MsgUtils->message("LSI", $msg, $::CALLBACK);

        # Message to Log and callback
		my $msg;
		$msg = "Host not responding\n";
	    xCAT::MsgUtils->message("LI", $msg, $::CALLBACK);





    Comments:


    Returns:
       1 for internal error ( invalid input to the routine)



=cut

#--------------------------------------------------------------------------------

sub message
{

    # Process the arguments
    shift;    # get rid of the class name
    my $sev       = shift;
    my $rsp       = shift;
    my $call_back = shift;    # optional
    my $exitcode  = shift;    # optional

    # should be I,IS, D, E, S, SA,A ,LS, W , L,N
    #  or S(I, D, E, S, W, L,N)
    #
    # if  SA option need to split syslog messages from auditlog entry
    #
    my $newrsp;
    if (($sev eq 'SA') || ($sev eq 'A'))
    { # if SA ( syslog and auditlog) or A ( only auditlog)then need to pull first entry from $rsp
            # for syslog, to preserve old interface
        $newrsp = $rsp;
        if ($sev eq 'SA') {    # syslog and auditlog
            $rsp = $newrsp->{syslogdata}->[0];
        }
    }
    my $stdouterrf = \*STDOUT;
    my $stdouterrd = '';
    if ($sev =~ /[E]/)
    {
        $stdouterrf = \*STDERR;
        $stdouterrd = '1>&2';
    }

    # check for logging
    my $logging = 0;
    if ($sev =~ /[L]/)
    {

        # no log opened, we have an error
        if (!defined($::LOG_FILE_HANDLE))
        {
            if ($call_back)
            {

                # build callback structure
                my $newrsp;
                my $sevkey = 'error';
                my $err =
"Logging requested without setting up log by calling xCAT:MsgUtils->start_logging.\n";
                push @{ $newrsp->{$sevkey} }, $err;
                push @{ $newrsp->{errorcode} }, "1";
                $call_back->($newrsp);    # send message to daemon/Client.pm
                return 1;
            }
            else
            {
                print
"Logging requested without setting up log by calling xCAT:MsgUtils->start_logging.\n";
                return 1;
            }
        }

        else
        {

            $logging = 1;

        }
    }

    if ($sev eq 'V')
    {    # verbose should have been handled in calling routine
        $sev = "I";
    }
    if ($sev eq 'SV')
    {    # verbose should have been handled in calling routine
        $sev = "SI";
    }

    # Check that correct structure is filled in. If the data is not in the
    # structure corresponding to the $sev,  then look for it in "data"
    #TODO: this is not really right for a few reasons:  1) all the fields in the
    #		response structure are arrays, so can handle multiple lines of text.  We
    #		should not just be check the 0th element.  2) a cmd may have both error
    #		text and data text.  3) this message() function should just take in a plain
    #		string and put it in the correct place based on the severity.

    #
    # if a callback routine is provided
    #
    if ($call_back)
    {    # callback routine provided
        my $sevkey;
        if    ($sev =~ /D/)  { $sevkey = 'data'; }
        elsif ($sev =~ /N/)  { $sevkey = 'node'; }
        elsif ($sev =~ /IS/) { $sevkey = 'sinfo'; }
        elsif ($sev =~ /I/)  { $sevkey = 'info'; }
        elsif ($sev =~ /W/)  { $sevkey = 'warning'; }
        elsif ($sev =~ /E/)
        {
            $sevkey = 'error';
            if (!defined($exitcode))
            {
                $exitcode = 1;
            }    # default to something non-zero
        }
        else
        {

            # build callback structure
            my $newrsp;
            my $sevkey = 'error';
            my $err =
              "Invalid or no severity code passed to MsgUtils::message().\n";
            push @{ $newrsp->{$sevkey} }, $err;
            push @{ $newrsp->{errorcode} }, "1";
            $call_back->($newrsp);    # send message to daemon/Client.pm
            return 1;
        }

        # check if logging to xCAT log, must be handled
        # separately because message data is a simple string
        #
        if (!$logging)
        {
            if ($sevkey ne 'data')
            {
                if (!defined($rsp->{$sevkey}) || !scalar(@{ $rsp->{$sevkey} }))
                {    # did not pass the text in in the severity-specific field
                        # so fix it
                    if (defined($rsp->{data}) && scalar(@{ $rsp->{data} }))
                    {
                        push @{ $rsp->{$sevkey} }, @{ $rsp->{data} };

                        # assume they passed
                        # in the text in the data field instead
                        @{ $rsp->{data} } = ();    # clear out the data field
                    }
                }
            }

            # if still nothing in the array, there is nothing to print out
            if (!defined($rsp->{$sevkey}) || !scalar(@{ $rsp->{$sevkey} }))
            {
                return;
            }

            if (defined($exitcode))
            {
                push @{ $rsp->{errorcode} }, $exitcode;
            }
            $call_back->($rsp);    # send message to daemon/Client.pm
            @{ $rsp->{$sevkey} } =
              ();    # clear out the rsp structure in case they use it again
            @{ $rsp->{data} }      = ();
            @{ $rsp->{errorcode} } = ();
        }
        else         # logging
        {

            # write to log
            print $::LOG_FILE_HANDLE $rsp;

            # build callback structure
            my $newrsp;
            push @{ $newrsp->{$sevkey} }, $rsp;
            if ($exitcode)
            {
                push @{ $newrsp->{errorcode} }, $exitcode;
            }
            $call_back->($newrsp);    # send message to daemon/Client.pm

        }
    }

    else                              # no callback provided
    {
        if ($logging)
        {                             # print to local xcat log
            print $::LOG_FILE_HANDLE $rsp;
        }
        else
        {                             # print to stdout

            print $stdouterrf $rsp . "\n";    # print the message
        }
    }

    # is syslog option requested

    if ($sev =~ /S/)
    {
        # If they want this msg to also go to syslog, do that now
        eval {
            openlog("xcat", "nofatal,pid", "local4");
            if ($sev eq 'SE') {
                syslog("err", $rsp);
            } else {
                syslog("info", $rsp);
            }
            closelog();
        };
        my $errstr = $@;
        if ($errstr)
        {
            print $stdouterrf
              "Error: Unable to log to syslog: $errstr\n";
            print "$rsp\n";
        }
    }

    # if write to auditlog table requested, if not on service node
    if (xCAT::Utils->isMN()) {
        if (($sev eq 'SA') || ($sev eq 'A'))
        {
            require xCAT::Table;
            my $auditlogentry;
            my $tab = xCAT::Table->new("auditlog");
            if ($tab)
            {
                my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
                  localtime(time);

                my $currtime = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
                    $year + 1900, $mon + 1, $mday,
                    $hour, $min, $sec);

                $auditlogentry->{audittime}  = $currtime;
                $auditlogentry->{userid}     = $newrsp->{userid}->[0];
                $auditlogentry->{clientname} = $newrsp->{clientname}->[0];
                $auditlogentry->{clienttype} = $newrsp->{clienttype}->[0];
                $auditlogentry->{command}    = $newrsp->{command}->[0];
                $auditlogentry->{noderange}  = $newrsp->{noderange}->[0];
                $auditlogentry->{args}       = $newrsp->{args}->[0];
                $auditlogentry->{status}     = $newrsp->{status}->[0];

                my @ret = $tab->setAttribs(undef, $auditlogentry);
                if (@ret > 1)
                {
                    print $stdouterrf "Unable to open auditlog\n";
                    eval {
                        openlog("xcat", "nofatal,pid", "local4");
                        syslog("err", "Unable to write to auditlog");
                        closelog();
                    };
                }
                else
                {
                    my $DBname = xCAT::Utils->get_DBName;
                    if (!($DBname =~ /^SQLITE/)) {
                        $tab->commit;
                    }
                }
            }
            else
            {    # error
                print $stdouterrf "Unable to open auditlog\n";
                eval {
                    openlog("xcat", "nofatal,pid", "local4");
                    syslog("err", "Unable to open auditlog");
                    closelog();
                };

            }
        }
    }
    return;
}


#-----------------------------------------------------------------------------

=head3 error_message

  A wrap function for message. If $callback is not defined, send the log to
  syslog, otherwise, send error message to client. Print service host if runs
  on service node.

  Example:

          xCAT::MsgUtils->error_message($msg, $callback);
=cut

#-----------------------------------------------------------------------------
sub error_message
{
    shift;
    my $msg = shift;
    my $callback = shift;
    my $rsp;
    $rsp->{data}->[0] = $msg;
    if (!defined($callback)) {
        message(undef, "S", $rsp, undef);
        return;
    }
    if ($isSN && !$host) {
        my @hostinfo = xCAT::NetworkUtils->determinehostname();
        $host = $hostinfo[-1];
    }
    $rsp->{host} = $host if $host;
    message(undef, "E", $rsp, $callback);
}

#-----------------------------------------------------------------------------

=head3 info_message

  A wrap function for message. If $callback is not defined, send the log to
  syslog, otherwise, send info message to client. Print service host if runs
  on service node.

  Example:

          xCAT::MsgUtils->info_message($msg, $callback);
=cut

#-----------------------------------------------------------------------------
sub info_message
{
    shift;
    my $msg = shift;
    my $callback = shift;
    my $rsp;
    $rsp->{data}->[0] = $msg;
    if (!defined($callback)) {
        message(undef, "S", $rsp, undef);
        return;
    }
    if ($isSN && !$host) {
        my @hostinfo = xCAT::NetworkUtils->determinehostname();
        $host = $hostinfo[-1];
    }
    $rsp->{host} = $host if $host;
    message(undef, "I", $rsp, $callback);
}

#-----------------------------------------------------------------------------

=head3 warn_message

  A wrap function for message. If $callback is not defined, send the log to
  syslog, otherwise, send warning message to client. Print service host if runs
  on service node.

  Example:

          xCAT::MsgUtils->warn_message($msg, $callback);
=cut

#-----------------------------------------------------------------------------
sub warn_message
{
    shift;
    my $msg = shift;
    my $callback = shift;
    my $rsp;
    $rsp->{data}->[0] = $msg;
    if (!defined($callback)) {
        message(undef, "S", $rsp, undef);
        return;
    }
    if ($isSN && !$host) {
        my @hostinfo = xCAT::NetworkUtils->determinehostname();
        $host = $hostinfo[-1];
    }
    $rsp->{host} = $host if $host;
    message(undef, "W", $rsp, $callback);
}

#--------------------------------------------------------------------------------

=head2    xCAT Logging Routines
		  To use xCAT Logging follow the following sample

	      my $rc=xCAT::MsgUtils->start_logging("mylogname"); # create/open log
						 .
						 .
						 .
            # Message to Log and callback
	     	my $msg;
		    $msg = "Host not responding\n";
	        xCAT::MsgUtils->message("LI", $msg, $::CALLBACK);
						 .
						 .
            # Message to Log
	     	my $msg;
		    $msg = "Host not responding\n";
	        xCAT::MsgUtils->message("L", $msg);
						 .
						
	      my $rc=xCAT::MsgUtils->stop_logging(); # close log
			 	

=cut

#--------------------------------------------------------------------------------

=head3 start_logging

        Start logging messages to a logfile. Return the log file handle so it
        can be used for updates and to close the file when done logging
		using stop_logging.

        Arguments:
                $logfilename ( just name, path is by default /var/log/xcat)
        Returns:
                $::LOG_FILE_HANDLE
        Globals:
                $::LOG_FILE_HANDLE
        Error:
                $::NOK
        Example:
                xCAT:Utils->start_logging("logname");

=cut

#--------------------------------------------------------------------------------

sub start_logging
{
    my ($class, $logfilename) = @_;
    my ($cmd, $rc);
    my $xCATLogDir = "/var/log/xcat/";

    my $logfile = $xCATLogDir;
    $logfile .= $logfilename;
    xCAT::MsgUtils->backup_logfile($logfile);

    # create the log directory if it's not already there
    if (!-d $xCATLogDir)
    {
        $cmd = "mkdir -m 644 -p $xCATLogDir";
        $rc  = system("$cmd");
        if ($rc >> 8)
        {
            xCAT::MsgUtils->message('SE', "Error running $cmd.\n");
            return ($::NOK);
        }
    }

    # open the log file
    unless (open(LOGFILE, ">>$logfile"))
    {

        # Cannot open file
        xCAT::MsgUtils->message('SE', "Error opening $logfile.\n");
        return ($::NOK);
    }

    $::LOG_FILE_HANDLE = \*LOGFILE;
    $::LOG_FILE_NAME   = $logfile;

    # Make the file to be unbuffered
    binmode($::LOG_FILE_HANDLE, ":unix");

    # Print the program name and date to the top of the logfile
    my $sdate = `/bin/date`;
    chomp $sdate;
    my $program = $0;
    xCAT::MsgUtils->message('L', "$program:logging started $sdate.\n");

    return ($::LOG_FILE_HANDLE);
}

#--------------------------------------------------------------------------------

=head3 stop_logging

        Turn off message logging. Routine expects to have a file handle
        passed in via the global $::LOG_FILE_HANDLE.

        Arguments:

        Returns:
                $::OK
        Globals:
                $::LOG_FILE_HANDLE
        Error:
                none
        Example:
                MsgUtils->stop_logging();
        Comments:
                closes the logfile and undefines $::LOG_FILE_HANDLE
                even on error.

=cut

#--------------------------------------------------------------------------------

sub stop_logging
{
    my ($class) = @_;
    if (defined($::LOG_FILE_HANDLE))
    {

        # Print the date at the bottom of the logfile
        my $sdate = `/bin/date`;
        chomp $sdate;
        my $program = $0;
        xCAT::MsgUtils->message('L', "$program:logging stopped $sdate.\n");

        close($::LOG_FILE_HANDLE);
        undef $::LOG_FILE_HANDLE;
    }
    return $::OK;
}

#--------------------------------------------------------------------------------

=head3    backup_logfile

        Backup the current logfile. Move logfile to logfile.1.
		Shift all other logfiles
        (logfile.[1-3]) up one number. The original logfile.4 is removed as in a FIFO.

        Arguments:
                $logfile ( full path)
        Returns:
                $::OK
        Error:
                undefined
        Example:
                xCAT::MsgUtils->backup_logfile($logfile);

=cut

#--------------------------------------------------------------------------------

sub backup_logfile
{
    my ($class, $logfile) = @_;

    my ($logfile1) = $logfile . ".1";
    my ($logfile2) = $logfile . ".2";
    my ($logfile3) = $logfile . ".3";
    my ($logfile4) = $logfile . ".4";

    if (-f $logfile)
    {
        rename($logfile3, $logfile4) if (-f $logfile3);
        rename($logfile2, $logfile3) if (-f $logfile2);
        rename($logfile1, $logfile2) if (-f $logfile1);
        rename($logfile,  $logfile1);
    }
    return $::OK;
}

sub verbose_message
{
    shift;
    my $req  = shift;
    my $data = shift;
    if (!defined($req->{verbose})) {
        return;
    }
    my ($sec, $min, $hour, $mday, $mon, $yr, $wday, $yday, $dst) = localtime(time);
    my $time = sprintf "%04d%02d%02d.%02d:%02d:%02d", $yr + 1900, $mon + 1, $mday, $hour, $min, $sec;
    $data = "$time ($$) " . $data;
    if (defined($req->{callback})) {
        my %rsp;
        $rsp{data} = [$data];
        xCAT::MsgUtils->message("I", \%rsp, $req->{callback});
    } else {
        xCAT::MsgUtils->message("I", $data);
    }
}

#--------------------------------------------------------------------------------

=head3 trace

    Display different level trace message in syslog.

    Arguments:
        $verbose: indicate whether current command is with -V option. 1 is yes and 0 is no.
        $level: the level of trace message, can be one of "I","W","E","D","i","w","e","d".
            "I" or "i": means information level.
            "W" or "w": means warning level.
            "E" or "e": means error level.
            "D" or "d": means debug level.

            As long as the trace subroutine is called, "information", "warning" and "error" level message will be displayed in syslog.
            The "debug" level message is displayed in syslog only when any one of the below two conditions is true
            1. The current command with -V option. i.e. $verbose=1.
            2. The xcatdebugmode, which is an attribute of site table, equals 1.

            If $level is not any one of "I","W","E","D","i","w","e","d", the trace subroutine do nothing.

        $logcontent: the specific message wanted to be displayed in syslog
    Returns:
        none
		
    Error:
        none
		
    Note:
        Label "xcat" and trace message level, such as "INFO", "ERR", "WARNING" AND "DEBUG", will be added before real trace message automatically. It's convenient to filter in syslog.

    Example:
        xCAT::MsgUtils->trace(1,"D","this is debug trace message");
        xCAT::MsgUtils->trace(0,"i","this is information trace message");
        xCAT::MsgUtils->trace(0,"E","this is error trace message");
=cut

#--------------------------------------------------------------------------------
sub trace() {
    shift;
    my $verbose    = shift;
    my $level      = shift;
    my $logcontent = shift;

    my $prefix = "";
    if (($level eq "E") || ($level eq "e")) { $prefix = "ERR"; }
    if (($level eq "W") || ($level eq "w")) { $prefix = "WARNING"; }
    if (($level eq "I") || ($level eq "i")) { $prefix = "INFO"; }
    if (($level eq "D") || ($level eq "d")) { $prefix = "DEBUG"; }

    return unless ($prefix); #unknown level, do nothing.

    if (($verbose == 0) && ($prefix eq "DEBUG")) {
        my @tmp = xCAT::TableUtils->get_site_attribute("xcatdebugmode");
        my $xcatdebugmode = $tmp[0];
        return unless (($xcatdebugmode == 1) || ($xcatdebugmode == 2));
    }

    eval {
        openlog("xcat", "nofatal,pid", "local4");
        syslog("$prefix", $logcontent);
        closelog();
    };
    if ($@) {
        print "Error: Unable to log to syslog: $@\n";
        print "$prefix . $logcontent\n";
    }

}

#------------------------------------------------------------------

=head3  _perf_log
  Libary function to write the perf log. Do not use it directly.
=cut

#-----------------------------------------------------------------
sub _perf_log
{
    my $type = shift;
    my $msg = shift;
    my $fh;
    my $ret = open($fh, '>>', PERF_LOG);
    if (!$ret) {
        xCAT::MsgUtils->message("S", "Open perf log file error: $!");
    }
    my $line = localtime()."\t$type\t$msg\n";
    print $fh $line;
    close $fh;
}

#------------------------------------------------------------------

=head3  perf_log_info
  Trace information for perf
  Argument:
           $msg (trace message)
=cut

#-----------------------------------------------------------------
sub perf_log_info
{
    shift;
    my $msg = shift;
    _perf_log('info', $msg);
}

#------------------------------------------------------------------

=head3  perf_log_process
  Trace process information for perf
  Arguments:
            $process_type (immediate, plugin, etc)
            $req (xcat reqeust)
            $msg (additional message, optional)
=cut

#-----------------------------------------------------------------
sub perf_log_process
{
    shift;
    my ($process_type, $req, $msg, $pid) = @_;
    if (defined($req)) {
        my $command = $req->{command}->[0];
        _perf_log($process_type, "$$\t$command\t$msg");
    } else {
        _perf_log($process_type, "$pid\t$msg");
    }
}

#------------------------------------------------------------------

=head3  report_node_error
  Using the passing callback to report node level error
  Arguments:
            $cb   (callback reference)
            $node (node name)
            $msg  (error message)
=cut

#-----------------------------------------------------------------
sub report_node_error
{
    shift;
    my ($cb, $node, $msg) = @_;

    my $rsp;
    $rsp->{node}->[0]->{name}->[0]      = $node;
    $rsp->{node}->[0]->{error}->[0]     = $msg;
    $rsp->{node}->[0]->{errorcode}->[0] = 1;
    $cb->($rsp);
}

1;

