313 lines
8.5 KiB
C++
313 lines
8.5 KiB
C++
|
/*--
|
||
|
Copyright (c) 1999 Microsoft Corporation
|
||
|
Module Name: LOG.CPP
|
||
|
Author: John Spaith
|
||
|
Abstract: Logging functions
|
||
|
--*/
|
||
|
|
||
|
#include "pch.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
|
||
|
// Responsible for log handling functions.
|
||
|
// A curhttpd.log and prvhttpd.log keep track of current and the previous
|
||
|
// changes. The format for each line is stolen from IIS, except that we
|
||
|
// include the date in each line; they create a new log for each day.
|
||
|
|
||
|
|
||
|
#include "httpd.h"
|
||
|
|
||
|
#ifdef WEB_SERVER_LOGGING
|
||
|
|
||
|
// const char cszDateOutputFmt[] = "%3s, %02d %3s %04d %02d:%02d:%02d ";
|
||
|
|
||
|
#define CURRENT_LOG_NAME L"\\current-httpd.log"
|
||
|
#define PREVIOUS_LOG_NAME L"\\previous-httpd.log"
|
||
|
#define LOG_REMOTE_ADDR_SIZE 50
|
||
|
#define LOG_EXTRA_BUFFER_LEN 100
|
||
|
|
||
|
void CLog::WriteEvent(DWORD dwEvent,...)
|
||
|
{
|
||
|
CHAR szOutput[MINBUFSIZE];
|
||
|
WCHAR wszFormat[512];
|
||
|
WCHAR wszOutput[512];
|
||
|
PSTR pszTrav = szOutput;
|
||
|
SYSTEMTIME st;
|
||
|
va_list ap;
|
||
|
|
||
|
if (!LoadString(g_hInst,dwEvent,wszFormat,celems(wszFormat)))
|
||
|
return;
|
||
|
|
||
|
va_start(ap,dwEvent);
|
||
|
wvsprintf(wszOutput,wszFormat,ap);
|
||
|
va_end (ap);
|
||
|
|
||
|
GetSystemTime(&st);
|
||
|
pszTrav = WriteHTTPDate(szOutput,&st,FALSE);
|
||
|
|
||
|
pszTrav += MyW2A(wszOutput,pszTrav,MINBUFSIZE - (pszTrav - szOutput)) - 1;
|
||
|
WriteData(szOutput,pszTrav - szOutput);
|
||
|
}
|
||
|
|
||
|
CLog::~CLog()
|
||
|
{
|
||
|
WriteEvent(IDS_HTTPD_SHUTDOWN_COMPLETE);
|
||
|
MyCloseHandle(m_hLog);
|
||
|
DeleteCriticalSection(&m_CritSection);
|
||
|
}
|
||
|
|
||
|
#define MAX_LOG_OPEN_ATTEMPTS 15
|
||
|
|
||
|
CLog::CLog(DWORD_PTR dwMaxFileLen, WCHAR * lpszLogDir)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
memset(this, 0, sizeof(*this));
|
||
|
if (0 == dwMaxFileLen || NULL == lpszLogDir)
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "HTTPD: No logging to be performed, because of on registry settings");
|
||
|
m_hLog = INVALID_HANDLE_VALUE;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_dwMaxFileSize = dwMaxFileLen;
|
||
|
InitializeCriticalSection(&m_CritSection);
|
||
|
TraceTag(ttidWebServer, "HTTPD: Initializing log files");
|
||
|
|
||
|
|
||
|
// remove trailing "\" if log file dir entry ends in it
|
||
|
if ( lpszLogDir[wcslen(lpszLogDir) - 1] == L'\\')
|
||
|
lpszLogDir[wcslen(lpszLogDir) - 1] = L'\0';
|
||
|
|
||
|
|
||
|
wsprintf(lpszCurrentLog,L"%s%s",lpszLogDir, CURRENT_LOG_NAME);
|
||
|
wsprintf(lpszPrevLog,L"%s%s",lpszLogDir,PREVIOUS_LOG_NAME);
|
||
|
|
||
|
|
||
|
// Note: It's possible that the log file is being written to a flash
|
||
|
// drive, in which case the web server initialization routines would
|
||
|
// be called before the drive is ready during boot time, in which
|
||
|
// case we get an INVALID_HANDLE_VALUE. To make sure the drive has
|
||
|
// time to initialize, we go through this loop before failing.
|
||
|
for (i = 0; i < MAX_LOG_OPEN_ATTEMPTS; i++)
|
||
|
{
|
||
|
if (INVALID_HANDLE_VALUE != (m_hLog = MyOpenAppendFile(lpszCurrentLog)))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
Sleep(1000);
|
||
|
}
|
||
|
|
||
|
if (INVALID_HANDLE_VALUE == m_hLog)
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "HTTPD: CreateFile fails for log <<%s>>, no logging will be done, GLE=0x%08x",lpszCurrentLog,GetLastError());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_dwFileSize = GetFileSize(m_hLog,NULL);
|
||
|
if ((DWORD)-1 == m_dwFileSize)
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "HTTPD: Get file size on log <<%s>> failed, err = %d",lpszCurrentLog,GetLastError());
|
||
|
m_hLog = INVALID_HANDLE_VALUE;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (m_dwFileSize != 0) // This moves filePtr to append log file
|
||
|
{
|
||
|
if ((DWORD)-1 == SetFilePointer(m_hLog, (DWORD)m_dwFileSize, NULL, FILE_BEGIN))
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "HTTPD: Get file size on log %s failed, err = %d",lpszCurrentLog,GetLastError());
|
||
|
m_hLog = INVALID_HANDLE_VALUE;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WriteEvent(IDS_HTTPD_STARTUP);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// The log written has the following format
|
||
|
// (DATE) (TIME) (IP of requester) (Method) (Request-URI) (Status returned)
|
||
|
|
||
|
// DATE is in the same format as the recommended one for httpd headers
|
||
|
// TIME is GMT
|
||
|
|
||
|
void CLog::WriteLog(CHttpRequest* pRequest)
|
||
|
{
|
||
|
CHAR szBuffer[MINBUFSIZE];
|
||
|
PSTR psz = szBuffer;
|
||
|
DWORD_PTR dwToWrite = 0;
|
||
|
|
||
|
DEBUGCHK(pRequest != NULL);
|
||
|
|
||
|
if (INVALID_HANDLE_VALUE == m_hLog) // no logging setup in reg, or prev failure
|
||
|
return;
|
||
|
|
||
|
// Make sure that buffer is big enough for filter additions, append if not so
|
||
|
// We add LOG_EXTRA_BUFFER_LEN to account for date, spaces, and HTTP status code.
|
||
|
DWORD cbNeeded = LOG_EXTRA_BUFFER_LEN + pRequest->GetLogBufferSize();
|
||
|
|
||
|
if (cbNeeded > MINBUFSIZE)
|
||
|
{
|
||
|
psz = MyRgAllocNZ(CHAR,cbNeeded);
|
||
|
if (!psz)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pRequest->GenerateLog(psz,&dwToWrite);
|
||
|
WriteData(psz,dwToWrite);
|
||
|
|
||
|
if (szBuffer != psz)
|
||
|
MyFree(psz);
|
||
|
}
|
||
|
|
||
|
void CLog::WriteData(PSTR szBuffer, DWORD_PTR dwToWrite)
|
||
|
{
|
||
|
DWORD dwWritten = 0;
|
||
|
|
||
|
EnterCriticalSection(&m_CritSection);
|
||
|
{
|
||
|
m_dwFileSize += dwToWrite;
|
||
|
// roll over the logs once the maximum size has been reached.
|
||
|
if (m_dwFileSize > m_dwMaxFileSize)
|
||
|
{
|
||
|
MyCloseHandle(m_hLog);
|
||
|
DeleteFile(lpszPrevLog);
|
||
|
MoveFile(lpszCurrentLog,lpszPrevLog);
|
||
|
m_hLog = MyOpenAppendFile(lpszCurrentLog);
|
||
|
m_dwFileSize = dwToWrite;
|
||
|
}
|
||
|
|
||
|
if (m_hLog != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
WriteFile(m_hLog,(LPCVOID) szBuffer,(DWORD)dwToWrite,&dwWritten,NULL);
|
||
|
TraceTag(ttidWebServer, "HTTPD: Wrote log out to file");
|
||
|
}
|
||
|
}
|
||
|
LeaveCriticalSection(&m_CritSection);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Returns the size of the buffer we'll need.
|
||
|
DWORD CHttpRequest::GetLogBufferSize()
|
||
|
{
|
||
|
PHTTP_FILTER_LOG pFLog = m_pFInfo ? m_pFInfo->m_pFLog : NULL;
|
||
|
DWORD cbNeeded;
|
||
|
|
||
|
// remotehost
|
||
|
if (pFLog && pFLog->pszClientHostName)
|
||
|
cbNeeded = strlen(pFLog->pszClientHostName);
|
||
|
else
|
||
|
cbNeeded = LOG_REMOTE_ADDR_SIZE;
|
||
|
|
||
|
if (pFLog && pFLog->pszOperation)
|
||
|
cbNeeded += strlen(pFLog->pszOperation);
|
||
|
else if (m_pszMethod)
|
||
|
cbNeeded += strlen(m_pszMethod);
|
||
|
|
||
|
if (pFLog && pFLog->pszTarget)
|
||
|
cbNeeded += strlen(pFLog->pszTarget);
|
||
|
else if (m_pszURL)
|
||
|
cbNeeded += strlen(m_pszURL);
|
||
|
|
||
|
if (m_pszLogParam)
|
||
|
cbNeeded += strlen(m_pszLogParam);
|
||
|
|
||
|
return cbNeeded;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// Generates the log. First this fcn sees if a filter has created a new
|
||
|
// logging structure, in which case valid data in pFLog will override actual
|
||
|
// server data. In the typical case we just use CHttpRequest info.
|
||
|
|
||
|
void CHttpRequest::GenerateLog(PSTR szBuffer, DWORD_PTR *pdwToWrite)
|
||
|
{
|
||
|
SYSTEMTIME st;
|
||
|
PSTR pszTarget = NULL;
|
||
|
PSTR pszTrav = szBuffer;
|
||
|
int i = 0;
|
||
|
|
||
|
|
||
|
GetSystemTime(&st); // Use GMT time for the log, too
|
||
|
|
||
|
PHTTP_FILTER_LOG pFLog = m_pFInfo ? m_pFInfo->m_pFLog : NULL;
|
||
|
|
||
|
// date and time
|
||
|
pszTrav = WriteHTTPDate(pszTrav,&st,FALSE);
|
||
|
|
||
|
// i += sprintf(szBuffer + i, cszDateOutputFmt,
|
||
|
// rgWkday[st.wDayOfWeek], st.wDay, rgMonth[st.wMonth], st.wYear, st.wHour, st.wMinute, st.wSecond);
|
||
|
|
||
|
// remotehost
|
||
|
if (pFLog && pFLog->pszClientHostName)
|
||
|
{
|
||
|
pszTrav = strcpyEx(pszTrav,pFLog->pszClientHostName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CHAR szAddress[LOG_REMOTE_ADDR_SIZE];
|
||
|
GetRemoteAddress(m_socket,szAddress);
|
||
|
pszTrav = strcpyEx(pszTrav,szAddress);
|
||
|
}
|
||
|
*pszTrav++ = ' ';
|
||
|
|
||
|
// The method (GET, POST, ...)
|
||
|
if (pFLog && pFLog->pszOperation)
|
||
|
pszTrav = strcpyEx(pszTrav,pFLog->pszOperation);
|
||
|
else if (m_pszMethod)
|
||
|
pszTrav = strcpyEx(pszTrav,m_pszMethod);
|
||
|
else
|
||
|
*pszTrav++ = '\t'; // If we get a bad request from browser, m_pszMethod may be NULL.
|
||
|
|
||
|
*pszTrav++ = ' ';
|
||
|
|
||
|
// target (URI)
|
||
|
if (pFLog && pFLog->pszTarget)
|
||
|
pszTarget = (PSTR) pFLog->pszTarget;
|
||
|
else
|
||
|
pszTarget = m_pszURL;
|
||
|
|
||
|
|
||
|
// like IIS, we convert any spaces in here into "+" signs.
|
||
|
// NOTES: IIS does this for both filter changed targets and URLS.
|
||
|
// It only does it with spaces, doesn't convert \r\n, tabs, or any other escape
|
||
|
// characters
|
||
|
|
||
|
if (pszTarget) // on a bad request, m_pszURL may = NULL. Check.
|
||
|
{
|
||
|
while ( *pszTarget)
|
||
|
{
|
||
|
if ( *pszTarget == ' ')
|
||
|
*pszTrav++ = '+';
|
||
|
else
|
||
|
*pszTrav++ = *pszTarget;
|
||
|
pszTarget++;
|
||
|
}
|
||
|
}
|
||
|
*pszTrav++ = ' ';
|
||
|
|
||
|
|
||
|
// status
|
||
|
if (pFLog)
|
||
|
_itoa(pFLog->dwHttpStatus,pszTrav,10);
|
||
|
else
|
||
|
_itoa(rgStatus[m_rs].dwStatusNumber,pszTrav,10);
|
||
|
pszTrav = strchr(pszTrav,'\0');
|
||
|
*pszTrav++ = ' ';
|
||
|
|
||
|
if (m_pszLogParam) // ISAPI Extension logging has modified something
|
||
|
pszTrav = strcpyEx(pszTrav,m_pszLogParam);
|
||
|
*pszTrav++ = '\r';
|
||
|
*pszTrav++ = '\n';
|
||
|
*pszTrav= '\0';
|
||
|
|
||
|
|
||
|
*pdwToWrite = pszTrav - szBuffer;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#endif //!WEB_SERVER_LOGGING
|