/*++ Copyright (c) 1995 Microsoft Corporation Module Name : ilogcls.cxx Abstract: This file contains member functions for Logging classes: INET_BASIC_LOG and INET_FILE_LOG Author: Murali R. Krishnan ( MuraliK ) 3-Feb-1995 Environment: User Mode -- Win32 Project: Internet Services Common DLL Functions Exported: INET_BASIC_LOG::INET_BASIC_LOG() INET_BASIC_LOG::SetServerName() INET_BASIC_LOG::SetLogRecordFormat() INET_BASIC_LOG::LogInformation() INET_FILE_LOG::INET_FILE_LOG() INET_FILE_LOG::~INET_FILE_LOG() INET_FILE_LOG::LogInformation() INET_FILE_LOG::GetConfig() Revision History: MuraliK 15-May-1995 Extended LogInformation structure + file names changed to use sequence numbers. MuraliK 07-Jun-1995 Week numbers calculated differently. MuraliK 28-Jun-1995 Added ANSI API for LogInformation() MuraliK 26-Feb-1996 speed up formatting and improve file buffering --*/ /************************************************************ * Include Headers ************************************************************/ # include # include "inetlog.h" # include "ilogcls.hxx" /************************************************************ * Symbolic Constants ************************************************************/ # define DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A (',') const CHAR G_PSZ_DELIMITER[3] = { DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A, ' ', '\0'}; # define LEN_PSZ_DELIMITER ( sizeof(G_PSZ_DELIMITER)/sizeof(CHAR) - 1) inline VOID CopyLogDelimiter( IN CHAR * pchBuffer) { lstrcpyA( pchBuffer, G_PSZ_DELIMITER); } // CopyLogDelimiter() # define DEFAULT_LOG_FILE_NAME_W L"inetsvcs" # define DEFAULT_NCSA_FILE_NAME_W L"NCSA" # define DEFAULT_LOG_FILE_EXTENSION_W L"log" // // For the present the data written to file is always ANSI // so undefine the UNICODE output flag // # ifdef INTERNET_LOG_IN_UNICODE # undef INTERNET_LOG_IN_UNICODE # endif // // lstrcpyn() has a bug that disallows the copy of last character. // This is a bug as per the documentation. till that is fixed use following // # undef LSTRCPYN_DEBUGGED # define DEFAULT_SERVER_NAME_W L"InternetServer" # define DEFAULT_LOG_RECORD_FORMAT_W L"Internet Standard Log" # define DEFAULT_LOG_RECORD_FORMAT_TYPE InternetStdLogFormat # define PSZ_UNKNOWN_FIELD_W L"-" # define PSZ_UNKNOWN_FIELD_A "-" // // class static data // WCHAR * INET_BASIC_LOG::sm_pwszDefaultUser = L"-"; CHAR * INET_BASIC_LOG::sm_pszDefaultUser = "-"; extern TCHAR * s_rgchMonths[]; inline BOOL IsEmptyStr( IN LPCWSTR psz) { return ( psz == NULL || *psz == L'\0'); } inline BOOL IsEmptyStr(IN LPCSTR psz) { return ( psz == NULL || *psz == '\0'); } /************************************************************ * Functions ************************************************************/ static BOOL IsBeginningOfNewPeriod( IN INETLOG_PERIOD ilPeriod, IN LPSYSTEMTIME pstCurrentFile, IN LPSYSTEMTIME pstNow); VOID CopyUnicodeStringToBuffer( OUT WCHAR * pwchBuffer, IN DWORD cchMaxSize, IN LPCWSTR pwszSource) /* copies at most cbMaxSize-1 characters from pwszSource to pwchBuffer */ { DBG_ASSERT( pwszSource != NULL); DWORD cchLen = lstrlenW( pwszSource); if ( cchLen >= cchMaxSize) { DBGPRINTF( ( DBG_CONTEXT, "Long String ( %d chars) %ws given." " Truncating to %d chars\n", cchLen, pwszSource, cchMaxSize - 1)); // There is a bug in the lstrcpyn. hence need to work around it. #ifndef LSTRCPYN_DEBUGGED cchLen = cchMaxSize - 2; # else cchLen = cchMaxSize -1; # endif } #ifndef LSTRCPYN_DEBUGGED lstrcpynW( pwchBuffer, pwszSource, cchLen + 1); # else lstrcpynW( pwchBuffer, pwszSource, cchLen ); # endif return; } // CopyUnicodeStringToBuffer() /************************************************** * Member Functions of class INET_BASIC_LOG **************************************************/ INET_BASIC_LOG::INET_BASIC_LOG( IN LPCWSTR pszServiceName, IN EVENT_LOG * pEventLog) /*++ This function creates a new INET_BASIC_LOG object. It obtains the name of the server by using GetCompterName() --*/ : m_logFormat ( DEFAULT_LOG_RECORD_FORMAT_TYPE), m_pEventLog ( pEventLog) { CopyUnicodeStringToBuffer( m_rgchServiceName, MAX_SERVICE_NAME_LEN, pszServiceName); // // Set the computer name to be some default value. // Actual value will be set by INET_BASIC_LOG::SetServerName() // lstrcpyW( m_rgchServerName, DEFAULT_SERVER_NAME_W); lstrcpyW( m_rgchLogRecordFormat, DEFAULT_LOG_RECORD_FORMAT_W); // includes 2 ulonglongs, 5 dwords, spaces etc. m_cchMinForLogRecord = ( 80 + lstrlenW(m_rgchServerName) + lstrlenW( m_rgchServiceName)); // cached service-server name string m_cchRgchSSName = wsprintfA(m_rgchServiceServerName, "%ws%c %ws%c ", m_rgchServiceName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A, m_rgchServerName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A ); wsprintfW(m_rgwchServiceServerName, L"%ws%c %ws%c ", m_rgchServiceName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A, m_rgchServerName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A ); // set the time zone offset TIME_ZONE_INFORMATION tzTimeZone; DWORD dwError = GetTimeZoneInformation(&tzTimeZone); switch (dwError) { case TIME_ZONE_ID_UNKNOWN: lstrcpyA( m_szGMTOffset, ""); break; default: { float fOffset = (float)tzTimeZone.Bias/(float)60; int nOffset = tzTimeZone.Bias/60; if ((tzTimeZone.Bias*tzTimeZone.DaylightBias)<0) { lstrcpyA(m_szGMTOffset,"-"); } else { lstrcpyA(m_szGMTOffset,"+"); } // set up the "+0800" or "-0800" NCSA information CHAR szTmp[MAX_PATH]; wsprintfA(szTmp,"%02ld",abs(nOffset)); lstrcatA(m_szGMTOffset,szTmp); int nMinOffset = abs((int)((fOffset-nOffset)*60)); wsprintf(szTmp,"%02ld",nMinOffset); lstrcatA(m_szGMTOffset,szTmp); } break; } return; } // INET_BASIC_LOG::INET_BASIC_LOG() DWORD INET_BASIC_LOG::LogInformation(IN const INETLOG_INFORMATIONA * pInetLogInfo, OUT LPSTR pszErrorMessage, IN OUT LPDWORD lpcchErrorMessage ) /*++ Same as the following function: LogInformation( IN const INETLOG_INFORMATIONW * pInetLogInfo). Keep the two functions consistent. This functions performs the action of logging information for InetNoLog. Essentially, does not produce any logging entry at all. Arguments: pInetLogInfo pointer to structure containing the Information to be logged. Returns: Win32 Error Code --*/ { IF_DEBUG( INETLOG) { DBGPRINTF( ( DBG_CONTEXT, "%s(%08x)::LogInformation( pInetLogInfo = %08x) called.\n", QueryClassIdString(), this, pInetLogInfo)); } // // Equivalent of no logging case. Do Nothing. // return ( NO_ERROR); } // INET_BASIC_LOG::LogInformation() DWORD INET_BASIC_LOG::LogInformation( IN const INETLOG_INFORMATIONW * pInetLogInfo, OUT LPWSTR pszErrorMessage, IN OUT LPDWORD lpcchErrorMessage ) /*++ This functions performs the action of logging information for InetNoLog. Essentially, does not produce any logging entry at all. Arguments: pInetLogInfo pointer to structure containing the Information to be logged. Returns: Win32 Error Code --*/ { IF_DEBUG( INETLOG) { DBGPRINTF( ( DBG_CONTEXT, "%s(%08x)::LogInformation( pInetLogInfo = %08x) called.\n", QueryClassIdString(), this, pInetLogInfo)); } // // Equivalent of no logging case. Do Nothing. // return ( NO_ERROR); } // INET_BASIC_LOG::LogInformation() BOOL INET_BASIC_LOG::SetLogRecordFormat( IN LPCWSTR pszLogFormat) /*++ This functions stores the new log record format into buffer and sets the parsed log format for subsequent output. Arguments: pszLogFormat pointer to wide char string containing the new log record format to be used. Returns: TRUE on success and FALSE on failure. Note: The log record formats specified can be one of the standard formats. Or be a totally new custom format. In case of custom formats. we use a parser to construct the appropriate log format. For now only the standard formats are allowed, each specified by a name. --*/ { CopyUnicodeStringToBuffer( m_rgchLogRecordFormat, MAX_LOG_RECORD_FORMAT_LEN, pszLogFormat); // // Parse the format if necessary and construct intermediate structures. // NYI // return ( TRUE); } // INET_BASIC_LOG::SetLogRecordFormat() VOID INET_BASIC_LOG::SetServerName( IN LPCWSTR pszServerName) /*++ This functions stores the server name for the log object. The server name should be the name of the server on which the given service is running. Arguments: pszServerName pointer to Unicode string containing the server name. Returns: TRUE on success and FALSE on failure. --*/ { DWORD lenDiff = lstrlenW(pszServerName) - lstrlenW(m_rgchServerName); CopyUnicodeStringToBuffer( m_rgchServerName, MAX_SERVER_NAME_LEN, pszServerName); // since we cache the minimum chars required, update the length required. m_cchMinForLogRecord += lenDiff; // cached service-server name string m_cchRgchSSName = wsprintfA(m_rgchServiceServerName, "%ws%c %ws%c ", m_rgchServiceName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A, m_rgchServerName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A ); wsprintfW(m_rgwchServiceServerName, L"%ws%c %ws%c ", m_rgchServiceName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A, m_rgchServerName, DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A ); return ; } // INET_BASIC_LOG::SetServerName() BOOL INET_BASIC_LOG::FormatLogInformation( IN const INETLOG_INFORMATIONA * pInetLogInfo, IN const SYSTEMTIME * pstCurrentTime, IN OUT CHAR * pchBuffer, IN OUT LPDWORD lpcchBuffer ) const /*++ This function same as the other FormatLogInformation() function just that this works with CHARs. NOTE: If any changes are made to this function, also update the other function. --*/ { BOOL fReturn = FALSE; CHAR rgchDateTime[30]; // buffer to print date int cchDateTime; LPCSTR pszUserName = pInetLogInfo->pszClientUserName; LPCSTR pszOperation = pInetLogInfo->pszOperation; LPCSTR pszTarget = pInetLogInfo->pszTarget; LPCSTR pszParameters= pInetLogInfo->pszParameters; LPCSTR pszServerAddr= pInetLogInfo->pszServerIpAddress; int cchPartial; int cchUserName; int cchOperation; int cchTarget; int cchServerAddr; int cchBytesSent; int cchBytesRecvd; int cchParameters; // // Format the Date and Time for logging. // Date & Time fields + log records may need to be localized. NYI // DBG_ASSERT( pstCurrentTime != NULL); cchDateTime = IslFormatDateTime( pstCurrentTime, dftLog, rgchDateTime); // If strings are empty set them to default values. if ( IsEmptyStr(pszUserName)) { pszUserName = QueryDefaultUserNameA();} if ( IsEmptyStr(pszOperation)) { pszOperation = PSZ_UNKNOWN_FIELD_A; } if ( IsEmptyStr(pszParameters)) { pszParameters= PSZ_UNKNOWN_FIELD_A; } if ( IsEmptyStr(pszTarget)) { pszTarget = PSZ_UNKNOWN_FIELD_A; } if ( IsEmptyStr(pszServerAddr)) { pszServerAddr= PSZ_UNKNOWN_FIELD_A; } // // Initialize the buffer. // DBG_ASSERT( pchBuffer != NULL && lpcchBuffer != NULL); *pchBuffer = '\0'; switch ( m_logFormat) { case InternetStdLogFormat: { CHAR rgchBytesSent[32] = ""; CHAR rgchBytesRecvd[32] = ""; DWORD cchReqd; DWORD dwError; dwError = IsLargeIntegerToDecimalChar( &pInetLogInfo->liBytesSent, rgchBytesSent); DBG_ASSERT( dwError == NO_ERROR); dwError = IsLargeIntegerToDecimalChar( &pInetLogInfo->liBytesRecvd, rgchBytesRecvd); DBG_ASSERT( dwError == NO_ERROR); // // Format is: // Host UserName Date Time ProcessingTime BytesRecvd BytesSent // ServiceName ServerName ServiceStatus Win32Status // Operation Target Parameters // cchReqd = (QueryCchMinimum() + lstrlenA( pInetLogInfo->pszClientHostName) + (cchUserName = lstrlenA( pszUserName)) + (cchServerAddr = lstrlenA( pszServerAddr)) + (cchOperation = lstrlenA( pszOperation)) + (cchTarget = lstrlenA( pszTarget)) + (cchParameters = lstrlenA( pszParameters)) + (cchBytesSent = lstrlenA( rgchBytesSent)) + (cchBytesRecvd = lstrlenA( rgchBytesRecvd)) + cchDateTime ); if ( ( fReturn = ( cchReqd < *lpcchBuffer))) { DWORD cchStored = 0; CHAR pszPartial[200]; #if 0 // // Optimize the common case using lstrcpy() & lstrcat() // CHAR chDelimiter = DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A; cchStored = wsprintfA(pchBuffer, "%s%c %s%c %hs%c %hs%c %ws%c %ws%c " "%s%c %u%c %hs%c %hs%c %u%c %u%c " "%s%c %s%c %s%c", pInetLogInfo->pszClientHostName, chDelimiter, pszUserName, chDelimiter, rgchDateTime, m_rgchServiceName, chDelimiter, m_rgchServerName, chDelimiter, pszServerAddr, chDelimiter, pInetLogInfo->msTimeForProcessing, chDelimiter, rgchBytesRecvd, chDelimiter, rgchBytesSent, chDelimiter, pInetLogInfo->dwServiceSpecificStatus, chDelimiter, pInetLogInfo->dwWin32Status, chDelimiter, pszOperation, chDelimiter, pszTarget, chDelimiter, pszParameters, chDelimiter ); # endif // get client host name lstrcpyA( pchBuffer, pInetLogInfo->pszClientHostName); // append UserName cchPartial = lstrlenA( pchBuffer); CopyLogDelimiter( pchBuffer + cchPartial); lstrcpyA( pchBuffer + cchPartial + LEN_PSZ_DELIMITER, pszUserName); // append DateTime cchPartial += LEN_PSZ_DELIMITER + cchUserName; CopyLogDelimiter( pchBuffer + cchPartial); lstrcpyA( pchBuffer + cchPartial + LEN_PSZ_DELIMITER, rgchDateTime); // append Service/ServerName : // do not append delimiter, since rgchDateTime already has it cchPartial += cchDateTime; lstrcpyA(pchBuffer + cchPartial + LEN_PSZ_DELIMITER, m_rgchServiceServerName); // append ServerAddr cchPartial += LEN_PSZ_DELIMITER + m_cchRgchSSName; lstrcpyA(pchBuffer + cchPartial, pszServerAddr); // append processing time (in milliseconds) cchPartial += cchServerAddr; CopyLogDelimiter( pchBuffer + cchPartial); cchPartial += LEN_PSZ_DELIMITER + wsprintfA(pchBuffer + cchPartial + LEN_PSZ_DELIMITER, "%u", pInetLogInfo->msTimeForProcessing ); // append Bytes Received CopyLogDelimiter( pchBuffer + cchPartial); lstrcpyA( pchBuffer + cchPartial + LEN_PSZ_DELIMITER, rgchBytesRecvd); // append Bytes Sent cchPartial += cchBytesRecvd + LEN_PSZ_DELIMITER; CopyLogDelimiter( pchBuffer + cchPartial); lstrcpyA( pchBuffer + cchPartial + LEN_PSZ_DELIMITER, rgchBytesSent); // append Service Status & Win32 Status cchPartial += cchBytesSent + LEN_PSZ_DELIMITER; CopyLogDelimiter( pchBuffer + cchPartial); cchPartial += LEN_PSZ_DELIMITER + wsprintfA(pchBuffer + cchPartial + LEN_PSZ_DELIMITER, "%u%s%u", pInetLogInfo->dwServiceSpecificStatus, G_PSZ_DELIMITER, pInetLogInfo->dwWin32Status ); // append Operation CopyLogDelimiter( pchBuffer + cchPartial); lstrcpyA( pchBuffer + cchPartial + LEN_PSZ_DELIMITER, pszOperation); // append Target cchPartial += cchOperation + LEN_PSZ_DELIMITER; CopyLogDelimiter( pchBuffer + cchPartial); lstrcpyA( pchBuffer + cchPartial + LEN_PSZ_DELIMITER, pszTarget); // append Parameters cchPartial += cchTarget + LEN_PSZ_DELIMITER; CopyLogDelimiter( pchBuffer + cchPartial); lstrcpyA( pchBuffer + cchPartial + LEN_PSZ_DELIMITER, pszParameters); cchPartial += cchParameters + LEN_PSZ_DELIMITER; CopyLogDelimiter( pchBuffer + cchPartial); cchStored = ( cchPartial + LEN_PSZ_DELIMITER); DBG_ASSERT( cchStored < *lpcchBuffer); *lpcchBuffer = cchStored; } else { SetLastError( ERROR_INSUFFICIENT_BUFFER); *lpcchBuffer = cchReqd; } break; } // case InternetStdLogFormat: case NCSALogFormat: { CHAR rgchBytesSent[ 32]=""; CHAR rgchServiceStatus[32]=""; DWORD dwError; DWORD cchStored = 0; dwError = IsLargeIntegerToDecimalChar( &pInetLogInfo->liBytesSent, rgchBytesSent); DBG_ASSERT( dwError == NO_ERROR); wsprintfA( rgchServiceStatus, "%d", pInetLogInfo->dwServiceSpecificStatus ); if ( 0 != strcmp( pszParameters, "-")) { cchStored = wsprintfA(pchBuffer, "%s - %s [%02d/%s/%d:%02d:%02d:%02d %s] \"%s %s?%s HTTP/1.0\" %s %s", pInetLogInfo->pszClientHostName, pszUserName, pstCurrentTime->wDay, s_rgchMonths[pstCurrentTime->wMonth-1], pstCurrentTime->wYear, pstCurrentTime->wHour, pstCurrentTime->wMinute, pstCurrentTime->wSecond, m_szGMTOffset, pszOperation, pszTarget, pszParameters, rgchServiceStatus, rgchBytesSent ); } else { cchStored = wsprintfA(pchBuffer, "%s - %s [%02d/%s/%d:%02d:%02d:%02d %s] \"%s %s HTTP/1.0\" %s %s", pInetLogInfo->pszClientHostName, pszUserName, pstCurrentTime->wDay, s_rgchMonths[pstCurrentTime->wMonth-1], pstCurrentTime->wYear, pstCurrentTime->wHour, pstCurrentTime->wMinute, pstCurrentTime->wSecond, m_szGMTOffset, pszOperation, pszTarget, rgchServiceStatus, rgchBytesSent ); } *lpcchBuffer = cchStored; fReturn = TRUE; } break; // NCSALogFormat default: DBGPRINTF( ( DBG_CONTEXT, " %d Formatting of log records not implemented.\n", m_logFormat)); *lpcchBuffer = 0; // Nothing stored. break; } // switch() return ( fReturn); } // INET_BASIC_LOG::FormatLogInformation() BOOL INET_BASIC_LOG::FormatLogInformation( IN const INETLOG_INFORMATIONW * pInetLogInfo, IN const SYSTEMTIME * pstCurrentTime, IN OUT WCHAR * pwchBuffer, IN OUT LPDWORD lpcchBuffer ) const /*++ This function formats the log record using the information in pInetLogInfo and stores the same in pbBuffer. Arguments: pInetLogInfo pointer to Log Information structure pstCurrentTime pointer to SystemTime structure containing the current time used in generation of the time stamp. pwchBuffer pointer to byte buffer which will contain the log record on successful completion of this function. lpcchBuffer pointer to count of Wide characters stored in buffer When this function is called it contains the size of buffer On return it contains the number of bytes of data written. Returns: TRUE on success and FALSE on failure. Use GetLastError() for detailed error code. --*/ { BOOL fReturn = FALSE; CHAR rgchDateTime[30]; // buffer to print date LPCWSTR pszUserName = pInetLogInfo->pszClientUserName; LPCWSTR pszOperation = pInetLogInfo->pszOperation; LPCWSTR pszTarget = pInetLogInfo->pszTarget; LPCWSTR pszParameters= pInetLogInfo->pszParameters; LPCWSTR pszServerAddr= pInetLogInfo->pszServerIpAddress; CHAR chDelimiter = DEFAULT_DELIMITER_CHAR_IN_LOG_RECORD_A; // // Format the Date and Time for logging. // Date & Time fields + log records may need to be localized. NYI // DBG_ASSERT( pstCurrentTime != NULL); IslFormatDateTime( pstCurrentTime, dftLog, rgchDateTime); // If strings are empty set them to default values. if ( IsEmptyStr(pszUserName)) { pszUserName = QueryDefaultUserName(); } if ( IsEmptyStr(pszOperation)) { pszOperation = PSZ_UNKNOWN_FIELD_W; } if ( IsEmptyStr(pszParameters)) { pszParameters= PSZ_UNKNOWN_FIELD_W; } if ( IsEmptyStr(pszTarget)) { pszTarget = PSZ_UNKNOWN_FIELD_W; } if ( IsEmptyStr(pszServerAddr)) { pszServerAddr= PSZ_UNKNOWN_FIELD_W; } // // Initialize the buffer. // DBG_ASSERT( pwchBuffer != NULL && lpcchBuffer != NULL); *pwchBuffer = L'\0'; switch ( m_logFormat) { case InternetStdLogFormat: { CHAR rgchBytesSent[ 32]; CHAR rgchBytesRecvd[ 32]; DWORD cchReqd; DWORD dwError; dwError = IsLargeIntegerToDecimalChar( &pInetLogInfo->liBytesSent, rgchBytesSent); DBG_ASSERT( dwError == NO_ERROR); dwError = IsLargeIntegerToDecimalChar( &pInetLogInfo->liBytesRecvd, rgchBytesRecvd); DBG_ASSERT( dwError == NO_ERROR); // // Format is: // Host UserName Date Time ProcessingTime BytesRecvd BytesSent // ServiceName ServerName ServiceStatus Win32Status // Operation Target Parameters // cchReqd = (QueryCchMinimum() + lstrlenW( pInetLogInfo->pszClientHostName) + lstrlenW( pszUserName) + lstrlenW( pszServerAddr) + lstrlenW( pszOperation) + lstrlenW( pszTarget) + lstrlenW( pszParameters) + strlen( rgchBytesSent) + strlen( rgchBytesRecvd) + strlen( rgchDateTime) ); if ( ( fReturn = ( cchReqd < *lpcchBuffer))) { DWORD cchStored = 0; cchStored = wsprintfW( pwchBuffer, #ifndef CHICAGO L"%ws%c %ws%c %hs%c %hs%ws" L"%ws%c %d%c %hs%c %hs%c %u%c %u%c " L"%ws%c %ws%c %ws%c", #else L"%s%c %s%c %S%c %S%c %s%c " L"%s%c %s%c %d%c %S%c %S%c %u%c %u%c " L"%s%c %s%c %s%c", #endif pInetLogInfo->pszClientHostName, chDelimiter, pszUserName, chDelimiter, rgchDateTime, m_rgwchServiceServerName, pszServerAddr, chDelimiter, pInetLogInfo->msTimeForProcessing, chDelimiter, rgchBytesRecvd, chDelimiter, rgchBytesSent, chDelimiter, pInetLogInfo->dwServiceSpecificStatus, chDelimiter, pInetLogInfo->dwWin32Status, chDelimiter, pszOperation, chDelimiter, pszTarget, chDelimiter, pszParameters, chDelimiter ); DBG_ASSERT( cchStored < *lpcchBuffer); *lpcchBuffer = cchStored; } else { SetLastError( ERROR_INSUFFICIENT_BUFFER); *lpcchBuffer = cchReqd; } break; } // case InternetStdLogFormat: case NCSALogFormat: { CHAR rgchBytesSent[32]=""; WCHAR rgchServiceStatus[32]=L""; DWORD dwError; DWORD cchStored = 0; dwError = IsLargeIntegerToDecimalChar( &pInetLogInfo->liBytesSent, rgchBytesSent); DBG_ASSERT( dwError == NO_ERROR); wsprintfW( (WCHAR*)rgchServiceStatus, L"%d", pInetLogInfo->dwServiceSpecificStatus ); if ( 0 != lstrcmpW( pszParameters, L"-")) { cchStored = wsprintfW(pwchBuffer, L"%s - %s [%02d/%s/%d:%02d:%02d:%02d %s] \"%s %s?%s HTTP/1.0\" %s %s", pInetLogInfo->pszClientHostName, pszUserName, pstCurrentTime->wDay, s_rgchMonths[pstCurrentTime->wMonth-1], pstCurrentTime->wYear, pstCurrentTime->wHour, pstCurrentTime->wMinute, pstCurrentTime->wSecond, m_szGMTOffset, pszOperation, pszTarget, pszParameters, rgchServiceStatus, rgchBytesSent ); } else { cchStored = wsprintfW(pwchBuffer, L"%s - %s [%02d/%s/%d:%02d:%02d:%02d %s] \"%s %s HTTP/1.0\" %s %s", pInetLogInfo->pszClientHostName, pszUserName, pstCurrentTime->wDay, s_rgchMonths[pstCurrentTime->wMonth-1], pstCurrentTime->wYear, pstCurrentTime->wHour, pstCurrentTime->wMinute, pstCurrentTime->wSecond, m_szGMTOffset, pszOperation, pszTarget, rgchServiceStatus, rgchBytesSent ); } *lpcchBuffer = cchStored; fReturn = TRUE; } break; // NCSALogFormat default: DBGPRINTF( ( DBG_CONTEXT, " %d Formatting of log records not implemented.\n", m_logFormat)); *lpcchBuffer = 0; // Nothing stored. break; } // switch() return ( fReturn); } // INET_BASIC_LOG::FormatLogInformation() # if DBG VOID INET_BASIC_LOG::Print( VOID) const { DBGPRINTF( ( DBG_CONTEXT, " Printing %s ( %08x)." " ServiceName = %ws, ServerName = %ws." " LogFormat = %d = %ws.\n", QueryClassIdString(), this, m_rgchServiceName, m_rgchServerName, m_logFormat, m_rgchLogRecordFormat)); return; } // INET_BASIC_LOG::Print() # endif // DBG /************************************************** * Member Functions of class INET_FILE_LOG **************************************************/ INET_FILE_LOG::INET_FILE_LOG( IN LPCWSTR pszServiceName, IN EVENT_LOG * pEventLog, IN LPCWSTR pszLogFileDirectory, IN INETLOG_PERIOD ilPeriod, // Default is InetLogNoPeriod IN INETLOG_FORMAT iLogFormat // default is InternetStdLogFormat ) /*++ Constructs a file logging object. Log records from server are sent to file ( or files for periodic logging). The log files are stored in the logging directory specified in the constructor. The logging period specifies the manner in which log files are written. For example: In daily logging mode, a separate log file is generated for each day. Arguments: pszServiceName pointer to string containing the service name. pszLogFileDirectory pointer to string containing the directory in which to store the log record. Returns: constructed INET_FILE_LOG object. --*/ : INET_BASIC_LOG( pszServiceName, pEventLog), m_ilPeriod ( ilPeriod), m_sequence ( 1), m_pLogFile ( NULL), m_cbTotalWritten ( 0), m_cbSizeForTruncation( MAX_FILE_TRUNCATION_SIZE) // set to default { DBG_ASSERT( pszLogFileDirectory != NULL); CopyUnicodeStringToBuffer( m_rgchLogFileDirectory, MAX_PATH, pszLogFileDirectory); InitializeCriticalSection( &m_csLock); memset( &m_stCurrentFile, 0, sizeof( m_stCurrentFile)); memset( m_rgchLogFileName, 0, sizeof(m_rgchLogFileName)); SetInetLogFormat( iLogFormat ); return; } // INET_FILE_LOG::INET_FILE_LOG() INET_FILE_LOG::~INET_FILE_LOG( VOID) /*++ This frees dynamic memory acquired for INET_FILE_LOG and also closes file handles which may require to be closed. Arguments: None --*/ { // // No Dynamic memory. No action needs to be taken. // Lock(); // // Close Log File handle for the file into which log record is sent. // CloseLogFile(); Unlock(); DeleteCriticalSection( &m_csLock); IF_DEBUG( INETLOG) { DBGPRINTF((DBG_CONTEXT, "INET_FILE_LOG(%08x) is destroyed\n", this)); } return; } // INET_FILE_LOG::~INET_FILE_LOG() DWORD INET_FILE_LOG::LogInformation( IN const INETLOG_INFORMATIONA * pInetLogInfo, OUT LPSTR pszErrorMessage, IN OUT LPDWORD lpcchErrorMessage ) /*++ See comments in INET_FILE_LOG::LogInformation( IN const INETLOG_INFORMATIONA * ); NOTE: This function should be similar to that other function, to ensure correct functionality. Both are similar execept that one uses ANSI and the other uses UNICODE. --*/ { DWORD dwError = NO_ERROR; SYSTEMTIME stNow; GetLocalTime( &stNow); // // 1. Format the log record to contain the information to be logged. // CHAR rgchBuffer[ MAX_LOG_RECORD_LEN]; DWORD cchBufferLen = MAX_LOG_RECORD_LEN - 3; BOOL fReturn = FormatLogInformation( pInetLogInfo, &stNow, rgchBuffer, &cchBufferLen); if ( fReturn) { // // Append a new line char at the end of the log record. // DBG_ASSERT( cchBufferLen <= MAX_LOG_RECORD_LEN - 3); rgchBuffer[ cchBufferLen++] = '\r'; rgchBuffer[ cchBufferLen++] = '\n'; rgchBuffer[ cchBufferLen] = '\0'; // terminate the string // // 2. Check and change file for writing the log record. // // A new log file has to be opened if: // it is a beginning of new period or // the file has overflown max size for truncation or // if this is the first log entry. // BOOL fOpenNewFile = ( IsBeginningOfNewPeriod( m_ilPeriod, &m_stCurrentFile, &stNow) || ( m_pLogFile == NULL)); BOOL fBackup = ( !fOpenNewFile && IsFileOverFlowForCB( cchBufferLen*sizeof( CHAR))); Lock(); if ( fOpenNewFile || fBackup) { // // 0. Check to see if the opening of new file is still required. // Reason: Some other thread could have done the same before // this thread cruised through the lock. // 1. Close existing log file. // 2. Form the new log file name. // 3. Open the new log file. // BOOL fOpenNewFile2 = ( IsFileOverFlowForCB( cchBufferLen * sizeof( WCHAR)) || ( IsBeginningOfNewPeriod( m_ilPeriod, &m_stCurrentFile, &stNow) || ( m_pLogFile == NULL))); if ( fOpenNewFile2) { fReturn = (CloseLogFile() && FormNewLogFileName( &stNow, fBackup) && OpenLogFile()); if ( fReturn) { // record the time of opening of this new file m_stCurrentFile = stNow; } } } // opening new log file or making backups. // // 3. Write the log record to file ( using given handle). // // TBD: We can move the write outside the critical section // --> this could be achieved by using ref counts on // pLogFile object. // DBG_ASSERT( m_pLogFile != NULL || !fReturn); fReturn = (fReturn && m_pLogFile->Write((PVOID ) rgchBuffer, cchBufferLen*sizeof(CHAR)) ); if ( fReturn) { IncrementBytesWritten( cchBufferLen * sizeof(CHAR)); } Unlock(); } // successful log record has been written. if ( !fReturn ) { dwError = GetLastError(); if ( pszErrorMessage != NULL && lpcchErrorMessage != NULL) { // For file logging we will not store any error message. lstrcpyA( pszErrorMessage, ""); *lpcchErrorMessage = 0; } } IF_DEBUG( INETLOG) { DBGPRINTF( ( DBG_CONTEXT, "%s(%08x)::LogInformation() returns %d. Error = %d\n", QueryClassIdString(), this, fReturn, dwError)); } return ( dwError); } // INET_FILE_LOG::LogInformation() DWORD INET_FILE_LOG::LogInformation( IN const INETLOG_INFORMATIONW * pInetLogInfo, OUT LPWSTR pszErrorMessage, IN OUT LPDWORD lpcchErrorMessage ) /*++ This functions performs the action of logging information for InetLogtoFile and InetLogToPeriodicFile. It also checks for following: 1) If the file size is greater than specified MAX_TRUNCATION_SIZE, then it closes the file, renames it as backup file ( after deleting backup) and reopens a new file. 2) If the period the file has been in existence > period specified, then the current file is closed, new file is opened with the new name ( specified by period) and log record will continue to be written there. ( This is only for InetLogToPeriodicFile.) Arguments: pInetLogInfo pointer to structure containing the Information to be logged. Returns: Win32 Error code --*/ { DWORD dwError = NO_ERROR; SYSTEMTIME stNow; GetLocalTime( &stNow); // // 1. Format the log record to contain the information to be logged. // WCHAR rgchBuffer[ MAX_LOG_RECORD_LEN]; DWORD cchBufferLen = MAX_LOG_RECORD_LEN - 3; BOOL fReturn = FormatLogInformation( pInetLogInfo, &stNow, rgchBuffer, &cchBufferLen); if ( fReturn) { // // Append a new line char at the end of the log record. // DBG_ASSERT( cchBufferLen <= MAX_LOG_RECORD_LEN - 3); rgchBuffer[ cchBufferLen++] = L'\r'; rgchBuffer[ cchBufferLen++] = L'\n'; rgchBuffer[ cchBufferLen] = L'\0'; // terminate the string // // 2. Check and change file handle for writing the log record. // // A new log file has to be opened if: // it is a beginning of new period or // the file has overflown max size for truncation or // if this is the first log entry. // BOOL fOpenNewFile = ( IsBeginningOfNewPeriod( m_ilPeriod, &m_stCurrentFile, &stNow) || ( m_pLogFile == NULL )); BOOL fBackup = ( !fOpenNewFile && IsFileOverFlowForCB( cchBufferLen*sizeof( WCHAR))); Lock(); if ( fOpenNewFile || fBackup) { // // 0. Check to see if the opening of new file is still required. // Reason: Some other thread could have done the same before // this thread cruised through the lock. // 1. Close existing log file. // 2. Form the new log file name. // 3. Open the new log file. // BOOL fOpenNewFile2 = ( IsFileOverFlowForCB( cchBufferLen * sizeof( WCHAR)) || ( IsBeginningOfNewPeriod( m_ilPeriod, &m_stCurrentFile, &stNow) || ( m_pLogFile == NULL))); if ( fOpenNewFile2) { fReturn = (CloseLogFile() && FormNewLogFileName( &stNow, fBackup) && OpenLogFile()); if ( fReturn) { // record the time of opening of this new file m_stCurrentFile = stNow; } } } // opening new log file or making backups. // // 3. Write the log record to file ( using given handle). // // TBD: We can move the write outside the critical section // --> this could be achieved by using ref counts on // pLogFile object. // DBG_ASSERT( m_pLogFile != NULL); fReturn = (fReturn && m_pLogFile->Write((PVOID ) rgchBuffer, cchBufferLen*sizeof(WCHAR)) ); if ( fReturn) { IncrementBytesWritten( cchBufferLen * sizeof(WCHAR)); } Unlock(); } // successful log record has been written. if ( !fReturn ) { dwError = GetLastError(); if ( pszErrorMessage != NULL && lpcchErrorMessage != NULL) { // For file logging we will not store any error message. lstrcpyW( pszErrorMessage, L""); *lpcchErrorMessage = 0; } } IF_DEBUG( INETLOG) { DBGPRINTF( ( DBG_CONTEXT, "%s(%08x)::LogInformation() returns %d. Error = %d\n", QueryClassIdString(), this, fReturn, dwError)); } return ( dwError); } // INET_FILE_LOG::LogInformation() BOOL INET_FILE_LOG::OpenLogFile( VOID) /*++ This functions is called when a new log file needs to be opened. It forms the name of the log file using service name and logging period selected. Then it opens a handle for the approapriate file. The file path is extracted from \ LogFileName is formed before calling this function. This INET_FILE_LOG object should be locked before calling this function. Arguments: None Returns: TRUE if successful and FALSE if there is a failure. Use GetLastError() to get the error codes for error. --*/ { BOOL fReturn = TRUE; WCHAR rgchPath[ MAX_PATH + 1]; if ( m_pLogFile != NULL) { // // already a log file is open. return silently // DBGPRINTF( ( DBG_CONTEXT, " Log File %ws is already open ( %08x)\n", m_rgchLogFileName, m_pLogFile)); DBG_ASSERT( fReturn == TRUE); } else { // // Append log file name to path to form the path of file to be opened. // if ( lstrlenW( m_rgchLogFileName) + lstrlenW( m_rgchLogFileDirectory) >= MAX_PATH) { SetLastError( ERROR_FILE_NOT_FOUND); DBG_ASSERT( m_pLogFile == NULL); fReturn = FALSE; } else { #ifndef CHICAGO // wsprintfW( rgchPath, L"%ws\\%ws", lstrcpyW( rgchPath, m_rgchLogFileDirectory); lstrcatW( rgchPath, L"\\"); lstrcatW( rgchPath, m_rgchLogFileName); IF_DEBUG( INETLOG) { DBGPRINTF( ( DBG_CONTEXT, "Opening New File %ws\n", rgchPath)); } m_pLogFile = ILOG_FILE::OpenFileForAppend(rgchPath); if (m_pLogFile != NULL) { m_cbTotalWritten = 0; #else CHAR szDirA[MAX_PATH+1]; CHAR szFileA[MAX_PATH+1]; DWORD cch; *szDirA = '0'; cch = WideCharToMultiByte(CP_ACP, 0, m_rgchLogFileDirectory, -1, szDirA, sizeof(szDirA)/sizeof(CHAR), NULL,NULL ); *szFileA = '0'; cch = WideCharToMultiByte(CP_ACP, 0, m_rgchLogFileName, -1, szFileA, sizeof(szFileA)/sizeof(CHAR), NULL,NULL ); wsprintfA( (LPSTR)rgchPath, "%s\\%s", szDirA,szFileA); IF_DEBUG( INETLOG) { DBGPRINTF( ( DBG_CONTEXT, "Opening New File %s\n", rgchPath)); } m_hLogFile = CreateFile((LPCSTR) rgchPath, GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, // security attributes OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // template file handle if ( ( fReturn = (m_hLogFile != INVALID_HANDLE_VALUE))) { m_cbTotalWritten = 0; // set the file pointer at the end of the file (append mode) if ( SetFilePointer( m_hLogFile, 0, NULL, FILE_END) == (DWORD) -1L) { DBGPRINTF(( DBG_CONTEXT, "SetFilePointer(%ws, End) failed. Error=%u\n", rgchPath, GetLastError())); fReturn = FALSE; CloseLogFile(); m_hLogFile = INVALID_HANDLE_VALUE; } # endif // CHICAGO } else { fReturn = FALSE; } } if ( !fReturn) { DBGPRINTF( ( DBG_CONTEXT, " Failed to Open File %ws\\%ws. Error = %u.\n", m_rgchLogFileDirectory, m_rgchLogFileName, GetLastError())); } } return ( fReturn); } // INET_FILE_LOG::OpenLogFile() BOOL INET_FILE_LOG::CloseLogFile( VOID) /*++ Closes the current log file. The INET_FILE_LOG object should be locked before calling this function. If there is no valid file open, then this function silently returns TRUE. Arguments: None Returns: TRUE on success and FALSE if there is a failure. --*/ { BOOL fReturn = TRUE; if ( m_pLogFile != NULL) { DWORD dwError = ILOG_FILE::CloseFile( &m_pLogFile); if ( dwError != NO_ERROR) { fReturn = FALSE; SetLastError( dwError); } } return ( fReturn); } // INET_FILE_LOG::CloseLogFile() inline DWORD WeekOfMonth(IN LPSYSTEMTIME pstNow) /*++ Finds the ordinal number of the week of current month. The numbering of weeks starts from 1 and run through 5 per month (max). The week number changes only on sundays. The calculation to be use is: 1 + ( dayOfMonth - 1)/7 + (( dayOfMonth - 1) % 7 > dayOfWeek); (a) (b) (c) (d) (a) to set the week numbers to begin from week numbered "1" (b) used to calculate the rough number of the week on which a given day falls based on the date. (c) calculates what is the offset from the start of week for a given day based on the fact that a week had 7 days. (d) is the raw day of week given to us. (c) > (d) indicates that the week is rolling forward and hence the week count should be offset by 1 more. --*/ { DWORD dwTmp; dwTmp = (pstNow->wDay - 1); dwTmp = ( 1 + dwTmp/7 + (((dwTmp % 7) > pstNow->wDayOfWeek) ? 1 : 0)); return ( dwTmp); } // WeekOfMonth() BOOL INET_FILE_LOG::FormNewLogFileName( IN LPSYSTEMTIME pstNow, IN BOOL fBackup) /*++ This function that forms the new log file name based on type of periodic logging done. Arguments: pstNow pointer to SystemTime which contains the current time. fBackup flag indicating if we want to make current file a backup. Returns: TRUE on success in forming the name or FALSE if there is any error. --*/ { if ( fBackup || m_ilPeriod == InetLogNoPeriod) { // // Make current file a backup file. Rename the current file as backup // DBGPRINTF( ( DBG_CONTEXT, " Backing up the log file %ws not implemented.\n", m_rgchLogFileName)); #ifndef CHICAGO wsprintfW(m_rgchLogFileName, L"%.6ws%u.%ws", #else wsprintfW(m_rgchLogFileName, L"%.6s%u.%s", #endif ( QueryInetLogFormat() == NCSALogFormat )? DEFAULT_NCSA_FILE_NAME_W : DEFAULT_LOG_FILE_NAME_W, m_sequence, DEFAULT_LOG_FILE_EXTENSION_W); // cycle sequence number through 1 thru 99. m_sequence = (m_sequence < 99) ? (m_sequence + 1) : 1; } else { // // switch case and generate a new name // WORD wYear = ( pstNow->wYear % 100); // retain just last 2 digits. switch ( m_ilPeriod) { case InetLogDaily: #ifndef CHICAGO wsprintfW( m_rgchLogFileName, L"%.2ws%02.2u%02u%02u.%ws", #else wsprintfW( m_rgchLogFileName, L"%.2s%02.2u%02u%02u.%s", #endif ( QueryInetLogFormat() == NCSALogFormat )? DEFAULT_NCSA_FILE_NAME_W : DEFAULT_LOG_FILE_NAME_W, wYear, pstNow->wMonth, pstNow->wDay, DEFAULT_LOG_FILE_EXTENSION_W); break; case InetLogWeekly: #ifndef CHICAGO wsprintfW( m_rgchLogFileName, L"%.2ws%02.2u%02u%02u.%ws", #else wsprintfW( m_rgchLogFileName, L"%.2s%02.2u%02u%02u.%s", #endif ( QueryInetLogFormat() == NCSALogFormat )? DEFAULT_NCSA_FILE_NAME_W : DEFAULT_LOG_FILE_NAME_W, wYear, pstNow->wMonth, WeekOfMonth(pstNow), DEFAULT_LOG_FILE_EXTENSION_W); break; case InetLogMonthly: #ifndef CHICAGO wsprintfW( m_rgchLogFileName, L"%.2ws%02u%02u.%ws", #else wsprintfW( m_rgchLogFileName, L"%.2s%02u%02u.%s", #endif ( QueryInetLogFormat() == NCSALogFormat )? DEFAULT_NCSA_FILE_NAME_W : DEFAULT_LOG_FILE_NAME_W, wYear, pstNow->wMonth, DEFAULT_LOG_FILE_EXTENSION_W); break; case InetLogYearly: #ifndef CHICAGO wsprintfW( m_rgchLogFileName, L"%.2ws%02u.%ws", #else wsprintfW( m_rgchLogFileName, L"%.2s%02u.%s", #endif ( QueryInetLogFormat() == NCSALogFormat )? DEFAULT_NCSA_FILE_NAME_W : DEFAULT_LOG_FILE_NAME_W, wYear, DEFAULT_LOG_FILE_EXTENSION_W); break; default: DBG_ASSERT( FALSE); break; } // switch() } IF_DEBUG( INETLOG) { DBGPRINTF( ( DBG_CONTEXT, "INET_FILE_LOG(%08x)::FormNewLogFileName() ==> file %ws." " Returns %d.\n", this, m_rgchLogFileName, TRUE)); } return ( TRUE); } // INET_FILE_LOG::FormNewLogFileName() BOOL INET_FILE_LOG::GetConfig(OUT PINETLOG_CONFIGURATIONW pLogConfig) const { BOOL fReturn; DBG_ASSERT( pLogConfig != NULL); fReturn = INET_BASIC_LOG::GetConfig(pLogConfig); if (fReturn) { // Store other file specific configuration informtaion pLogConfig->u.logFile.ilPeriod = m_ilPeriod; pLogConfig->u.logFile.cbSizeForTruncation = m_cbSizeForTruncation; pLogConfig->u.logFile.ilFormat = QueryInetLogFormat(); CopyUnicodeStringToBuffer(pLogConfig->u.logFile.rgchLogFileDirectory, MAX_PATH, m_rgchLogFileDirectory); } return (fReturn); } // INET_FILE_LOG::GetConfig() # if DBG VOID INET_FILE_LOG::Print( VOID) const { DBGPRINTF( ( DBG_CONTEXT, " Printing %s ( %08x).\n", QueryClassIdString(), this)); INET_BASIC_LOG::Print(); DBGPRINTF( ( DBG_CONTEXT, " LogFileDirectory = %ws\n TruncationSize = %u\n", m_rgchLogFileDirectory, m_cbSizeForTruncation)); DBGPRINTF( ( DBG_CONTEXT, " Logging Period = %d. Sequence = %d\n" " LogFile Object = %08x. FileName = %ws. BytesWritten = %u.\n", m_ilPeriod, m_sequence, m_pLogFile, m_rgchLogFileName, m_cbTotalWritten)); DBGPRINTF( ( DBG_CONTEXT, " Critical Section at %08x\n", &m_csLock)); if ( m_pLogFile != NULL) { m_pLogFile->Print(); } return; } // INET_FILE_LOG::Print() # endif // DBG /************************************************** * Auxiliary Functions **************************************************/ static BOOL IsBeginningOfNewPeriod( IN INETLOG_PERIOD ilPeriod, IN LPSYSTEMTIME pstCurrentFile, IN LPSYSTEMTIME pstNow) /*++ This function checks to see if we are beginning a new period for a given periodic interval type ( specified using ilPeriod). Arguments: ilPeriod INETLOG_PERIOD specifying the periodic interval. pstCurrentFile pointer to SYSTEMTIME for the current file. pstNow pointer to SYSTEMTIME for the present time. Returns: TRUE if a new period is beginning ( ie pstNow > pstCurrentFile). FALSE otherwise. --*/ { BOOL fNewPeriod = FALSE; switch ( ilPeriod) { case InetLogNoPeriod: break; case InetLogDaily: fNewPeriod = fNewPeriod || pstCurrentFile->wDay != pstNow->wDay; // // Fall Through // case InetLogMonthly: fNewPeriod = fNewPeriod || pstCurrentFile->wMonth != pstNow->wMonth; // // Fall Through // case InetLogYearly: fNewPeriod = fNewPeriod || pstCurrentFile->wYear != pstNow->wYear; break; case InetLogWeekly: fNewPeriod = pstNow->wDayOfWeek == 0; break; default: DBG_ASSERT( FALSE); break; } // switch() return ( fNewPeriod); } // IsBeginningOfNewPeriod() /************************ End of File ***********************/