Darwin-Streaming-Server/APICommonCode/QTSSRollingLog.cpp

551 lines
17 KiB
C++
Raw Permalink Normal View History

/*
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*
*/
/*
File: QTSSRollingLog.cpp
Contains: Implements object defined in .h file
*/
#include <time.h>
#include <stdlib.h>
#include "SafeStdLib.h"
#include <string.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#ifndef __Win32__
#include <sys/time.h>
#endif
#include "QTSSRollingLog.h"
#include "OS.h"
#include "OSMemory.h"
#include "OSArrayObjectDeleter.h"
#include "ResizeableStringFormatter.h"
static Bool16 sCloseOnWrite = true;
QTSSRollingLog::QTSSRollingLog() :
fLog(NULL),
fLogCreateTime(-1),
fLogFullPath(NULL),
fAppendDotLog(true),
fLogging(true)
{
this->SetTaskName("QTSSRollingLog");
}
QTSSRollingLog::~QTSSRollingLog()
{
//
// Log should already be closed, but just in case...
this->CloseLog();
delete [] fLogFullPath;
}
// Set this to true to get the log to close the file between writes.
void QTSSRollingLog::SetCloseOnWrite(Bool16 closeOnWrite)
{
sCloseOnWrite = closeOnWrite;
}
Bool16 QTSSRollingLog::IsLogEnabled()
{
return sCloseOnWrite || (fLog != NULL);
}
void QTSSRollingLog::WriteToLog(char* inLogData, Bool16 allowLogToRoll)
{
OSMutexLocker locker(&fMutex);
if (fLogging == false)
return;
if (sCloseOnWrite && fLog == NULL)
this->EnableLog(fAppendDotLog ); //re-open log file before we write
if (allowLogToRoll)
(void)this->CheckRollLog();
if (fLog != NULL)
{
qtss_fprintf(fLog, "%s", inLogData);
::fflush(fLog);
}
if (sCloseOnWrite)
this->CloseLog( false );
}
Bool16 QTSSRollingLog::RollLog()
{
OSMutexLocker locker(&fMutex);
//returns false if an error occurred, true otherwise
//close the old file.
if (fLog != NULL)
this->CloseLog();
if (fLogging == false)
return false;
//rename the old file
Bool16 result = this->RenameLogFile(fLogFullPath);
if (result)
this->EnableLog(fAppendDotLog);//re-opens log file
return result;
}
char* QTSSRollingLog::GetLogPath(char *extension)
{
char *thePath = NULL;
OSCharArrayDeleter logDir(this->GetLogDir()); //The string passed into this function is a copy
OSCharArrayDeleter logName(this->GetLogName()); //The string passed into this function is a copy
ResizeableStringFormatter formatPath(NULL,0); //allocate the buffer
formatPath.PutFilePath(logDir, logName);
if ( extension != NULL)
formatPath.Put(extension);
formatPath.PutTerminator();
thePath = formatPath.GetBufPtr();
formatPath.Set(NULL,0); //don't delete buffer we are returning the path as a result
return thePath;
}
void QTSSRollingLog::EnableLog( Bool16 appendDotLog )
{
//
// Start this object running!
this->Signal(Task::kStartEvent);
OSMutexLocker locker(&fMutex);
fAppendDotLog = appendDotLog;
if (fLogging == false)
return;
char *extension = ".log";
if (!appendDotLog)
extension = NULL;
delete[] fLogFullPath;
fLogFullPath = this->GetLogPath(extension);
//we need to make sure that when we create a new log file, we write the
//log header at the top
Bool16 logExists = this->DoesFileExist(fLogFullPath);
//create the log directory if it doesn't already exist
if (!logExists)
{
OSCharArrayDeleter tempDir(this->GetLogDir());
OS::RecursiveMakeDir(tempDir.GetObject());
}
fLog = ::fopen(fLogFullPath, "a+");//open for "append"
if (NULL != fLog)
{
if (!logExists) //the file is new, write a log header with the create time of the file.
{ fLogCreateTime = this->WriteLogHeader(fLog);
#if __MacOSX__
(void) ::chown(fLogFullPath, 76, (gid_t)-1);//set owner to user qtss.
#endif
}
else //the file is old, read the log header to find the create time of the file.
fLogCreateTime = this->ReadLogHeader(fLog);
}
}
void QTSSRollingLog::CloseLog( Bool16 leaveEnabled )
{
OSMutexLocker locker(&fMutex);
if (leaveEnabled)
sCloseOnWrite = true;
if (fLog != NULL)
{
::fclose(fLog);
fLog = NULL;
}
}
//returns false if some error has occurred
Bool16 QTSSRollingLog::FormatDate(char *ioDateBuffer, Bool16 logTimeInGMT)
{
Assert(NULL != ioDateBuffer);
//use ansi routines for getting the date.
time_t calendarTime = ::time(NULL);
Assert(-1 != calendarTime);
if (-1 == calendarTime)
return false;
struct tm* theTime = NULL;
struct tm timeResult;
if (logTimeInGMT)
theTime = ::qtss_gmtime(&calendarTime, &timeResult);
else
theTime = qtss_localtime(&calendarTime, &timeResult);
Assert(NULL != theTime);
if (NULL == theTime)
return false;
// date time needs to look like this for extended log file format: 2001-03-16 23:34:54
// this wonderful ANSI routine just does it for you.
// the format is YYYY-MM-DD HH:MM:SS
// the date time is in GMT, unless logTimeInGMT is false, in which case
// the time logged is local time
//qtss_strftime(ioDateBuffer, kMaxDateBufferSize, "%d/%b/%Y:%H:%M:%S", theLocalTime);
qtss_strftime(ioDateBuffer, kMaxDateBufferSizeInBytes, "%Y-%m-%d %H:%M:%S", theTime);
return true;
}
Bool16 QTSSRollingLog::CheckRollLog()
{
//returns false if an error occurred, true otherwise
if (fLog == NULL)
return true;
//first check to see if log rolling should happen because of a date interval.
//This takes precedence over size based log rolling
// this is only if a client connects just between 00:00:00 and 00:01:00
// since the task object runs every minute
// when an entry is written to the log file, only the file size must be checked
// to see if it exceeded the limits
// the roll interval should be monitored in a task object
// and rolled at midnight if the creation time has exceeded.
if ((-1 != fLogCreateTime) && (0 != this->GetRollIntervalInDays()))
{
time_t logCreateTimeMidnight = -1;
QTSSRollingLog::ResetToMidnight(&fLogCreateTime, &logCreateTimeMidnight);
Assert(logCreateTimeMidnight != -1);
time_t calendarTime = ::time(NULL);
Assert(-1 != calendarTime);
if (-1 != calendarTime)
{
double theExactInterval = ::difftime(calendarTime, logCreateTimeMidnight);
SInt32 theCurInterval = (SInt32)::floor(theExactInterval);
//transfer_roll_interval is in days, theCurInterval is in seconds
SInt32 theRollInterval = this->GetRollIntervalInDays() * 60 * 60 * 24;
if (theCurInterval > theRollInterval)
return this->RollLog();
}
}
//now check size based log rolling
UInt32 theCurrentPos = ::ftell(fLog);
//max_transfer_log_size being 0 is a signal to ignore the setting.
if ((this->GetMaxLogBytes() != 0) &&
(theCurrentPos > this->GetMaxLogBytes()))
return this->RollLog();
return true;
}
Bool16 QTSSRollingLog::RenameLogFile(const char* inFileName)
{
//returns false if an error occurred, true otherwise
//this function takes care of renaming a log file from "myLogFile.log" to
//"myLogFile.981217000.log" or if that is already taken, myLogFile.981217001.log", etc
//fix 2287086. Rolled log name can be different than original log name
//GetLogDir returns a copy of the log dir
OSCharArrayDeleter logDirectory(this->GetLogDir());
//create the log directory if it doesn't already exist
OS::RecursiveMakeDir(logDirectory.GetObject());
//GetLogName returns a copy of the log name
OSCharArrayDeleter logBaseName(this->GetLogName());
//QTStreamingServer.981217003.log
//format the new file name
OSCharArrayDeleter theNewNameBuffer(NEW char[::strlen(logDirectory) + kMaxFilenameLengthInBytes + 3]);
//copy over the directory - append a '/' if it's missing
::strcpy(theNewNameBuffer, logDirectory);
if (theNewNameBuffer[::strlen(theNewNameBuffer)-1] != kPathDelimiterChar)
{
::strcat(theNewNameBuffer, kPathDelimiterString);
}
//copy over the base filename
::strcat(theNewNameBuffer, logBaseName.GetObject());
//append the date the file was created
struct tm timeResult;
struct tm* theLocalTime = qtss_localtime(&fLogCreateTime, &timeResult);
char timeString[10];
qtss_strftime(timeString, 10, ".%y%m%d", theLocalTime);
::strcat(theNewNameBuffer, timeString);
SInt32 theBaseNameLength = ::strlen(theNewNameBuffer);
//loop until we find a unique name to rename this file
//and append the log number and suffix
SInt32 theErr = 0;
for (SInt32 x = 0; (theErr == 0) && (x<=1000); x++)
{
if (x == 1000) //we don't have any digits left, so just reuse the "---" until tomorrow...
{
//add a bogus log number and exit the loop
qtss_sprintf(theNewNameBuffer + theBaseNameLength, "---.log");
break;
}
//add the log number & suffix
qtss_sprintf(theNewNameBuffer + theBaseNameLength, "%03"_S32BITARG_".log", x);
//assume that when ::stat returns an error, it is becase
//the file doesnt exist. Once that happens, we have a unique name
// csl - shouldn't you watch for a ENOENT result?
struct stat theIdontCare;
theErr = ::stat(theNewNameBuffer, &theIdontCare);
WarnV((theErr == 0 || OSThread::GetErrno() == ENOENT), "unexpected stat error in RenameLogFile");
}
//rename the file. Use posix rename function
int result = ::rename(inFileName, theNewNameBuffer);
if (result == -1)
theErr = (SInt32)OSThread::GetErrno();
else
theErr = 0;
WarnV(theErr == 0 , "unexpected rename error in RenameLogFile");
if (theErr != 0)
return false;
else
return true;
}
Bool16 QTSSRollingLog::DoesFileExist(const char *inPath)
{
struct stat theStat;
int theErr = ::stat(inPath, &theStat);
if (theErr != 0)
return false;
else
return true;
}
time_t QTSSRollingLog::WriteLogHeader(FILE* inFile)
{
OSMutexLocker locker(&fMutex);
//The point of this header is to record the exact time the log file was created,
//in a format that is easy to parse through whenever we open the file again.
//This is necessary to support log rolling based on a time interval, and POSIX doesn't
//support a create date in files.
time_t calendarTime = ::time(NULL);
Assert(-1 != calendarTime);
if (-1 == calendarTime)
return -1;
struct tm timeResult;
struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult);
Assert(NULL != theLocalTime);
if (NULL == theLocalTime)
return -1;
//
// Files are always created at hour 0 (we don't care about the time, we always
// want them to roll at midnight.
//theLocalTime->tm_hour = 0;
//theLocalTime->tm_min = 0;
//theLocalTime->tm_sec = 0;
char tempbuf[1024];
qtss_strftime(tempbuf, sizeof(tempbuf), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime);
//qtss_sprintf(tempbuf, "#Log File Created On: %d/%d/%d %d:%d:%d %d:%d:%d GMT\n",
// theLocalTime->tm_mon, theLocalTime->tm_mday, theLocalTime->tm_year,
// theLocalTime->tm_hour, theLocalTime->tm_min, theLocalTime->tm_sec,
// theLocalTime->tm_yday, theLocalTime->tm_wday, theLocalTime->tm_isdst);
this->WriteToLog(tempbuf, !kAllowLogToRoll);
return this->ReadLogHeader(inFile);
}
time_t QTSSRollingLog::ReadLogHeader(FILE* inFile)
{
OSMutexLocker locker(&fMutex);
//This function reads the header in a log file, returning the time stored
//at the beginning of this file. This value is used to determine when to
//roll the log.
//Returns -1 if the header is bogus. In that case, just ignore time based log rolling
//first seek to the beginning of the file
SInt32 theCurrentPos = ::ftell(inFile);
if (theCurrentPos == -1)
return -1;
(void)::rewind(inFile);
const UInt32 kMaxHeaderLength = 500;
char theFirstLine[kMaxHeaderLength];
if (NULL == ::fgets(theFirstLine, kMaxHeaderLength, inFile))
{
::fseek(inFile, 0, SEEK_END);
return -1;
}
::fseek(inFile, 0, SEEK_END);
struct tm theFileCreateTime;
// Zero out fields we will not be using
theFileCreateTime.tm_isdst = -1;
theFileCreateTime.tm_wday = 0;
theFileCreateTime.tm_yday = 0;
//if (EOF == ::sscanf(theFirstLine, "#Log File Created On: %d/%d/%d %d:%d:%d\n",
// &theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year,
// &theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec))
// return -1;
//
// We always want to roll at hour 0, so ignore the time of creation
if (EOF == ::sscanf(theFirstLine, "#Log File Created On: %d/%d/%d %d:%d:%d\n",
&theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year,
&theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec))
return -1;
//
// It should be like this anyway, but if the log file is legacy, then...
// No! The log file will have the actual time in it but we shall return the exact time
//theFileCreateTime.tm_hour = 0;
//theFileCreateTime.tm_min = 0;
//theFileCreateTime.tm_sec = 0;
// Actually, it seems like all platforms need this.
//#ifdef __Win32__
// Win32 has slightly different atime basis than UNIX.
theFileCreateTime.tm_yday--;
theFileCreateTime.tm_mon--;
theFileCreateTime.tm_year -= 1900;
//#endif
#if 0
//use ansi routines for getting the date.
time_t calendarTime = ::time(NULL);
Assert(-1 != calendarTime);
if (-1 == calendarTime)
return false;
struct tm timeResult;
struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult);
Assert(NULL != theLocalTime);
if (NULL == theLocalTime)
return false;
#endif
//ok, we should have a filled in tm struct. Convert it to a time_t.
//time_t thePoopTime = ::mktime(theLocalTime);
time_t theTime = ::mktime(&theFileCreateTime);
return theTime;
}
SInt64 QTSSRollingLog::Run()
{
//
// If we are going away, just return
EventFlags events = this->GetEvents();
if (events & Task::kKillEvent)
return -1;
OSMutexLocker locker(&fMutex);
UInt32 theRollInterval = (this->GetRollIntervalInDays()) * 60 * 60 * 24;
if((fLogCreateTime != -1) && (fLog != NULL))
{
time_t logRollTimeMidnight = -1;
this->ResetToMidnight(&fLogCreateTime, &logRollTimeMidnight);
Assert(logRollTimeMidnight != -1);
if(theRollInterval != 0)
{
time_t calendarTime = ::time(NULL);
Assert(-1 != calendarTime);
double theExactInterval = ::difftime(calendarTime, logRollTimeMidnight);
if(theExactInterval > 0) {
UInt32 theCurInterval = (UInt32)::floor(theExactInterval);
if (theCurInterval >= theRollInterval)
this->RollLog();
}
}
}
return 60 * 1000;
}
void QTSSRollingLog::ResetToMidnight(time_t* inTimePtr, time_t* outTimePtr)
{
if(*inTimePtr == -1)
{
*outTimePtr = -1;
return;
}
struct tm timeResult;
struct tm* theLocalTime = qtss_localtime(inTimePtr, &timeResult);
Assert(theLocalTime != NULL);
theLocalTime->tm_hour = 0;
theLocalTime->tm_min = 0;
theLocalTime->tm_sec = 0;
// some weird stuff
//theLocalTime->tm_yday--;
//theLocalTime->tm_mon--;
//theLocalTime->tm_year -= 1900;
*outTimePtr = ::mktime(theLocalTime);
}