1 // Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
6 #include <CryCore/Platform/CryWindows.h>
7 #include <CryEntitySystem/IEntitySystem.h>
8 #include <CrySerialization/IArchiveHost.h>
9 #include <CrySerialization/STL.h>
10 #include <CrySchematyc2/TemplateUtils/TemplateUtils_ScopedConnection.h>
11 #include <CrySystem/File/ICryPak.h>
12 #include <CrySystem/ConsoleRegistration.h>
16 SERIALIZATION_ENUM_BEGIN_NESTED(Schematyc2
, ELogMessageType
, "Schematyc Log Message Type")
17 SERIALIZATION_ENUM(Schematyc2::ELogMessageType::Comment
, "Comment", "Comment")
18 SERIALIZATION_ENUM(Schematyc2::ELogMessageType::Warning
, "Warning", "Warning")
19 SERIALIZATION_ENUM(Schematyc2::ELogMessageType::Error
, "Error", "Error")
20 SERIALIZATION_ENUM(Schematyc2::ELogMessageType::CriticalError
, "CriticalError", "Critical Error")
21 SERIALIZATION_ENUM_END()
27 template <size_t SIZE
> inline void FormatMessage(char (&messageBuffer
)[SIZE
], const char* szFormat
, va_list va_args
)
29 const size_t pos
= strlen(messageBuffer
);
30 vsnprintf(messageBuffer
+ pos
, sizeof(messageBuffer
) - pos
- 1, szFormat
, va_args
);
33 inline ECriticalErrorStatus
DisplayCriticalErrorMessage(const char* szMessage
)
35 string message
= szMessage
;
36 message
.append("\r\n\r\n");
37 message
.append("Would you like to continue?\r\nClick 'Yes' to continue, 'No' to break or 'Cancel' to continue and ignore this error.");
39 switch (CryMessageBox(message
.c_str(), "Schematyc - Critical Error!", eMB_YesNoCancel
))
43 return ECriticalErrorStatus::Continue
;
47 return ECriticalErrorStatus::Ignore
;
50 return ECriticalErrorStatus::Break
;
53 void CriticalErrorCommand(IConsoleCmdArgs
* pArgs
)
55 const char* szMessage
= "";
56 if(pArgs
->GetArgCount() == 2)
58 szMessage
= pArgs
->GetArg(1);
60 SCHEMATYC2_SYSTEM_CRITICAL_ERROR(szMessage
);
63 void FatalErrorCommand(IConsoleCmdArgs
* pArgs
)
65 const char* szMessage
= "";
66 if(pArgs
->GetArgCount() == 2)
68 szMessage
= pArgs
->GetArg(1);
70 SCHEMATYC2_SYSTEM_FATAL_ERROR(szMessage
);
73 class CFileOutput
: public ILogOutput
, public IErrorObserver
77 typedef CryStackStringT
<char, 512> MessageString
;
78 typedef std::vector
<MessageString
> MessageQueue
;
79 typedef std::vector
<LogStreamId
> StreamIds
;
80 typedef std::vector
<ELogMessageType
> MessageTypes
;
84 CFileOutput(LogMessageSignal
& messageSignal
, const char* szFileName
)
86 , m_bErrorObserverRegistered(false)
87 , m_bWriteToFile(true)
88 , m_bForwardToStandardLog(false)
89 , m_fileName(szFileName
)
92 messageSignal
.Connect(LogMessageSignal::Delegate::FromMemberFunction
<CFileOutput
, &CFileOutput::OnLogMessage
>(*this), m_connectionScope
);
102 virtual void ConfigureFileOutput(bool bWriteToFile
, bool bForwardToStandardLog
) override
// Hack! Allows game to decide where messages are forwarded.
104 m_bWriteToFile
= bWriteToFile
;
105 m_bForwardToStandardLog
= bForwardToStandardLog
;
108 virtual void EnableStream(const LogStreamId
& streamId
) override
110 stl::push_back_unique(m_streamIds
, streamId
);
113 virtual void ClearStreams() override
118 virtual void EnableMessageType(ELogMessageType messageType
) override
120 stl::push_back_unique(m_messageTypes
, messageType
);
123 virtual void ClearMessageTypes() override
125 m_messageTypes
.clear();
128 virtual void Update() override
137 virtual void OnAssert(const char* szCondition
, const char* szMessage
, const char* szFileName
, unsigned int fileLineNumber
) override
142 virtual void OnFatalError(const char* szMessage
) override
149 void OnLogMessage(const SLogMessageData
& logMessageData
)
151 if(m_streamIds
.empty() || std::find(m_streamIds
.begin(), m_streamIds
.end(), logMessageData
.streamId
) != m_streamIds
.end())
153 if(m_messageTypes
.empty() || std::find(m_messageTypes
.begin(), m_messageTypes
.end(), logMessageData
.messageType
) != m_messageTypes
.end())
155 EnqueueMessage(logMessageData
);
162 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
166 CryAutoCriticalSection
lock(m_criticalSection
);
167 if (m_pFile
&& !m_messagQueue
.empty())
169 const size_t valueSize
= sizeof(MessageString::CharTraits::value_type
);
170 for (const MessageString
& message
: m_messagQueue
)
172 fwrite(message
.c_str(), valueSize
, message
.size(), m_pFile
);
174 m_messagQueue
.clear();
186 CryAutoCriticalSection
lock(m_criticalSection
);
196 if(m_bErrorObserverRegistered
)
198 m_bErrorObserverRegistered
= false;
199 gEnv
->pSystem
->UnregisterErrorObserver(this);
202 m_messagQueue
.shrink_to_fit();
211 m_bErrorObserverRegistered
= gEnv
->pSystem
->RegisterErrorObserver(this);
213 FILE *pFile
= fxopen(m_fileName
.c_str(), "rb");
218 stack_string
backupFileName("LogBackups/");
219 backupFileName
.append(m_fileName
.c_str());
220 #if CRY_PLATFORM_DURANGO
221 CRY_VERIFY(CopyFile2(CryStringUtils::UTF8ToWStrSafe(m_fileName
), CryStringUtils::UTF8ToWStrSafe(backupFileName
), nullptr) == S_OK
, "Error copying schematyc log backup file");
223 CopyFile(m_fileName
.c_str(), backupFileName
.c_str(), true);
227 if (m_pFile
= fxopen(m_fileName
.c_str(), "wtc"))
229 setvbuf(m_pFile
, m_writeBuffer
, _IOFBF
, s_writeBufferSize
);
233 CryWarning(VALIDATOR_MODULE_UNKNOWN
, VALIDATOR_WARNING
, "Failed to write Schematyc2 log to disk!");
236 m_messagQueue
.reserve(100);
240 void EnqueueMessage(const SLogMessageData
& logMessageData
)
242 MessageString output
;
245 LogUtils::TimeStringBuffer stringBuffer
= "";
247 output
.append(LogUtils::FormatTime(stringBuffer
));
251 const string cryLinkUri
= logMessageData
.metaInfo
.GetUri();
252 if(!cryLinkUri
.empty())
255 output
.append(logMessageData
.metaInfo
.GetUri().c_str());
259 output
.append(logMessageData
.szMessage
);
264 CryAutoCriticalSection
lock(m_criticalSection
);
265 m_messagQueue
.push_back(output
);
268 if (m_bForwardToStandardLog
)
270 CryLog("SCHEMATYC: %s", output
.c_str());
276 static const size_t s_writeBufferSize
= 1024 * 1024;
278 MessageQueue m_messagQueue
;
279 StreamIds m_streamIds
;
280 MessageTypes m_messageTypes
;
281 FILE * volatile m_pFile
;
283 CryCriticalSection m_criticalSection
;
284 char m_writeBuffer
[s_writeBufferSize
];
285 bool m_bErrorObserverRegistered
;
287 bool m_bForwardToStandardLog
;
288 TemplateUtils::CConnectionScope m_connectionScope
;
292 const LogStreamId
CLog::FIRST_DYNAMIC_STREAM_ID(0xffff0000);
294 //////////////////////////////////////////////////////////////////////////
296 : m_nextDynamicStreamId(FIRST_DYNAMIC_STREAM_ID
)
298 m_pSettings
= SSettingsPtr(new SSettings(SettingsModifiedCallback::FromMemberFunction
<CLog
, &CLog::OnSettingsModified
>(*this)));
301 //////////////////////////////////////////////////////////////////////////
304 CreateStream("Default", LOG_STREAM_DEFAULT
);
305 CreateStream("System", LOG_STREAM_SYSTEM
);
306 CreateStream("Compiler", LOG_STREAM_COMPILER
);
307 CreateStream("Game", LOG_STREAM_GAME
);
308 gEnv
->pSchematyc2
->GetEnvRegistry().RegisterSettings("log_settings", m_pSettings
);
309 REGISTER_COMMAND("sc2_CriticalError", LogUtils::CriticalErrorCommand
, VF_NULL
, "Trigger Schematyc critical error");
310 REGISTER_COMMAND("sc2_FatalError", LogUtils::FatalErrorCommand
, VF_NULL
, "Trigger Schematyc fatal error");
313 //////////////////////////////////////////////////////////////////////////
314 ELogMessageType
CLog::GetMessageType(const char* szMessageType
)
316 SCHEMATYC2_SYSTEM_ASSERT(szMessageType
);
319 const Serialization::EnumDescription
& enumDescription
= Serialization::getEnumDescription
<ELogMessageType
>();
320 for(int logMessageTypeIdx
= 0, logMessageTypeCount
= enumDescription
.count(); logMessageTypeIdx
< logMessageTypeCount
; ++ logMessageTypeIdx
)
322 if(stricmp(enumDescription
.labelByIndex(logMessageTypeIdx
), szMessageType
) == 0)
324 return static_cast<ELogMessageType
>(enumDescription
.valueByIndex(logMessageTypeIdx
));
328 return ELogMessageType::Invalid
;
331 //////////////////////////////////////////////////////////////////////////
332 const char* CLog::GetMessageTypeName(ELogMessageType messageType
)
334 const Serialization::EnumDescription
& enumDescription
= Serialization::getEnumDescription
<ELogMessageType
>();
335 return enumDescription
.labelByIndex(enumDescription
.indexByValue(static_cast<int>(messageType
)));
338 //////////////////////////////////////////////////////////////////////////
339 LogStreamId
CLog::CreateStream(const char* szName
, const LogStreamId
& staticStreamId
)
341 SCHEMATYC2_SYSTEM_ASSERT(szName
);
342 if(szName
&& (FindStream(szName
) == INVALID_INDEX
))
344 LogStreamId streamId
;
345 if(staticStreamId
== LogStreamId::s_invalid
)
347 streamId
= m_nextDynamicStreamId
++;
349 else if(staticStreamId
< FIRST_DYNAMIC_STREAM_ID
)
351 if(FindStream(staticStreamId
) == INVALID_INDEX
)
353 streamId
= staticStreamId
;
356 SCHEMATYC2_SYSTEM_ASSERT(streamId
!= LogStreamId::s_invalid
);
357 if(streamId
!= LogStreamId::s_invalid
)
359 m_streams
.push_back(SStream(szName
, streamId
));
363 return LogStreamId::s_invalid
;
366 //////////////////////////////////////////////////////////////////////////
367 void CLog::DestroyStream(const LogStreamId
& streamId
)
369 for(Streams::iterator itStream
= m_streams
.begin(), itEndStream
= m_streams
.end(); itStream
!= itEndStream
; ++ itStream
)
371 if(itStream
->id
== streamId
)
373 m_streams
.erase(itStream
);
379 //////////////////////////////////////////////////////////////////////////
380 LogStreamId
CLog::GetStreamId(const char* szName
) const
382 const size_t streamIdx
= FindStream(szName
);
383 return streamIdx
!= INVALID_INDEX
? m_streams
[streamIdx
].id
: LogStreamId::s_invalid
;
386 //////////////////////////////////////////////////////////////////////////
387 const char* CLog::GetStreamName(const LogStreamId
& streamId
) const
389 const size_t streamIdx
= FindStream(streamId
);
390 return streamIdx
!= INVALID_INDEX
? m_streams
[streamIdx
].name
.c_str() : "";
393 //////////////////////////////////////////////////////////////////////////
394 void CLog::VisitStreams(const LogStreamVisitor
& visitor
) const
396 SCHEMATYC2_SYSTEM_ASSERT(visitor
);
399 for(const SStream
& stream
: m_streams
)
401 visitor(stream
.name
.c_str(), stream
.id
);
406 //////////////////////////////////////////////////////////////////////////
407 ILogOutputPtr
CLog::CreateFileOutput(const char* szFileName
)
409 ILogOutputPtr
pFileOutput(new LogUtils::CFileOutput(m_signals
.message
, szFileName
));
410 m_outputs
.push_back(pFileOutput
);
414 //////////////////////////////////////////////////////////////////////////
415 void CLog::Comment(const LogStreamId
& streamId
, CLogMessageMetaInfo metaInfo
, const char* szFormat
, ...)
417 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
420 va_start(va_args
, szFormat
);
421 char messageBuffer
[1024] = "";
422 LogUtils::FormatMessage(messageBuffer
, szFormat
, va_args
);
424 m_signals
.message
.Send(SLogMessageData(streamId
, ELogMessageType::Comment
, metaInfo
, messageBuffer
));
427 //////////////////////////////////////////////////////////////////////////
428 void CLog::Warning(const LogStreamId
& streamId
, CLogMessageMetaInfo metaInfo
, const char* szFormat
, ...)
430 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
433 va_start(va_args
, szFormat
);
434 char messageBuffer
[1024] = "";
435 LogUtils::FormatMessage(messageBuffer
, szFormat
, va_args
);
437 m_signals
.message
.Send(SLogMessageData(streamId
, ELogMessageType::Warning
, metaInfo
, messageBuffer
));
440 //////////////////////////////////////////////////////////////////////////
441 void CLog::Error(const LogStreamId
& streamId
, CLogMessageMetaInfo metaInfo
, const char* szFormat
, ...)
443 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
446 va_start(va_args
, szFormat
);
447 char messageBuffer
[1024] = "";
448 LogUtils::FormatMessage(messageBuffer
, szFormat
, va_args
);
450 m_signals
.message
.Send(SLogMessageData(streamId
, ELogMessageType::Error
, metaInfo
, messageBuffer
));
453 //////////////////////////////////////////////////////////////////////////
454 ECriticalErrorStatus
CLog::CriticalError(const LogStreamId
& streamId
, CLogMessageMetaInfo metaInfo
, const char* szFormat
, ...)
457 va_start(va_args
, szFormat
);
458 char messageBuffer
[1024] = "";
459 LogUtils::FormatMessage(messageBuffer
, szFormat
, va_args
);
461 m_signals
.message
.Send(SLogMessageData(streamId
, ELogMessageType::CriticalError
, metaInfo
, messageBuffer
));
462 if(CVars::sc2_DisplayCriticalErrors
)
464 return LogUtils::DisplayCriticalErrorMessage(messageBuffer
);
468 return ECriticalErrorStatus::Continue
;
472 //////////////////////////////////////////////////////////////////////////
473 void CLog::FatalError(const LogStreamId
& streamId
, CLogMessageMetaInfo metaInfo
, const char* szFormat
, ...)
476 va_start(va_args
, szFormat
);
477 char messageBuffer
[1024] = "";
478 LogUtils::FormatMessage(messageBuffer
, szFormat
, va_args
);
480 m_signals
.message
.Send(SLogMessageData(streamId
, ELogMessageType::FatalError
, metaInfo
, messageBuffer
));
483 //////////////////////////////////////////////////////////////////////////
486 for(ILogOutputPtr
& pOutput
: m_outputs
)
492 //////////////////////////////////////////////////////////////////////////
493 SLogSignals
& CLog::Signals()
498 //////////////////////////////////////////////////////////////////////////
499 CLog::SStream::SStream(const char* _szName
, const LogStreamId
& _id
)
504 //////////////////////////////////////////////////////////////////////////
505 void CLog::OnSettingsModified()
507 for(const string
& userStream
: m_pSettings
->userStreams
)
509 const char* szStreamName
= userStream
.c_str();
510 if(szStreamName
[0] && (FindStream(szStreamName
) == INVALID_INDEX
))
512 CreateStream(szStreamName
);
517 //////////////////////////////////////////////////////////////////////////
518 size_t CLog::FindStream(const char* szName
) const
520 SCHEMATYC2_SYSTEM_ASSERT(szName
);
523 for(size_t streamIdx
= 0, steamCount
= m_streams
.size(); streamIdx
< steamCount
; ++ streamIdx
)
525 if(stricmp(m_streams
[streamIdx
].name
.c_str(), szName
) == 0)
531 return INVALID_INDEX
;
534 //////////////////////////////////////////////////////////////////////////
535 size_t CLog::FindStream(const LogStreamId
& streamId
) const
537 for(size_t streamIdx
= 0, steamCount
= m_streams
.size(); streamIdx
< steamCount
; ++ streamIdx
)
539 if(m_streams
[streamIdx
].id
== streamId
)
544 return INVALID_INDEX
;
547 //////////////////////////////////////////////////////////////////////////
548 void CLog::GetUserStreamsFileName(stack_string
& fileName
) const
550 fileName
= gEnv
->pSystem
->GetIPak()->GetGameFolder();
551 fileName
.append("/");
552 fileName
.append(CVars::GetStringSafe(CVars::sc2_RootFolder
));
553 fileName
.append("/");
554 fileName
.append("log_user_streams.xml");
557 //////////////////////////////////////////////////////////////////////////
558 bool CLog::IsLoggingEnabled() const
560 return CVars::sc2_LogToFile
!= nullptr && CVars::sc2_LogToFile
->GetIVal() != 0;
563 //////////////////////////////////////////////////////////////////////////
564 CLog::SSettings::SSettings(const SettingsModifiedCallback
& _modifiedCallback
)
565 : modifiedCallback(_modifiedCallback
)
568 //////////////////////////////////////////////////////////////////////////
569 void CLog::SSettings::Serialize(Serialization::IArchive
& archive
)
571 archive(userStreams
, "userStreams", "User Streams");
572 if(archive
.isInput() && modifiedCallback
)
578 //////////////////////////////////////////////////////////////////////////
579 CLogRecorder::~CLogRecorder()
584 //////////////////////////////////////////////////////////////////////////
585 void CLogRecorder::Begin()
587 m_recordedMessages
.reserve(1024);
588 gEnv
->pSchematyc2
->GetLog().Signals().message
.Connect(LogMessageSignal::Delegate::FromMemberFunction
<CLogRecorder
, &CLogRecorder::OnLogMessage
>(*this), m_connectionScope
);
591 //////////////////////////////////////////////////////////////////////////
592 void CLogRecorder::End()
594 m_connectionScope
.Release();
597 //////////////////////////////////////////////////////////////////////////
598 void CLogRecorder::VisitMessages(const LogMessageVisitor
& visitor
)
600 SCHEMATYC2_SYSTEM_ASSERT(visitor
);
603 for(auto recordedMessage
: m_recordedMessages
)
605 if(visitor(SLogMessageData(recordedMessage
.streamId
, recordedMessage
.messageType
, recordedMessage
.metaInfo
, recordedMessage
.message
.c_str())) != EVisitStatus::Continue
)
613 //////////////////////////////////////////////////////////////////////////
614 void CLogRecorder::Clear()
616 m_recordedMessages
.clear();
619 //////////////////////////////////////////////////////////////////////////
620 CLogRecorder::SRecordedMessage::SRecordedMessage(const SLogMessageData
& _logMessageData
)
621 : streamId(_logMessageData
.streamId
)
622 , messageType(_logMessageData
.messageType
)
623 , metaInfo(_logMessageData
.metaInfo
)
624 , message(_logMessageData
.szMessage
)
627 //////////////////////////////////////////////////////////////////////////
628 void CLogRecorder::OnLogMessage(const SLogMessageData
& logMessageData
)
630 m_recordedMessages
.push_back(SRecordedMessage(logMessageData
));