blob: 7aa32ff4139499dee67cb8eb8c08da8566a4ace0 [file] [log] [blame]
//**********************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@
#include "errno.h"
#include "string.h"
#include "NALog.h"
#include "NAStdioFile.h"
#include "CatSQLShare.h"
#define FILE_COULD_NOT_BE_ACCESSED 20356 //Error 20356 - File cannot be accessed.
#define TIME_KWORD "Time: "
#define TIME_KWORD_LENGTH 6
#define PROCESS_FILENAME_LENGTH 48 // Length of process name
#define PROCESS_KWORD "Process: "
#define PROCESS_KWORD_LENGTH 9
#define HEADER_LEN 16
// This macro is called whenever a failure occurs and we want to stop logging
#define TURNOFFLOGGING \
m_bLoggingEnabled = FALSE; \
m_bLogFailure = TRUE;
// This macro is called in DEBUG code to return an error code 20356 when the log
// cannot be accessed. It's up to the caller to handle error 20356.
// In RELEASE code, the logging attribute it turned off
// and the operation continues.
#ifdef _DEBUG
#define THROWERROREXCP \
if (GetLastError() != ENOERR) { \
return FILE_COULD_NOT_BE_ACCESSED; \
}
#endif
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
CNALogfile::CNALogfile()
: m_bPrintTime (TRUE),
m_bPrintProcessID (TRUE),
m_bLoggingEnabled (FALSE),
m_bFlushAtLogTime (TRUE),
m_bPrintLogHeader (TRUE),
m_bDashToUnderscore (FALSE),
m_eLoggingLevel (CNALogfile::eLEVEL1),
m_bClearLogAtOpen (FALSE),
m_bLogFailure (FALSE),
m_pLogName (NULL)
{
// Check for envvar: SQLMX_UTIL_LOGNAME
// If the envvar defined, get the log name and log attributes
// from the variable
char * pLogfileInfo = getenv( "SQLMX_UTIL_LOGNAME" );
if (pLogfileInfo != NULL)
{
ScanEnvVar (pLogfileInfo);
// Set up to indicate that the envvar was processed successfully and
// we are ready to start logging
m_bLoggingEnabled = TRUE;
}
}
// -------------------------------------------------------------------------
// Destructor
// -------------------------------------------------------------------------
CNALogfile::~CNALogfile()
{
if (m_pLogName)
delete [] m_pLogName;
}
// -------------------------------------------------------------------------
// Helper: ScanEnvVar
//
// This method scans the contents of the environment variable:
// SQLMX_LOGNAME (pEnvVarInfo)
// and sets up the requested information in the CNALog class
// -------------------------------------------------------------------------
void
CNALogfile::ScanEnvVar ( char *pEnvVarInfo )
{
Int32 length = 0;
char *pStartOfToken = pEnvVarInfo;
char *pNextToken = GetNextToken (pStartOfToken, length);
while (pNextToken)
{
NAString token (pNextToken, length);
token.toLower();
if (token == "clear")
m_bClearLogAtOpen = TRUE;
else if (token == "1")
m_eLoggingLevel = CNALogfile::eLEVEL1;
else if ( token == "2")
m_eLoggingLevel = CNALogfile::eLEVEL2;
else if (token == "3")
m_eLoggingLevel = CNALogfile::eLEVEL3;
else
{
m_pLogName = new char [token.length() + 1];
strcpy (m_pLogName, token);
}
pStartOfToken = pNextToken + length;
pNextToken = GetNextToken (pStartOfToken, length);
}
}
// ------------------------------------------------------------------------
// Helper: GetNextToken
//
// This method extract the next token from the string requested through
// the environment variable (SQLMX_LOGNAME)
//
// It returns a pointer to the start of the token and its length
// ------------------------------------------------------------------------
char *
CNALogfile::GetNextToken (char *pStrToScan, Int32 &length)
{
char *pStartOfToken = pStrToScan;
// If we are at the end of the string, return NULL.
if (pStartOfToken[0] == '\0')
return NULL;
// if the first character is the comma, point past it.
if (pStartOfToken[0] == ',')
pStartOfToken++;
char *pEndOfStr = pStartOfToken + strlen(pStartOfToken);
// Take care of any leading blanks before the options start
while(pStartOfToken < pEndOfStr && *pStartOfToken == ' ')
pStartOfToken++;
if (pStartOfToken >= pEndOfStr)
return NULL;
char *pNextComma = strchr (pStartOfToken, ',');
if (pNextComma)
length = pNextComma - pStartOfToken;
else
length = (Int32)strlen(pStartOfToken);
return pStartOfToken;
}
// ----------------------------------------------------------------------
// Helper: GetTimeToLog
//
// This method gets a time value: Time: Thu Jun 9 08:30:49 2005
// to write to the log
// ----------------------------------------------------------------------
NAString
CNALogfile::GetTimeToLog(void)
{
Int32 length = CTIME_LENGTH + TIME_KWORD_LENGTH + 2;
char timeBuf[CTIME_LENGTH + TIME_KWORD_LENGTH + 2];
char *pTimeBuf = (char *)&timeBuf;
strcpy (pTimeBuf, TIME_KWORD);
pTimeBuf = pTimeBuf + strlen (TIME_KWORD);
GetTimeString(pTimeBuf, FALSE);
strcat(pTimeBuf, " ");
NAString timeStr (timeBuf);
return timeStr;
}
// ----------------------------------------------------------------------
// Helper: GetProcessToLog
//
// This method gets a process value: Process: \SQUAW.$:1:437:130336604
// to write to the log
// ----------------------------------------------------------------------
NAString
CNALogfile::GetProcessToLog()
{
char processIdStrBuf[101];
size_t processIdStrLen = 0;
Int32 rtnCode = -1;
processIdStrBuf[0] = '\0';
rtnCode = SqlShareLnxGetMyProcessIdString(processIdStrBuf, // char * outBuf
100, // size_t outBufMaxLen
&processIdStrLen); // size_t * computedProcessIdStrLen
// CAT_ASSERT (rtnCode == XZFIL_ERR_OK);
processIdStrBuf[processIdStrLen] = '\0';
NAString processStr(processIdStrBuf);
return processStr;
}
// ----------------------------------------------------------------------
// Helper: ConvertDashToUnderscore
//
// This helper method convert any dash in log filename into underscore
// ----------------------------------------------------------------------
void
CNALogfile::ConvertDashToUnderscore(char *pLogName)
{
Int32 pos = 0;
while (pLogName[pos] != '\0')
{
if (pLogName[pos] == '-')
pLogName[pos] = '_';
pos++;
}
}
// ----------------------------------------------------------------------
// Method: generic Open
//
// If the open fails, logging is turned off
// In debug mode, an exception is thrown
// ----------------------------------------------------------------------
Int32
CNALogfile::Open( void )
{
// If file already opened, return
if (IsOpen())
return 0;
// If the logname was not specified, go generate one
if (!m_pLogName)
{
m_pLogName = new char [CTIME_LENGTH + 4 + 1];
strcpy(m_pLogName, "LOG-");
GetTimeString(m_pLogName+4, TRUE);
}
if (m_bDashToUnderscore)
{
ConvertDashToUnderscore(m_pLogName);
}
// Open the file for read access to see if it exists and if it exists
// contains a log header
CNAStdioFile::EOpenMode stdioMode = CNAStdioFile::eRead;
try
{
NAString headerStr ("*************** ");
if (inherited::Open(m_pLogName, stdioMode))
{
// This is an existing file, go ahead and read the first row from the
// file. It must be a log header record. If not, then turn off logging
// and exit.
// The header looks like:
// *************** Time: <t> Process: <p> Log opened ***************
// <t> ::= Thu Jun 9 08:30:49 2005
// <p> ::= \SQUAW.$:1:437:130336604
NAString rowRead;
rowRead.append(' ', HEADER_LEN);
if (ReadString((char *)rowRead.data(), HEADER_LEN))
{
// Found data in the file
// But the data is not a header row, turn off logging
if ( !isHeaderString((char *)rowRead.data()) )
{
TURNOFFLOGGING
m_lastError = 2;
inherited::Close();
#ifdef _DEBUG
THROWERROREXCP
#endif
return 0;
}
}
inherited::Close();
}
// Go ahead and open the log for write/append access
stdioMode =
m_bClearLogAtOpen ? CNAStdioFile::eWrite : CNAStdioFile::eAppend;
if (!inherited::Open(m_pLogName, stdioMode))
{
TURNOFFLOGGING
#ifdef _DEBUG
THROWERROREXCP
#endif
}
m_bLoggingEnabled = TRUE;
// Log file is good, write the log header
headerStr += GetTimeToLog();
headerStr += GetProcessToLog();
headerStr += (" Log opened ***************");
// The header line should not have the time and process ID prepended
// store the current values, turn off the options, log the row, then
// reset the time and process ID values.
NABoolean printProcessId = GetPrintProcessID();
NABoolean printTime = GetTimePrint();
SetTimePrint (FALSE);
SetPrintProcessID (FALSE);
if (m_bPrintLogHeader)
Write (headerStr, headerStr.length());
#ifdef _DEBUG
// Try logging different amounts of data
char * pLogSizeLimit = getenv( "LOG_SIZE_LIMIT" );
if (pLogSizeLimit)
{
NAString str
("asdfghjkl;qwertyuiopzxcvbnm,./asdfghjkl;qwertyuiopzxcvbnm,./");
NAString newstr;
Int32 upperlimit = atoi(pLogSizeLimit);
if (upperlimit > 0)
{
for (Int32 i = 0; i < upperlimit; i++)
newstr += str;
if (newstr.length() > 0)
{
newstr += "\n";
Int32 retcode = inherited::WriteString(newstr);
NAString numWritten;
numWritten = LongToNAString((Lng32)retcode);
numWritten += ": Number of bytes written \n";
inherited::WriteString(numWritten);
}
}
}
#endif
SetTimePrint(printTime);
SetPrintProcessID (printProcessId);
}
// The CNAStdioFile Open method could throw an exception.
// Just catch it, turn off logging, and return
catch (...)
{
TURNOFFLOGGING
#ifdef _DEBUG
THROWERROREXCP
#endif
return 0;
}
return 0;
}
// ----------------------------------------------------------------------
// Method::Open
//
// This method set the log name and open mode in the class structures
// and calls the generic Open method.
// ----------------------------------------------------------------------
Int32
CNALogfile::Open( char *pLogName, CNAStdioFile::EOpenMode mode )
{
// If log name has already been specified, delete it
// Caller may want to open a different file
if (m_pLogName)
{
Close();
delete [] m_pLogName;
}
// Save the log name
m_pLogName = new char [strlen(pLogName) + 1];
strcpy (m_pLogName, pLogName);
// Set clearLogAtOpen
m_bClearLogAtOpen = (mode == CNAStdioFile::eAppend) ? FALSE : TRUE;
// Open file
return Open();
}
Int32
CNALogfile::Open( NAString logName, CNAStdioFile::EOpenMode mode )
{
return Open((char *) logName.data(), mode);
}
// ----------------------------------------------------------------------
// Method: Close
//
// Errors are ignored
// ----------------------------------------------------------------------
void CNALogfile::Close()
{
if (IsOpen())
inherited::Close();
m_bLoggingEnabled = FALSE;
}
// ----------------------------------------------------------------------
// Method: Write
//
// If the write was not successful, FALSE is returned and logging is
// turned off.
// FALSE is also returned if Write is called and logging is not activated
// or the log file is not opened
// ----------------------------------------------------------------------
NABoolean CNALogfile::Write(const char * pBuffer, Lng32 buflen)
{
if (!IsLoggingEnabled())
return FALSE;
// If for some reason logging is enabled but the log operation failed
// don't try to log again
if (GetLogFailure())
{
SetLoggingEnabled (FALSE);
return FALSE;
}
// Construct the row to log
NAString row;
if (m_bPrintTime)
row += GetTimeToLog();
if (m_bPrintProcessID)
row += GetProcessToLog();
// print line
// rsm - I am not sure if this is still required
if (buflen > 0)
{
// Strings such as error msgs come here as "ERROR[nn] Msg text.\r\n".
// The WINNT fwrite takes that final \n and replaces it with \r\n,
// so we end up with "ERROR[nn] Msg text.\r\r\n"
// which appears in the log as "ERROR[nn] Msg text.^M"
// and screws up MKS diff utility (on regressions e.g.).
//
// And yes, it is safe to cast away const-ness to do this;
// we are only overwriting *existing* chars in the buffer
// *and* if this if-test is true the buffer contents must be
// coming from a run-time rather than a static/pure/read-only/const data
//
if (buflen > 1 && pBuffer[buflen-1] == '\n' && pBuffer[buflen-2] == '\r')
{
char *bufxxx = (char *)pBuffer;
bufxxx[buflen-2] = '\n';
bufxxx[buflen-1] = '\0';
buflen--;
}
}
// Add the buffer;
row += pBuffer;
row += "\n";
if (row.length() > 0)
{
Int32 retcode = inherited::WriteString(row);
if (!retcode)
{
TURNOFFLOGGING;
#ifdef _DEBUG
THROWERROREXCP
#endif
return FALSE;
}
}
return TRUE;
}
// ----------------------------------------------------------------------
// Method: Log
//
// Log a message to the current log file
// ----------------------------------------------------------------------
Int32
CNALogfile::Log (const char *pBuffer, ELoggingLevel level)
{
if (IsLoggingEnabled() && m_eLoggingLevel >= level)
{
#ifndef _DEBUG
// Only log LEVEL3 in debug mode
if (level == CNALogfile::eLEVEL3)
return 0;
#endif
// If the write wrote something - flush the buffer
if (Write (pBuffer, (Lng32)strlen(pBuffer)))
{
if (Flush() != ENOERR)
{
TURNOFFLOGGING;
#ifdef _DEBUG
THROWERROREXCP
#endif
}
}
}
return 0;
}
NABoolean
CNALogfile::isHeaderString (const char *strHeader)
{
NAString header ("*************** ");
NAString headerTemp = NAString(strHeader, HEADER_LEN);
if (headerTemp == header)
{
return TRUE;
}
return FALSE;
}