
/** @file  psLogMsg.c
 *  @brief Procedures for logging messages.
 *  \ingroup LogTrace
 *
 *  This file contains code for setting message log levels, message log
 *  formats, message log destinations, and for generating the messages
 *  themselves.
 *  @ingroup LogTrace
 *
 *  @author Robert Lupton, Princeton University
 *  @author GLG, MHPCC
 *
 *  @version $Revision: 1.52 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-09-20 02:43:53 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

/*****************************************************************************
NOTES: currently, the prototype code has the following global variables:
    static psS32 p_psGlobalLogDest;
    static psS32 p_psGlobalLogLevel;
    static psS32 p_psLogTime;
    static psS32 p_psLogHost;
    static psS32 p_psLogLevel;
    static psS32 p_psLogName;
    static psS32 p_psLogMsg;
 *****************************************************************************/
#include "config.h"

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>

#include "psLogMsg.h"
#include "psError.h"
#include "psTrace.h"

#include "psErrorText.h"

#define MIN_LOG_LEVEL 0
#define MAX_LOG_LEVEL 9

#define MAX_LOG_LINE_LENGTH 256

static FILE *logDest = (FILE *) 1;      // flag to initialize to stderr before using.
static int logFD = 0;
static psS32 globalLogLevel = PS_LOG_INFO;        // log all messages at this or above
static psBool logTime = true;     // Flag to include time info
static psBool logHost = true;     // Flag to include host info
static psBool logLevel = true;    // Flag to include level info
static psBool logName = true;     // Flag to include name info
static psBool logMsg = true;      // Flag to include message info

/*****************************************************************************
psLogSetLevel(): Set the current log level and return old level.
Input:
 level (psS32): the new log level.
Output:
 The old log level.
 *****************************************************************************/
int psLogSetLevel(int level)
{
    // Save old global log level for changing it.
    psS32 oldLevel = globalLogLevel;

    if ((level < MIN_LOG_LEVEL) || (level > MAX_LOG_LEVEL)) {
        psLogMsg("logmsg", PS_LOG_WARN, "Attempt to set invalid logMsg level: %d", level);
        level = (level < MIN_LOG_LEVEL) ? MIN_LOG_LEVEL : MAX_LOG_LEVEL;
    }
    // Set new global log level
    globalLogLevel = level;

    // Return old global log level
    return oldLevel;
}

int psLogGetLevel()
{
    return (globalLogLevel);
}

/*****************************************************************************
psLogSetDestination(): sets the log message destination.
Input:
 dest (psS32): the new log destination
Return:
 An psBool: TRUE if successful.
 *****************************************************************************/
bool psLogSetDestination(int fd)
{
    FILE *fp;
    // if logDest has not been initialized, do so before using it
    if (logDest == (FILE *) 1) {
        logDest = stderr;
        logFD = 2;
    }
    if (fd == -1)
        return false;

    if ( fd == 1 ) {
        //        fp = stdout;
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        logDest = stdout;
        logFD = 1;
    } else if (fd == 2) {
        //        fp = stderr;
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        logDest = stderr;
        logFD = 2;
    } else if (fd == 0) {
        //        fp = NULL;
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        logDest = NULL;
        logFD = 0;
        //        return true;
    } else if (fd > 2) {
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        fp = fdopen(fd, "w");
        logDest = fp;
        logFD = fd;
        //        fclose(fp);
        //        return true;
    }

    return true;

}

int psLogGetDestination(void)
{
    if (logDest == (FILE *) 1) {
        logFD = 1;
    }
    return (logFD);
}


/*****************************************************************************
psLogSetFormat(): Set the format of psLogMsg output.  More precisely,
    provide a string consisting of the letters {H (host), L (level), M
    (message), N (name), T (time)}.  The default is "HLMNT".  This string
    determines whether or not they associated type of information will be
    included in message logs.  It does not determine the order in which that
    information will appear (that order is fixed).
 
Input:
    fmt: a string specifying the format.
Return:
    NULL.
 *****************************************************************************/
bool psLogSetFormat(const char *format)
{
    // assume none.
    logHost = false;
    logLevel = false;
    logMsg = false;
    logName = false;
    logTime = false;

    // if fmt is NULL, no logging is desired.
    if (format == NULL) {
        return false;
    }

    // XXX: What is the purpose of this conditional.
    if (strlen(format) == 0) {
        format = "THLNM";
    }
    // Step through each character in the format string.  For each letter
    // in that string, set the appropriate logging.

    for (const char *ptr = format; *ptr != '\0'; ptr++) {
        switch (*ptr) {
        case 'H':
        case 'h':
            logHost = true;
            break;
        case 'L':
        case 'l':
            logLevel = true;
            break;
        case 'M':
        case 'm':
            logMsg = true;
            break;
        case 'N':
        case 'n':
            logName = true;
            break;
        case 'T':
        case 't':
            logTime = true;
            break;
        default:
            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                    PS_ERRORTEXT_psLogMsg_UNKNOWN_KEY, *ptr);
            return false;
        }
    }

    // XXX: If one must at least log error messages, why don't we set logMsg = true here?
    if (!logMsg) {
        psTrace("utils.logMsg", 1, "You must at least log error messages (You chose \"%s\")", format);

    }
    return true;
}

int psMessageDestination (const char *dest)
{
    char protocol[5];
    char location[257];
    if (logDest == (FILE *) 1) {
        logDest = stderr;
    }

    if (dest == NULL || strcmp(dest, "none") == 0) {
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        logDest = NULL;
        return 0;
    }
    if (sscanf(dest, "%4s:%256s", protocol, location) < 2) {
        psError(PS_ERR_LOCATION_INVALID, true,
                PS_ERRORTEXT_psLogMsg_DESTINATION_MALFORMED,
                dest);
        return -1;
    }
    if (strcmp(protocol, "dest") == 0) {
        if (strcmp(location, "stderr") == 0) {
            if (logDest != NULL && logDest != stderr && logDest != stdout) {
                fclose(logDest);
            }
            logDest = stderr;
            return 2;
        }
        if (strcmp(location, "stdout") == 0) {
            if (logDest != NULL && logDest != stderr && logDest != stdout) {
                fclose(logDest);
            }
            logDest = stdout;
            return 1;
        }
        psError(PS_ERR_LOCATION_INVALID, true, PS_ERRORTEXT_psLogMsg_DEST_LOCATION_INVALID,
                location);
        return -1;
    } else if (strcmp(protocol, "file") == 0) {
        //        FILE *file = fopen(location, "w");
        int fileD = creat(location, 0666);

        if (fileD == 0) {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psLogMsg_OPEN_FILE_FAILED,
                    location);
            return -1;
        }
        if (logDest != NULL && logDest != stderr && logDest != stdout) {
            fclose(logDest);
        }
        logDest = fdopen(fileD, "w");
        return fileD;
    }
    return -1;
}

#ifndef HOST_NAME_MAX                // should be in limits.h
#define HOST_NAME_MAX 256
#endif // #ifndef HOST_NAME_MAX

/*****************************************************************************
psVLogMsg(): This routine sends the message, which is a printf style string
specified in the "..." argument, to the current message log destination with
the severity specified by the "level" argument.
 Input:
   name
   level
   fmt
   ap
 *****************************************************************************/
void psLogMsgV(const char *name, int level, const char *format, va_list ap)
{
    static psS32 first = 1;       // Flag for calling gethostname()
    static char hostname[HOST_NAME_MAX + 1];

    // Buffer for hostname.
    char clevel = 0;            // letter-name for level
    char head[MAX_LOG_LINE_LENGTH + 2]; // the added two are for the ending | and \0
    char *head_ptr = head;      // where we've got to in head
    psS32 maxLength = MAX_LOG_LINE_LENGTH;
    time_t clock = time(NULL);  // The current time.
    struct tm *utc = gmtime(&clock);    // The current gm time.

    // if logDest has not been initialized, do so before using it
    if (logDest == (FILE *) 1) {
        logDest = stderr;
    }
    // If logging is off, or if the level is too high, return immediately.
    if ((level > globalLogLevel) || (logDest == NULL)) {
        return;
    }
    // If I have not been here yet, determine my hostname and save it.
    if (first) {
        first = 0;
        gethostname(hostname, HOST_NAME_MAX);
    }

    switch (level) {
    case PS_LOG_ABORT:
        clevel = 'A';
        break;

    case PS_LOG_ERROR:
        clevel = 'E';
        break;

    case PS_LOG_WARN:
        clevel = 'W';
        break;

    case PS_LOG_INFO:
        clevel = 'I';
        break;

    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
        clevel = level + '0';
        break;

    default:
        psTrace("utils.logMsg", 2, "Invalid logMsg level: %d (%s)\n", level, format);
        level = (level < 0) ? 0 : 9;
        clevel = level + '0';
        break;
    }

    // Create the various log fields...
    if (logTime) {
        maxLength -= snprintf(head_ptr, maxLength, "%4d:%02d:%02d %02d:%02d:%02dZ",
                              utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday,
                              utc->tm_hour, utc->tm_min, utc->tm_sec) - 1;
        head_ptr += strlen(head_ptr);
    }
    // Hostname should be 20 characters.
    if (logHost) {
        if (head_ptr > head) {
            *head_ptr++ = '|';
        }
        maxLength -= snprintf(head_ptr, maxLength, "%-20s", hostname);
        head_ptr += strlen(head_ptr);
    }
    if (logLevel) {
        if (head_ptr > head) {
            *head_ptr++ = '|';
        }
        maxLength -= snprintf(head_ptr, maxLength, "%c", clevel);
        head_ptr += strlen(head_ptr);
    }
    if (logName) {
        if (head_ptr > head) {
            *head_ptr++ = '|';
        }
        maxLength -= snprintf(head_ptr, maxLength, "%s", name);

        head_ptr += strlen(head_ptr);
    }

    if (head_ptr > head) {
        *head_ptr++ = '\n';
    } else if (!logMsg) {                  // no output desired
        return;
    }
    *head_ptr = '\0';

    fputs(head, logDest);
    if (logMsg) {
        char msg[1024];
        char* msgPtr;
        vsnprintf(msg,1024, format, ap);  // create message

        // detect multiple lines in message and indent each line by 4 spaces.
        char* line = strtok_r(msg,"\n",&msgPtr);
        while (line != NULL) {
            fprintf(logDest,"    %s\n",line);
            line = strtok_r(NULL,"\n",&msgPtr);
        }
    } else {
        fputc('\n', logDest);
    }
}

/*****************************************************************************
psLogMsg(): This routine sends the message, which is a printf style string
specified in the "..." argument, to the current message log destination with
the severity specified by the "level" argument.
 
Input:
  name: Indicates the source of this log message.
  level: The severity of this log message.
  fmt: The printf-stype formatted string, followed by the arguments
        to that string.
  ... The arguments to the above printf-style string.
 
Return:
   NULL
 *****************************************************************************/
void psLogMsg(const char *name, int level, const char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    psLogMsgV(name, level, format, ap);
    va_end(ap);
}
