openclonk/src/lib/C4Log.cpp

363 lines
8.7 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Log file handling */
#include "C4Include.h"
#include "lib/C4Log.h"
#include "c4group/C4Components.h"
#include "editor/C4Console.h"
#include "game/C4GraphicsSystem.h"
#include "graphics/C4Shader.h"
#include "gui/C4GameLobby.h"
#include "lib/C4LogBuf.h"
#include "network/C4Network2.h"
#include "platform/C4Window.h"
#include "script/C4AulDebug.h"
#ifdef HAVE_SYS_FILE_H
#include <sys/file.h>
#endif
#if defined(HAVE_SHARE_H) || defined(_WIN32)
#include <share.h>
#endif
FILE *C4LogFile=nullptr;
FILE *C4ShaderLogFile = nullptr;
time_t C4LogStartTime;
StdStrBuf sLogFileName;
StdStrBuf sFatalError;
bool OpenLog()
{
// open
sLogFileName = C4CFN_Log; int iLog = 2;
#ifdef _WIN32
while (!(C4LogFile = _fsopen(Config.AtUserDataPath(sLogFileName.getData()), "wt", _SH_DENYWR)))
#elif defined(HAVE_SYS_FILE_H)
int fd = 0;
while (!(fd = open(Config.AtUserDataPath(sLogFileName.getData()), O_WRONLY | O_CREAT, 0644)) || flock(fd, LOCK_EX|LOCK_NB))
#else
while (!(C4LogFile = fopen(Config.AtUserDataPath(sLogFileName.getData()), "wb")))
#endif
{
// Already locked by another instance?
#if !defined(_WIN32) && defined(HAVE_SYS_FILE_H)
if (fd) close(fd);
#else
if (C4LogFile) fclose(C4LogFile);
#endif
// If the file does not yet exist, the directory is r/o
// don't go on then, or we have an infinite loop
if (access(Config.AtUserDataPath(sLogFileName.getData()), 0))
return false;
// try different name
sLogFileName.Format(C4CFN_LogEx, iLog++);
}
#if !defined(_WIN32) && defined(HAVE_SYS_FILE_H)
ftruncate(fd, 0);
C4LogFile = fdopen(fd, "wb");
#endif
// save start time
time(&C4LogStartTime);
return true;
}
bool OpenExtraLogs()
{
// shader log in editor mode (only one file)
bool success = true;
if (C4Shader::IsLogging())
{
#ifdef _WIN32
C4ShaderLogFile = _fsopen(Config.AtUserDataPath(C4CFN_LogShader), "wt", _SH_DENYWR);
#elif defined(HAVE_SYS_FILE_H)
C4ShaderLogFile = fopen(Config.AtUserDataPath(C4CFN_LogShader), "wb");
if (C4ShaderLogFile && flock(fileno(C4ShaderLogFile), LOCK_EX | LOCK_NB) != 0)
{
DebugLog("Couldn't lock shader log file, closing.");
fclose(C4ShaderLogFile);
C4ShaderLogFile = nullptr;
}
#else
C4ShaderLogFile = fopen(Config.AtUserDataPath(C4CFN_LogShader), "wb");
#endif
if (!C4ShaderLogFile) success = false;
}
return success;
}
bool CloseLog()
{
// close
if (C4ShaderLogFile) fclose(C4ShaderLogFile);
C4ShaderLogFile = nullptr;
if (C4LogFile) fclose(C4LogFile);
C4LogFile = nullptr;
// ok
return true;
}
int GetLogFD()
{
if (C4LogFile)
return fileno(C4LogFile);
else
return -1;
}
bool LogSilent(const char *szMessage, bool fConsole)
{
if (!Application.AssertMainThread()) return false;
// security
if (!szMessage) return false;
// add timestamp
time_t timenow; time(&timenow);
StdStrBuf TimeMessage;
TimeMessage.SetLength(11 + SLen(szMessage) + 1);
strftime(TimeMessage.getMData(), 11 + 1, "[%H:%M:%S] ", localtime(&timenow));
// output until all data is written
const char *pSrc = szMessage;
do
{
// timestamp will always be that length
char *pDest = TimeMessage.getMData() + 11;
// copy rest of message, skip tags
C4Markup Markup(false);
while (*pSrc)
{
Markup.SkipTags(&pSrc);
// break on crlf
while (*pSrc == '\r') pSrc++;
if (*pSrc == '\n') { pSrc++; break; }
// copy otherwise
if (*pSrc) *pDest++ = *pSrc++;
}
*pDest++='\n'; *pDest = '\0';
// Save into log file
if (C4LogFile)
{
fputs(TimeMessage.getData(),C4LogFile);
fflush(C4LogFile);
}
// Save into record log file, if available
if(Control.GetRecord())
{
Control.GetRecord()->GetLogFile()->Write(TimeMessage.getData(), TimeMessage.getLength());
#ifdef IMMEDIATEREC
Control.GetRecord()->GetLogFile()->Flush();
#endif
}
// Write to console
if (fConsole)
{
#if defined(_WIN32)
// debug: output to VC console when running with debugger
// Otherwise, print to stdout to allow capturing the log.
if (IsDebuggerPresent())
OutputDebugString(TimeMessage.GetWideChar());
else
#endif
{
fputs(TimeMessage.getData(),stdout);
fflush(stdout);
}
}
}
while (*pSrc);
return true;
}
bool LogSilent(const char *szMessage)
{
return LogSilent(szMessage, false);
}
int iDisableLog = 0;
bool Log(const char *szMessage)
{
if (!Application.AssertMainThread()) return false;
if (iDisableLog) return true;
// security
if (!szMessage) return false;
#ifndef NOAULDEBUG
// Pass on to debugger
if (C4AulDebug *pDebug = C4AulDebug::GetDebugger())
pDebug->OnLog(szMessage);
#endif
// Pass on to console
Console.Out(szMessage);
// pass on to lobby
C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
if (pLobby) pLobby->OnLog(szMessage);
// Add message to log buffer
bool fNotifyMsgBoard = false;
if (::GraphicsSystem.MessageBoard)
{
::GraphicsSystem.MessageBoard->AddLog(szMessage);
fNotifyMsgBoard = true;
}
// log
LogSilent(szMessage, true);
// Notify message board
if (fNotifyMsgBoard) ::GraphicsSystem.MessageBoard->LogNotify();
return true;
}
bool LogFatal(const char *szMessage)
{
if (!szMessage) szMessage = "(null)";
// add to fatal error message stack - if not already in there (avoid duplication)
if (!SSearch(sFatalError.getData(), szMessage))
{
if (!sFatalError.isNull()) sFatalError.AppendChar('|');
sFatalError.Append(szMessage);
}
// write to log - note that Log might overwrite a static buffer also used in szMessage
return !!Log(FormatString(LoadResStr("IDS_ERR_FATAL"), szMessage).getData());
}
void ResetFatalError()
{
sFatalError.Clear();
}
const char *GetFatalError()
{
return sFatalError.getData();
}
bool LogF(const char *strMessage, ...)
{
va_list args; va_start(args, strMessage);
// Compose formatted message
StdStrBuf Buf;
Buf.FormatV(strMessage, args);
// Log
return Log(Buf.getData());
}
bool LogSilentF(const char *strMessage, ...)
{
va_list args; va_start(args, strMessage);
// Compose formatted message
StdStrBuf Buf;
Buf.FormatV(strMessage, args);
// Log
return LogSilent(Buf.getData());
}
bool DebugLog(const char *strMessage)
{
if (Game.DebugMode)
return Log(strMessage);
else
return LogSilent(strMessage);
}
bool DebugLogF(const char *strMessage ...)
{
va_list args; va_start(args, strMessage);
StdStrBuf Buf;
Buf.FormatV(strMessage, args);
return DebugLog(Buf.getData());
}
size_t GetLogPos()
{
// get current log position
return FileSize(sLogFileName.getData());
}
bool GetLogSection(size_t iStart, size_t iLength, StdStrBuf &rsOut)
{
if (!iLength) { rsOut.Clear(); return true; }
// read section from log file
StdStrBuf BufOrig;
if (!BufOrig.LoadFromFile(sLogFileName.getData())) return false;
char *szBuf = BufOrig.getMData();
size_t iSize = BufOrig.getSize(); // size excluding terminator
// reduce to desired buffer section
if (iStart > iSize) iStart = iSize;
if (iStart + iLength > iSize) iLength = iSize - iStart;
szBuf += iStart; szBuf[iLength] = '\0';
// strip timestamps; convert linebreaks to Clonk-linebreaks '|'
char *szPosWrite=szBuf; const char *szPosRead=szBuf;
while (*szPosRead)
{
// skip timestamp
if (*szPosRead == '[')
while (*szPosRead && *szPosRead != ']') { --iSize; ++szPosRead; }
// skip whitespace behind timestamp
if (!*szPosRead) break;
szPosRead++;
// copy data until linebreak
size_t iLen=0;
while (*szPosRead && *szPosRead != 0x0d && *szPosRead != 0x0a)
{ ++szPosRead; ++iLen; }
if (iLen && szPosRead-iLen != szPosWrite) memmove(szPosWrite, szPosRead-iLen, iLen);
szPosWrite += iLen;
// skip additional linebreaks
while (*szPosRead == 0x0d || *szPosRead == 0x0a) ++szPosRead;
// write a Clonk-linebreak
if (*szPosRead) *szPosWrite++ = '|';
}
// done; create string buffer from data
rsOut.Copy(szBuf, szPosWrite - szBuf);
// done, success
return true;
}
bool ShaderLog(const char *szMessage)
{
// security
if (!C4ShaderLogFile) return false;
if (!Application.AssertMainThread()) return false;
if (!szMessage) return false;
// output into shader log file
fputs(szMessage, C4ShaderLogFile);
fputs("\n", C4ShaderLogFile);
fflush(C4ShaderLogFile);
return true;
}
bool ShaderLogF(const char *strMessage ...)
{
va_list args; va_start(args, strMessage);
StdStrBuf Buf;
Buf.FormatV(strMessage, args);
return ShaderLog(Buf.getData());
}