/* * * @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: QTSSErrorLogModule.cpp Contains: Implementation of object defined in .h file. */ #include #include "QTSSErrorLogModule.h" #include "QTSSMessages.h" #include "QTSSRollingLog.h" #include "QTSServerInterface.h" #include "QTSSExpirationDate.h" #include "OSMemory.h" #include "OS.h" #include "Task.h" // STATIC FUNCTIONS // The dispatch function for this module static QTSS_Error QTSSErrorLogModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock); // A service routine allowing other modules to roll the log static QTSS_Error RollErrorLog(QTSS_ServiceFunctionArgsPtr inArgs); static QTSS_Error Register(QTSS_Register_Params* inParams); static QTSS_Error Shutdown(); static QTSS_Error LogError(QTSS_RoleParamPtr inParamBlock); static void CheckErrorLogState(); static QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams); static void WriteStartupMessage(); static void WriteShutdownMessage(); typedef char* LevelMsg; static LevelMsg sErrorLevel[] = { "FATAL:", "WARNING:", "INFO:", "ASSERT:", "DEBUG:" }; // QTSSERRORLOG CLASS DEFINITION class QTSSErrorLog : public QTSSRollingLog { public: QTSSErrorLog() : QTSSRollingLog() {this->SetTaskName("QTSSErrorLog");} virtual ~QTSSErrorLog() {} virtual char* GetLogName() { return QTSServerInterface::GetServer()->GetPrefs()->GetErrorLogName();} virtual char* GetLogDir() { return QTSServerInterface::GetServer()->GetPrefs()->GetErrorLogDir();} virtual UInt32 GetRollIntervalInDays() { return QTSServerInterface::GetServer()->GetPrefs()->GetErrorRollIntervalInDays();} virtual UInt32 GetMaxLogBytes() { return QTSServerInterface::GetServer()->GetPrefs()->GetMaxErrorLogBytes();} }; //ERRORLOGCHECKTASK CLASS DEFINITION class ErrorLogCheckTask : public Task { public: ErrorLogCheckTask() : Task() {this->SetTaskName("ErrorLogCheckTask"); this->Signal(Task::kStartEvent); } virtual ~ErrorLogCheckTask() {} private: virtual SInt64 Run(); }; const UInt32 kMaxLogStringLen = 2172; // STATIC DATA static OSMutex* sLogMutex = NULL;//Log module isn't reentrant static QTSSErrorLog* sErrorLog = NULL; static char sLastErrorString[kMaxLogStringLen] = ""; static int sDupErrorStringCount = 0; static Bool16 sStartedUp = false; static ErrorLogCheckTask* sErrorLogCheckTask = NULL; // FUNCTION IMPLEMENTATIONS QTSS_Error QTSSErrorLogModule_Main(void* inPrivateArgs) { return _stublibrary_main(inPrivateArgs, QTSSErrorLogModuleDispatch); } QTSS_Error QTSSErrorLogModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock) { switch (inRole) { case QTSS_Register_Role: return Register(&inParamBlock->regParams); case QTSS_StateChange_Role: return StateChange(&inParamBlock->stateChangeParams); case QTSS_ErrorLog_Role: return LogError(inParamBlock); case QTSS_Shutdown_Role: return Shutdown(); } return QTSS_NoErr; } // ROLE METHODS QTSS_Error Register(QTSS_Register_Params* inParams) { sLogMutex = NEW OSMutex(); // Do role & service setup (void)QTSS_AddRole(QTSS_ErrorLog_Role); (void)QTSS_AddRole(QTSS_Shutdown_Role); (void)QTSS_AddRole(QTSS_StateChange_Role); (void)QTSS_AddService("RollErrorLog", &RollErrorLog); // Unlike most modules, all initialization for this module happens in // the register role. This is so that this error log can begin logging // errors ASAP. CheckErrorLogState(); WriteStartupMessage(); // Tell the server our name! static char* sModuleName = "QTSSErrorLogModule"; ::strcpy(inParams->outModuleName, sModuleName); sErrorLogCheckTask = NEW ErrorLogCheckTask(); return QTSS_NoErr; } QTSS_Error Shutdown() { WriteShutdownMessage(); if (sErrorLogCheckTask != NULL) { // sErrorLogCheckTask is a task object, so don't delete it directly // instead we signal it to kill itself. sErrorLogCheckTask->Signal(Task::kKillEvent); sErrorLogCheckTask = NULL; } return QTSS_NoErr; } QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams) { if (stateChangeParams->inNewState == qtssIdleState) { WriteShutdownMessage(); } else if (stateChangeParams->inNewState == qtssRunningState) { // Always force our preferences to be reread when we change // the server's state back to the start -- [sfu] QTSS_ServiceID id; (void) QTSS_IDForService(QTSS_REREAD_PREFS_SERVICE, &id); (void) QTSS_DoService(id, NULL); WriteStartupMessage(); } return QTSS_NoErr; } QTSS_Error LogError(QTSS_RoleParamPtr inParamBlock) { Assert(NULL != inParamBlock->errorParams.inBuffer); if (inParamBlock->errorParams.inBuffer == NULL) return QTSS_NoErr; UInt16 verbLvl = (UInt16) inParamBlock->errorParams.inVerbosity; if (verbLvl >= qtssIllegalVerbosity) verbLvl = qtssFatalVerbosity; QTSServerPrefs* thePrefs = QTSServerInterface::GetServer()->GetPrefs(); OSMutexLocker locker(sLogMutex); if (thePrefs->GetErrorLogVerbosity() >= inParamBlock->errorParams.inVerbosity) { size_t inStringLen = ::strlen(inParamBlock->errorParams.inBuffer); size_t lastStringLen = ::strlen(sLastErrorString); Bool16 isDuplicate = true; if (inStringLen > sizeof(sLastErrorString) -1) //truncate to max char buffer subtract \0 terminator inStringLen = sizeof(sLastErrorString) -1; if (lastStringLen != inStringLen) //same size? isDuplicate = false; // different sizes else if (::strncmp(inParamBlock->errorParams.inBuffer, sLastErrorString, lastStringLen ) != 0 ) //same chars? isDuplicate = false; //different chars //is this error message the same as the last one we received? if ( isDuplicate ) { //yes? increment count and bail if it's not the first time we've seen this message (otherwise fall thourhg and write it to the log) sDupErrorStringCount++; return QTSS_NoErr; } else { //we have a new error message, write a "previous line" message before writing the new log entry if ( sDupErrorStringCount >= 1 ) { /*** clean this up - lots of duplicate code ***/ //The error logger is the bottleneck for any and all messages printed by the server. //For debugging purposes, these messages can be printed to stdout as well. if (thePrefs->IsScreenLoggingEnabled()) qtss_printf("--last message repeated %d times\n", sDupErrorStringCount); CheckErrorLogState(); if (sErrorLog == NULL) return QTSS_NoErr; //timestamp the error char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); //for now, just ignore the error. if (!result) theDateBuffer[0] = '\0'; char tempBuffer[kMaxLogStringLen]; qtss_snprintf(tempBuffer,sizeof(tempBuffer), "%s: --last message repeated %d times\n", theDateBuffer, sDupErrorStringCount); sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); sDupErrorStringCount = 0; } ::strlcpy(sLastErrorString, inParamBlock->errorParams.inBuffer, sizeof(sLastErrorString)); } //The error logger is the bottleneck for any and all messages printed by the server. //For debugging purposes, these messages can be printed to stdout as well. if (thePrefs->IsScreenLoggingEnabled()) qtss_printf("%s %s\n", sErrorLevel[verbLvl], inParamBlock->errorParams.inBuffer); CheckErrorLogState(); if (sErrorLog == NULL) return QTSS_NoErr; //timestamp the error char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); //for now, just ignore the error. if (!result) theDateBuffer[0] = '\0'; char tempBuffer[kMaxLogStringLen]; qtss_snprintf(tempBuffer,sizeof(tempBuffer), "%s: %s %s\n", theDateBuffer, sErrorLevel[verbLvl], inParamBlock->errorParams.inBuffer); tempBuffer[sizeof(tempBuffer)-2] = '\n'; //make sure the entry has a line feed before the \0 terminator tempBuffer[sizeof(tempBuffer)-1] = '\0'; //make sure it is 0 terminated. sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); } return QTSS_NoErr; } void CheckErrorLogState() { //this function makes sure the logging state is in synch with the preferences. //extern variable declared in QTSSPreferences.h QTSServerPrefs* thePrefs = QTSServerInterface::GetServer()->GetPrefs(); //check error log. if ((NULL == sErrorLog) && (thePrefs->IsErrorLogEnabled())) { sErrorLog = NEW QTSSErrorLog(); sErrorLog->EnableLog(); } if ((NULL != sErrorLog) && (!thePrefs->IsErrorLogEnabled())) { sErrorLog->Delete(); //sErrorLog is a task object, so don't delete it directly sErrorLog = NULL; } } // SERVICE ROUTINES QTSS_Error RollErrorLog(QTSS_ServiceFunctionArgsPtr /*inArgs*/) { OSMutexLocker locker(sLogMutex); if (sErrorLog != NULL) sErrorLog->RollLog(); return QTSS_NoErr; } void WriteStartupMessage() { if (sStartedUp) return; sStartedUp = true; //format a date for the startup time char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); char tempBuffer[kMaxLogStringLen]; if (result) qtss_snprintf(tempBuffer,sizeof(tempBuffer), "# Streaming STARTUP %s\n", theDateBuffer); // log startup message to error log as well. if ((result) && (sErrorLog != NULL)) sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); //write the expire date to the log if ( QTSSExpirationDate::WillSoftwareExpire() && sErrorLog != NULL ) { QTSSExpirationDate::sPrintExpirationDate(tempBuffer); sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); } } void WriteShutdownMessage() { if (!sStartedUp) return; sStartedUp = false; //log shutdown message //format a date for the shutdown time char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); char tempBuffer[kMaxLogStringLen]; if (result) qtss_snprintf(tempBuffer, sizeof(tempBuffer), "# Streaming SHUTDOWN %s\n", theDateBuffer); if ( result && sErrorLog != NULL ) sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); } // This task runs once an hour to check and see if the log needs to roll. SInt64 ErrorLogCheckTask::Run() { static Bool16 firstTime = true; // don't check the log for rolling the first time we run. if (firstTime) { firstTime = false; } else { Bool16 success = false; if (sErrorLog != NULL && sErrorLog->IsLogEnabled()) success = sErrorLog->CheckRollLog(); Assert(success); } // execute this task again in one hour. return (60*60*1000); }