Log.cpp 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. // Description : Log related functions
  9. #include "CrySystem_precompiled.h"
  10. #include "Log.h"
  11. //this should not be included here
  12. #include <IConsole.h>
  13. #include <ISystem.h>
  14. #include "System.h"
  15. #include "CryPath.h" // PathUtil::ReplaceExtension()
  16. #include <AzFramework/IO/FileOperations.h>
  17. #include <AzCore/IO/ByteContainerStream.h>
  18. #include <AzCore/IO/FileIO.h>
  19. #include <AzCore/IO/Path/Path.h>
  20. #include <AzCore/Time/ITime.h>
  21. #ifdef WIN32
  22. #include <time.h>
  23. #endif
  24. #if defined(LINUX) || defined(APPLE)
  25. #include <syslog.h>
  26. #endif
  27. #define LOG_BACKUP_PATH "@log@/LogBackups"
  28. AZ_CVAR(int32_t, log_IncludeTime, 1, nullptr, AZ::ConsoleFunctorFlags::Null,
  29. "Toggles time stamping of log entries.\n"
  30. "Usage: log_IncludeTime [0/1/2/3/4/5]\n"
  31. " 0=off (default)\n"
  32. " 1=current time\n"
  33. " 2=relative time\n"
  34. " 3=current+relative time\n"
  35. " 4=absolute time in seconds since this mode was started\n"
  36. " 5=current time+server time"
  37. " 6=current date+current time");
  38. //////////////////////////////////////////////////////////////////////
  39. namespace LogCVars
  40. {
  41. float s_log_tick = 0;
  42. int max_backup_directory_size_mb = 200; //200MB default
  43. };
  44. #if defined(SUPPORT_LOG_IDENTER)
  45. static CLog::LogStringType indentString (" ");
  46. #endif
  47. namespace
  48. {
  49. // Definitions for Timestamp logging functions
  50. using TimeStringType = AZStd::fixed_string<128>;
  51. auto GetHourMinuteSeconds() -> TimeStringType
  52. {
  53. TimeStringType sTime;
  54. time_t ltime;
  55. time(&ltime);
  56. #ifdef AZ_COMPILER_MSVC
  57. struct tm today;
  58. localtime_s(&today, &ltime);
  59. strftime(sTime.data(), sTime.capacity(), "<%H:%M:%S> ", &today);
  60. // Fix up the internal size member of the fixed string
  61. // Using the traits_type::length function to calculate the strlen of the c-string returned by strftime
  62. sTime.resize_no_construct(TimeStringType::traits_type::length(sTime.data()));
  63. #else
  64. auto today = localtime(&ltime);
  65. strftime(sTime.data(), sTime.capacity(), "<%H:%M:%S> ", today);
  66. sTime.resize_no_construct(TimeStringType::traits_type::length(sTime.data()));
  67. #endif
  68. return sTime;
  69. };
  70. auto GetDateAndHourMinuteSeconds() -> TimeStringType
  71. {
  72. TimeStringType sTime;
  73. time_t ltime;
  74. time(&ltime);
  75. #ifdef AZ_COMPILER_MSVC
  76. struct tm today;
  77. localtime_s(&today, &ltime);
  78. strftime(sTime.data(), sTime.capacity(), "<%Y-%m-%d %H:%M:%S> ", &today);
  79. sTime.resize_no_construct(TimeStringType::traits_type::length(sTime.data()));
  80. #else
  81. auto today = localtime(&ltime);
  82. strftime(sTime.data(), sTime.capacity(), "<%Y-%m-%d %H:%M:%S> ", today);
  83. sTime.resize_no_construct(TimeStringType::traits_type::length(sTime.data()));
  84. #endif
  85. return sTime;
  86. };
  87. auto GetElapsedTimeInSeconds() -> TimeStringType
  88. {
  89. TimeStringType elapsedTime;
  90. static AZ::TimeMs lasttime = AZ::Time::ZeroTimeMs;
  91. const AZ::TimeMs currenttime = AZ::GetRealElapsedTimeMs();
  92. if (lasttime != AZ::Time::ZeroTimeMs)
  93. {
  94. uint32 dwMs = aznumeric_cast<uint32>(currenttime - lasttime);
  95. elapsedTime = TimeStringType::format("<%3d.%.3d>: ", dwMs / 1000, dwMs % 1000);
  96. }
  97. lasttime = currenttime;
  98. return elapsedTime;
  99. };
  100. auto GetElapsedTimeSinceStartInSeconds() -> TimeStringType
  101. {
  102. TimeStringType elapsedTime;
  103. static AZ::TimeMs lasttime = AZ::Time::ZeroTimeMs;
  104. const AZ::TimeMs currenttime = AZ::GetRealElapsedTimeMs();
  105. if (lasttime != AZ::Time::ZeroTimeMs)
  106. {
  107. uint32 dwMs = (uint32)(currenttime - lasttime);
  108. elapsedTime = TimeStringType::format("<%3d.%.3d>: ", dwMs / 1000, dwMs % 1000);
  109. }
  110. static bool bFirst = true;
  111. if (bFirst)
  112. {
  113. lasttime = currenttime;
  114. bFirst = false;
  115. }
  116. return elapsedTime;
  117. };
  118. }
  119. //////////////////////////////////////////////////////////////////////
  120. CLog::CLog(ISystem* pSystem)
  121. {
  122. memset(m_szFilename, 0, MAX_FILENAME_SIZE);
  123. memset(m_sBackupFilename, 0, MAX_FILENAME_SIZE);
  124. //memset(m_szTemp,0,MAX_TEMP_LENGTH_SIZE);
  125. m_pSystem = pSystem;
  126. m_pLogVerbosity = 0;
  127. m_pLogWriteToFile = 0;
  128. m_pLogWriteToFileVerbosity = 0;
  129. m_pLogVerbosityOverridesWriteToFile = 0;
  130. m_pLogSpamDelay = 0;
  131. m_pLogModule = 0;
  132. m_fLastLoadingUpdateTime = -1.f; // for streaming engine update
  133. m_backupLogs = true;
  134. #if defined(SUPPORT_LOG_IDENTER)
  135. m_indentation = 0;
  136. BuildIndentString();
  137. m_topIndenter = NULL;
  138. #endif
  139. m_nMainThreadId = CryGetCurrentThreadId();
  140. m_iLastHistoryItem = 0;
  141. memset(m_history, 0, sizeof(m_history));
  142. CheckAndPruneBackupLogs();
  143. }
  144. void CLog::RegisterConsoleVariables()
  145. {
  146. IConsole* console = m_pSystem->GetIConsole();
  147. #ifdef _RELEASE
  148. #if defined(RELEASE_LOGGING)
  149. #define DEFAULT_VERBOSITY 0
  150. #else
  151. #define DEFAULT_VERBOSITY -1
  152. #endif
  153. #else
  154. #define DEFAULT_VERBOSITY 3
  155. #endif
  156. if (console)
  157. {
  158. m_pLogVerbosity = REGISTER_INT("log_Verbosity", DEFAULT_VERBOSITY, VF_DUMPTODISK,
  159. "defines the verbosity level for log messages written to console\n"
  160. "-1=suppress all logs (including eAlways)\n"
  161. "0=suppress all logs(except eAlways)\n"
  162. "1=additional errors\n"
  163. "2=additional warnings\n"
  164. "3=additional messages\n"
  165. "4=additional comments");
  166. //writing to game.log during game play causes stalls on consoles
  167. m_pLogWriteToFile = REGISTER_INT("log_WriteToFile", 1, VF_DUMPTODISK, "toggle whether to write log to file (game.log)");
  168. m_pLogWriteToFileVerbosity = REGISTER_INT("log_WriteToFileVerbosity", DEFAULT_VERBOSITY, VF_DUMPTODISK,
  169. "defines the verbosity level for log messages written to files\n"
  170. "-1=suppress all logs (including eAlways)\n"
  171. "0=suppress all logs(except eAlways)\n"
  172. "1=additional errors\n"
  173. "2=additional warnings\n"
  174. "3=additional messages\n"
  175. "4=additional comments");
  176. m_pLogVerbosityOverridesWriteToFile = REGISTER_INT("log_VerbosityOverridesWriteToFile", 1, VF_DUMPTODISK, "when enabled, setting log_verbosity to 0 will stop all logging including writing to file");
  177. m_pLogSpamDelay = REGISTER_FLOAT("log_SpamDelay", 0.0f, 0, "Sets the minimum time interval between messages classified as spam");
  178. m_pLogModule = REGISTER_STRING("log_Module", "", VF_NULL, "Only show warnings from specified module");
  179. REGISTER_CVAR2("log_tick", &LogCVars::s_log_tick, LogCVars::s_log_tick, 0, "When not 0, writes tick log entry into the log file, every N seconds");
  180. REGISTER_CVAR2("max_log_backup_mb", &LogCVars::max_backup_directory_size_mb, LogCVars::max_backup_directory_size_mb, 0, "Maximum size of backup logs to keep on disk (in MB)");
  181. #if defined(KEEP_LOG_FILE_OPEN)
  182. REGISTER_COMMAND("log_flush", &LogFlushFile, 0, "Flush the log file");
  183. #endif
  184. }
  185. #undef DEFAULT_VERBOSITY
  186. }
  187. //////////////////////////////////////////////////////////////////////
  188. CLog::~CLog()
  189. {
  190. #if defined(SUPPORT_LOG_IDENTER)
  191. while (m_topIndenter)
  192. {
  193. m_topIndenter->Enable(false);
  194. }
  195. assert (m_indentation == 0);
  196. #endif
  197. CreateBackupFile();
  198. UnregisterConsoleVariables();
  199. CloseLogFile();
  200. }
  201. void CLog::UnregisterConsoleVariables()
  202. {
  203. m_pLogVerbosity = 0;
  204. m_pLogWriteToFile = 0;
  205. m_pLogWriteToFileVerbosity = 0;
  206. m_pLogVerbosityOverridesWriteToFile = 0;
  207. m_pLogSpamDelay = 0;
  208. }
  209. //////////////////////////////////////////////////////////////////////////
  210. void CLog::CloseLogFile()
  211. {
  212. m_logFileHandle.Close();
  213. }
  214. //////////////////////////////////////////////////////////////////////////
  215. bool CLog::OpenLogFile(const char* filename, AZ::IO::OpenMode mode)
  216. {
  217. if (m_logFileHandle.IsOpen())
  218. {
  219. // Can only AZ_Assert if a file is open, otherwise the AZ_Assert
  220. // would eventually lead to OpenLogFile being opened up again
  221. AZ_Assert(false, "Attempt to open log file when one is already open. This would lead to a handle leak.");
  222. return false;
  223. }
  224. if (filename == nullptr || filename[0] == '\0')
  225. {
  226. return false;
  227. }
  228. bool opened = m_logFileHandle.Open(filename, mode);
  229. #if defined(LINUX) || defined(APPLE)
  230. if (!opened)
  231. {
  232. syslog(LOG_NOTICE, "Failed to open log file [%s], mode [%d]", filename, static_cast<int>(mode));
  233. }
  234. #endif
  235. return opened;
  236. }
  237. //////////////////////////////////////////////////////////////////////////
  238. void CLog::SetVerbosity(int verbosity)
  239. {
  240. if (m_pLogVerbosity)
  241. {
  242. m_pLogVerbosity->Set(verbosity);
  243. }
  244. }
  245. bool CLog::CheckLogFormatter(const char* formatter)
  246. {
  247. if (!formatter)
  248. {
  249. AZ_Assert(false, "code problem - A message was sent to the log system with nullptr as the formatting string.");
  250. return false;
  251. }
  252. if (formatter[0] == 0)
  253. {
  254. // if there's nothing to log, we don't log anything. Blank lines at least contain a carriage return or something.
  255. return false;
  256. }
  257. return true;
  258. }
  259. //////////////////////////////////////////////////////////////////////////
  260. #if !defined(EXCLUDE_NORMAL_LOG)
  261. void CLog::LogWarning(const char* szFormat, ...)
  262. {
  263. if (!CheckLogFormatter(szFormat))
  264. {
  265. return;
  266. }
  267. va_list ArgList;
  268. char szBuffer[MAX_WARNING_LENGTH];
  269. va_start(ArgList, szFormat);
  270. vsnprintf_s(szBuffer, sizeof(szBuffer), sizeof(szBuffer) - 1, szFormat, ArgList);
  271. szBuffer[sizeof(szBuffer) - 1] = '\0';
  272. va_end(ArgList);
  273. va_start(ArgList, szFormat);
  274. LogV(eWarning, szFormat, ArgList);
  275. va_end(ArgList);
  276. }
  277. //////////////////////////////////////////////////////////////////////////
  278. void CLog::LogError(const char* szFormat, ...)
  279. {
  280. if (!CheckLogFormatter(szFormat))
  281. {
  282. return;
  283. }
  284. va_list ArgList;
  285. char szBuffer[MAX_WARNING_LENGTH];
  286. va_start(ArgList, szFormat);
  287. vsnprintf_s(szBuffer, sizeof(szBuffer), sizeof(szBuffer) - 1, szFormat, ArgList);
  288. szBuffer[sizeof(szBuffer) - 1] = '\0';
  289. va_end(ArgList);
  290. va_start(ArgList, szFormat);
  291. LogV(eError, szFormat, ArgList);
  292. va_end(ArgList);
  293. }
  294. //////////////////////////////////////////////////////////////////////////
  295. void CLog::Log(const char* szFormat, ...)
  296. {
  297. if (!CheckLogFormatter(szFormat))
  298. {
  299. return;
  300. }
  301. va_list arg;
  302. va_start(arg, szFormat);
  303. LogV (eMessage, szFormat, arg);
  304. va_end(arg);
  305. }
  306. //////////////////////////////////////////////////////////////////////////
  307. void CLog::LogAlways(const char* szFormat, ...)
  308. {
  309. if (!CheckLogFormatter(szFormat))
  310. {
  311. return;
  312. }
  313. va_list arg;
  314. va_start(arg, szFormat);
  315. LogV (eAlways, szFormat, arg);
  316. va_end(arg);
  317. }
  318. #endif // !defined(EXCLUDE_NORMAL_LOG)
  319. int MatchStrings(const char* str0, const char* str1)
  320. {
  321. const char* str[] = { str0, str1 };
  322. int i, bSkipWord, bAlpha[2], bWS[2], bStop = 0, nDiffs = 0, nWordDiffs, len = 0;
  323. do
  324. {
  325. for (i = 0; i < 2; i++) // skip the spaces, stop at 0
  326. {
  327. while (*str[i] == ' ')
  328. {
  329. if (!*str[i]++)
  330. {
  331. goto break2;
  332. }
  333. }
  334. }
  335. bWS[0] = bWS[1] = nWordDiffs = bSkipWord = 0;
  336. do
  337. {
  338. for (i = bAlpha[0] = bAlpha[1] = 0; i < 2; i++)
  339. {
  340. if (!bWS[i])
  341. {
  342. do
  343. {
  344. int chr = *str[i]++;
  345. bSkipWord |= iszero(chr - '\\') | iszero(chr - '/') | iszero(chr - '_'); // ignore different words with \,_,/
  346. bAlpha[i] = inrange(chr, 'A' - 1, 'Z' + 1) | inrange(chr, 'a' - 1, 'z' + 1);
  347. bWS[i] = iszero(chr - ' ');
  348. bStop |= iszero(chr);
  349. } while (!(bAlpha[i] | bWS[i] | bStop)); // wait for a letter or a space in each input string
  350. }
  351. }
  352. len += bAlpha[0] & bAlpha[1];
  353. nWordDiffs += 1 - iszero((int)(*str[0] - *str[1])) & - (bAlpha[0] & bAlpha[1]); // count diffs in this word
  354. } while ((1 - bWS[0] | 1 - bWS[1]) & 1 - bStop); // wait for space (word end) in both strings
  355. nDiffs += nWordDiffs & ~-bSkipWord;
  356. } while (!bStop);
  357. break2:
  358. return nDiffs * 10 < len;
  359. }
  360. //will log the text both to file and console
  361. //////////////////////////////////////////////////////////////////////
  362. void CLog::LogV(const ELogType type, const char* szFormat, va_list args)
  363. {
  364. // this is here in case someone called LogV directly, with an invalid formatter.
  365. if (!CheckLogFormatter(szFormat))
  366. {
  367. return;
  368. }
  369. LogV(type, 0, szFormat, args);
  370. }
  371. void CLog::LogV(const ELogType type, [[maybe_unused]] int flags, const char* szFormat, va_list args)
  372. {
  373. // this is here in case someone called LogV directly, with an invalid formatter.
  374. if (!CheckLogFormatter(szFormat))
  375. {
  376. return;
  377. }
  378. if (!szFormat)
  379. {
  380. return;
  381. }
  382. if (m_pLogVerbosityOverridesWriteToFile && m_pLogVerbosityOverridesWriteToFile->GetIVal())
  383. {
  384. if (m_pLogVerbosity && m_pLogVerbosity->GetIVal() < 0)
  385. {
  386. return;
  387. }
  388. }
  389. bool bfile = false, bconsole = false;
  390. const char* szCommand = szFormat;
  391. uint8 DefaultVerbosity = 0; // 0 == Always log (except for special -1 verbosity overrides)
  392. switch (type)
  393. {
  394. case eAlways:
  395. DefaultVerbosity = 0;
  396. break;
  397. case eWarningAlways:
  398. DefaultVerbosity = 0;
  399. break;
  400. case eErrorAlways:
  401. DefaultVerbosity = 0;
  402. break;
  403. case eInput:
  404. DefaultVerbosity = 0;
  405. break;
  406. case eInputResponse:
  407. DefaultVerbosity = 0;
  408. break;
  409. case eError:
  410. DefaultVerbosity = 1;
  411. break;
  412. case eWarning:
  413. DefaultVerbosity = 2;
  414. break;
  415. case eMessage:
  416. DefaultVerbosity = 3;
  417. break;
  418. case eComment:
  419. DefaultVerbosity = 4;
  420. break;
  421. default:
  422. assert(0);
  423. }
  424. szCommand = CheckAgainstVerbosity(szFormat, bfile, bconsole, DefaultVerbosity);
  425. if (!bfile && !bconsole)
  426. {
  427. return;
  428. }
  429. char szBuffer[MAX_WARNING_LENGTH + 32];
  430. char* szString = szBuffer;
  431. char* szAfterColour = szString;
  432. size_t prefixSize = 0;
  433. switch (type)
  434. {
  435. case eWarning:
  436. case eWarningAlways:
  437. azstrcpy(szString, MAX_WARNING_LENGTH, "$6[Warning] ");
  438. szString += 12; // strlen("$6[Warning] ");
  439. szAfterColour += 2;
  440. prefixSize = 12;
  441. break;
  442. case eError:
  443. case eErrorAlways:
  444. azstrcpy(szString, MAX_WARNING_LENGTH, "$4[Error] ");
  445. szString += 10; // strlen("$4[Error] ");
  446. szAfterColour += 2;
  447. prefixSize = 10;
  448. break;
  449. default:
  450. break;
  451. }
  452. int bufferlen = static_cast<int>(sizeof(szBuffer) - prefixSize);
  453. if (bufferlen > 0)
  454. {
  455. #if defined(AZ_RESTRICTED_PLATFORM)
  456. #include AZ_RESTRICTED_FILE(Log_cpp)
  457. #endif
  458. #if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
  459. #undef AZ_RESTRICTED_SECTION_IMPLEMENTED
  460. #else
  461. int count = vsnprintf_s(szString, bufferlen, bufferlen - 1, szCommand, args);
  462. #endif
  463. if (count == -1 || count >= bufferlen)
  464. {
  465. szBuffer[sizeof(szBuffer) - 1] = '\0';
  466. }
  467. }
  468. if (type == eWarningAlways || type == eWarning || type == eError || type == eErrorAlways)
  469. {
  470. const char* sAssetScope = GetAssetScopeString();
  471. if (*sAssetScope)
  472. {
  473. stack_string s = szBuffer;
  474. s += "\t<Scope> ";
  475. s += sAssetScope;
  476. azstrcpy(szBuffer, AZ_ARRAY_SIZE(szBuffer), s.c_str());
  477. }
  478. }
  479. float dt;
  480. const char* szSpamCheck = *szFormat == '%' ? szString : szFormat;
  481. if (m_pLogSpamDelay && (dt = m_pLogSpamDelay->GetFVal()) > 0.0f && type != eAlways && type != eInputResponse)
  482. {
  483. const int sz = sizeof(m_history) / sizeof(m_history[0]);
  484. int i, j;
  485. const AZ::TimeMs realTimeMs = AZ::GetRealElapsedTimeMs();
  486. const float time = AZ::TimeMsToSeconds(realTimeMs);
  487. for (i = m_iLastHistoryItem, j = 0; m_history[i].time > time - dt && j < sz; j++, i = i - 1 & sz - 1)
  488. {
  489. if (m_history[i].type != type)
  490. {
  491. continue;
  492. }
  493. if (m_history[i].ptr == szSpamCheck && *(int*)m_history[i].str == *(int*)szFormat || MatchStrings(m_history[i].str, szSpamCheck))
  494. {
  495. return;
  496. }
  497. }
  498. i = m_iLastHistoryItem = m_iLastHistoryItem + 1 & sz - 1;
  499. azstrcpy(m_history[i].str, AZ_ARRAY_SIZE(m_history[i].str), m_history[i].ptr = szSpamCheck);
  500. m_history[i].type = type;
  501. m_history[i].time = time;
  502. }
  503. LogString(szAfterColour, type);
  504. if (bfile)
  505. {
  506. LogStringToFile(szAfterColour, type, false, MessageQueueState::NotQueued);
  507. }
  508. if (bconsole)
  509. {
  510. LogStringToConsole(szBuffer, ELogType::eAlways, false);
  511. }
  512. switch (type)
  513. {
  514. case eAlways:
  515. case eInput:
  516. case eInputResponse:
  517. case eComment:
  518. case eMessage:
  519. GetISystem()->GetIRemoteConsole()->AddLogMessage(szString);
  520. break;
  521. case eWarning:
  522. case eWarningAlways:
  523. GetISystem()->GetIRemoteConsole()->AddLogWarning(szString);
  524. break;
  525. case eError:
  526. case eErrorAlways:
  527. GetISystem()->GetIRemoteConsole()->AddLogError(szString);
  528. break;
  529. }
  530. }
  531. void CLog::LogWithCallback(ELogType type, const LogWriteCallback& messageCallback)
  532. {
  533. if (!messageCallback)
  534. {
  535. return;
  536. }
  537. if (m_pLogVerbosityOverridesWriteToFile && m_pLogVerbosityOverridesWriteToFile->GetIVal())
  538. {
  539. if (m_pLogVerbosity && m_pLogVerbosity->GetIVal() < 0)
  540. {
  541. return;
  542. }
  543. }
  544. bool bfile = false;
  545. bool bconsole = false;
  546. uint8_t DefaultVerbosity = 0; // 0 == Always log (except for special -1 verbosity overrides)
  547. switch (type)
  548. {
  549. case eAlways:
  550. case eWarningAlways:
  551. case eErrorAlways:
  552. case eInput:
  553. case eInputResponse:
  554. DefaultVerbosity = 0;
  555. break;
  556. case eError:
  557. DefaultVerbosity = 1;
  558. break;
  559. case eWarning:
  560. DefaultVerbosity = 2;
  561. break;
  562. case eMessage:
  563. DefaultVerbosity = 3;
  564. break;
  565. case eComment:
  566. DefaultVerbosity = 4;
  567. break;
  568. default:
  569. break;
  570. }
  571. CheckAgainstVerbosity("", bfile, bconsole, DefaultVerbosity);
  572. if (!bfile && !bconsole)
  573. {
  574. return;
  575. }
  576. AZStd::fixed_string<8> colorString;
  577. AZStd::fixed_string<32> logCategoryString;
  578. switch (type)
  579. {
  580. case eWarning:
  581. case eWarningAlways:
  582. colorString = "$6";
  583. logCategoryString = "[Warning] ";
  584. break;
  585. case eError:
  586. case eErrorAlways:
  587. colorString = "$4";
  588. logCategoryString = "[Error] ";
  589. break;
  590. default:
  591. break;
  592. }
  593. auto LogStringWithCallback = [this](ELogType logType,
  594. const LogWriteCallback& messageCallback, AZStd::string_view logCategoryString)
  595. {
  596. AZStd::string message;
  597. AZ::IO::ByteContainerStream outputStream(&message);
  598. messageCallback(outputStream);
  599. if (message.empty())
  600. {
  601. return;
  602. }
  603. AZStd::string logString(logCategoryString);
  604. logString += message;
  605. constexpr bool appendToPrevLine = false;
  606. if (LogToMainThread(logString, logType, appendToPrevLine, SLogMsg::Destination::Default))
  607. {
  608. return;
  609. }
  610. for (auto callback : m_callbacks)
  611. {
  612. callback->OnWrite(logString, logType);
  613. }
  614. };
  615. LogStringWithCallback(type, messageCallback, logCategoryString);
  616. if (bfile)
  617. {
  618. auto LogStringToFileWithCallback = [this](ELogType logType,
  619. const LogWriteCallback& messageCallback, AZStd::string_view logCategoryString)
  620. {
  621. #if defined(_RELEASE) && defined(EXCLUDE_NORMAL_LOG) // no file logging in release
  622. return;
  623. #endif
  624. if (!m_pSystem || !AZ::IO::FileIOBase::GetInstance())
  625. {
  626. return;
  627. }
  628. AZStd::string message;
  629. AZ::IO::ByteContainerStream outputStream(&message);
  630. messageCallback(outputStream);
  631. if (message.empty())
  632. {
  633. return;
  634. }
  635. AZStd::string logString(logCategoryString);
  636. logString += message;
  637. constexpr bool appendToPrevLine = false;
  638. const bool bIsMainThread = LogToMainThread(logString, logType, appendToPrevLine, SLogMsg::Destination::File) == false;
  639. #if defined(_RELEASE)
  640. if (!bIsMainThread)
  641. {
  642. return;
  643. }
  644. #endif
  645. // this is a temp timeStr, it is reused in many branches(moved here to reduce stack usage)
  646. LogStringType timeStr;
  647. auto console = AZ::Interface<AZ::IConsole>::Get();
  648. if (uint32_t dwCVarState;
  649. console != nullptr
  650. && console->GetCvarValue("log_IncludeTime", dwCVarState) == AZ::GetValueResult::Success)
  651. {
  652. // See the log_IncludeTime CVar description as to what
  653. // values correspond to what time strings
  654. switch (dwCVarState)
  655. {
  656. case 1:
  657. case 5:
  658. timeStr = GetHourMinuteSeconds();
  659. break;
  660. case 2:
  661. timeStr = GetElapsedTimeInSeconds();
  662. break;
  663. case 3:
  664. timeStr = GetHourMinuteSeconds();
  665. timeStr += GetElapsedTimeInSeconds();
  666. break;
  667. case 4:
  668. timeStr = GetElapsedTimeSinceStartInSeconds();
  669. break;
  670. case 6:
  671. timeStr = GetDateAndHourMinuteSeconds();
  672. break;
  673. default:
  674. break;
  675. }
  676. }
  677. // do not OutputDebugString in release.
  678. #if !defined(_RELEASE)
  679. if (!timeStr.empty())
  680. {
  681. AZ::Debug::Platform::OutputToDebugger({}, timeStr);
  682. }
  683. AZ::Debug::Platform::OutputToDebugger({}, logString);
  684. if (!bIsMainThread)
  685. {
  686. return;
  687. }
  688. #endif // !defined(_RELEASE)
  689. //////////////////////////////////////////////////////////////////////////
  690. // Call callback function.
  691. for (auto callback : m_callbacks)
  692. {
  693. callback->OnWriteToFile(logString, !appendToPrevLine);
  694. }
  695. ////////////////////////////////////////////////
  696. //////////////////////////////////////////////////////////////////////////
  697. // Write to file.
  698. //////////////////////////////////////////////////////////////////////////
  699. if (int logToFile = m_pLogWriteToFile ? m_pLogWriteToFile->GetIVal() : 1; logToFile)
  700. {
  701. if (!m_logFileHandle.IsOpen())
  702. {
  703. OpenLogFile(m_szFilename, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath);
  704. }
  705. if (m_logFileHandle.IsOpen())
  706. {
  707. if (appendToPrevLine)
  708. {
  709. // if adding to a prior line erase the \n at the end.
  710. m_logFileHandle.Seek(-2, AZ::IO::GenericStream::SeekMode::ST_SEEK_END);
  711. }
  712. if (!timeStr.empty())
  713. {
  714. m_logFileHandle.Write(timeStr.size(), timeStr.data());
  715. }
  716. m_logFileHandle.Write(logString.size(), logString.data());
  717. #if !defined(KEEP_LOG_FILE_OPEN)
  718. CloseLogFile();
  719. #endif
  720. // do not use FLUSH on log files. Doing so will slow the engine down greatly when logging.
  721. // (the log is flushed automatically when an unhandled exception occurs)
  722. }
  723. }
  724. };
  725. LogStringToFileWithCallback(type, messageCallback, logCategoryString);
  726. }
  727. if (bconsole)
  728. {
  729. auto LogStringToConsoleWithCallback = [this](const LogWriteCallback& messageCallback,
  730. AZStd::string_view colorString, AZStd::string_view logCategoryString)
  731. {
  732. #if defined(_RELEASE) && defined(EXCLUDE_NORMAL_LOG) // no console logging in release
  733. return;
  734. #endif
  735. const ELogType logType = ELogType::eAlways;
  736. AZStd::string message;
  737. AZ::IO::ByteContainerStream outputStream(&message);
  738. messageCallback(outputStream);
  739. if (message.empty())
  740. {
  741. return;
  742. }
  743. AZStd::string logString(colorString);
  744. logString += AZStd::string_view(logCategoryString);
  745. logString += message;
  746. constexpr bool appendToPrevLine = false;
  747. if (LogToMainThread(logString, logType, appendToPrevLine, SLogMsg::Destination::Console))
  748. {
  749. return;
  750. }
  751. if (!m_pSystem)
  752. {
  753. return;
  754. }
  755. IConsole* console = m_pSystem->GetIConsole();
  756. if (!console)
  757. {
  758. return;
  759. }
  760. console->PrintLine(logString);
  761. // Call callback function.
  762. for (auto callback : m_callbacks)
  763. {
  764. callback->OnWriteToConsole(logString, !appendToPrevLine);
  765. }
  766. };
  767. LogStringToConsoleWithCallback(messageCallback, colorString, logCategoryString);
  768. }
  769. if (m_pSystem != nullptr)
  770. {
  771. auto LogStringToRemoteConsoleWithCallback = [](ELogType logType, const LogWriteCallback& messageCallback)
  772. {
  773. AZStd::string message;
  774. AZ::IO::ByteContainerStream outputStream(&message);
  775. messageCallback(outputStream);
  776. if (message.empty())
  777. {
  778. return;
  779. }
  780. switch (logType)
  781. {
  782. case eAlways:
  783. case eInput:
  784. case eInputResponse:
  785. case eComment:
  786. case eMessage:
  787. GetISystem()->GetIRemoteConsole()->AddLogMessage(message);
  788. break;
  789. case eWarning:
  790. case eWarningAlways:
  791. GetISystem()->GetIRemoteConsole()->AddLogWarning(message);
  792. break;
  793. case eError:
  794. case eErrorAlways:
  795. GetISystem()->GetIRemoteConsole()->AddLogError(message);
  796. break;
  797. }
  798. };
  799. LogStringToRemoteConsoleWithCallback(type, messageCallback);
  800. }
  801. }
  802. //will log the text both to the end of file and console
  803. //////////////////////////////////////////////////////////////////////
  804. #if !defined(EXCLUDE_NORMAL_LOG)
  805. void CLog::LogAppendWithPrevLine(const char* szFormat, ...)
  806. {
  807. if (!CheckLogFormatter(szFormat))
  808. {
  809. return;
  810. }
  811. if (m_pLogVerbosity && m_pLogVerbosity->GetIVal() < 0)
  812. {
  813. return;
  814. }
  815. if (m_pLogSpamDelay && m_pLogSpamDelay->GetFVal())
  816. { // Vlad: SpamDelay does not work correctly with LogAppendWithPrevLine
  817. return;
  818. }
  819. if (!szFormat)
  820. {
  821. return;
  822. }
  823. bool bfile = false, bconsole = false;
  824. const char* szCommand = CheckAgainstVerbosity(szFormat, bfile, bconsole);
  825. if (!bfile && !bconsole)
  826. {
  827. return;
  828. }
  829. va_list arglist;
  830. char szTemp[MAX_TEMP_LENGTH_SIZE];
  831. va_start(arglist, szFormat);
  832. vsnprintf_s(szTemp, sizeof(szTemp), sizeof(szTemp) - 1, szCommand, arglist);
  833. szTemp[sizeof(szTemp) - 1] = 0;
  834. va_end(arglist);
  835. if (bfile)
  836. {
  837. LogToFileAppendWithPrevLine("%s", szTemp);
  838. }
  839. if (bconsole)
  840. {
  841. LogToConsoleAppendWithPrevLine("%s", szTemp);
  842. }
  843. }
  844. //log to console only
  845. //////////////////////////////////////////////////////////////////////
  846. void CLog::LogStringToConsole(AZStd::string_view message, ELogType logType, bool appendToPrevLine)
  847. {
  848. #if defined(_RELEASE) && defined(EXCLUDE_NORMAL_LOG) // no console logging in release
  849. return;
  850. #endif
  851. if (message.empty())
  852. {
  853. return;
  854. }
  855. if (LogToMainThread(message, logType, appendToPrevLine, SLogMsg::Destination::Console))
  856. {
  857. return;
  858. }
  859. if (!m_pSystem)
  860. {
  861. return;
  862. }
  863. IConsole* console = m_pSystem->GetIConsole();
  864. if (!console)
  865. {
  866. return;
  867. }
  868. if (appendToPrevLine)
  869. {
  870. console->PrintLineAppendWithPrevLine(message);
  871. }
  872. else
  873. {
  874. console->PrintLine(message);
  875. }
  876. // Call callback function.
  877. for (auto callback : m_callbacks)
  878. {
  879. callback->OnWriteToConsole(message, !appendToPrevLine);
  880. }
  881. }
  882. //log to console only
  883. //////////////////////////////////////////////////////////////////////
  884. void CLog::LogToConsole(const char* szFormat, ...)
  885. {
  886. if (!CheckLogFormatter(szFormat))
  887. {
  888. return;
  889. }
  890. if (m_pLogVerbosity && m_pLogVerbosity->GetIVal() < 0)
  891. {
  892. return;
  893. }
  894. if (!szFormat)
  895. {
  896. return;
  897. }
  898. bool bfile = false, bconsole = false;
  899. const char* szCommand = CheckAgainstVerbosity(szFormat, bfile, bconsole);
  900. if (!bconsole)
  901. {
  902. return;
  903. }
  904. va_list arglist;
  905. char szBuffer[MAX_WARNING_LENGTH];
  906. va_start(arglist, szFormat);
  907. vsnprintf_s(szBuffer, sizeof(szBuffer), sizeof(szBuffer) - 1, szCommand, arglist);
  908. szBuffer[sizeof(szBuffer) - 1] = 0;
  909. va_end(arglist);
  910. LogStringToConsole(szBuffer, ELogType::eAlways, false);
  911. }
  912. //////////////////////////////////////////////////////////////////////
  913. void CLog::LogToConsoleAppendWithPrevLine(const char* szFormat, ...)
  914. {
  915. if (!CheckLogFormatter(szFormat))
  916. {
  917. return;
  918. }
  919. if (m_pLogVerbosity && m_pLogVerbosity->GetIVal() < 0)
  920. {
  921. return;
  922. }
  923. if (!szFormat)
  924. {
  925. return;
  926. }
  927. bool bfile = false, bconsole = false;
  928. const char* szCommand = CheckAgainstVerbosity(szFormat, bfile, bconsole);
  929. if (!bconsole)
  930. {
  931. return;
  932. }
  933. va_list arglist;
  934. char szTemp[MAX_TEMP_LENGTH_SIZE];
  935. va_start(arglist, szFormat);
  936. vsnprintf_s(szTemp, sizeof(szTemp), sizeof(szTemp) - 1, szCommand, arglist);
  937. szTemp[sizeof(szTemp) - 1] = 0;
  938. va_end(arglist);
  939. if (!m_pSystem)
  940. {
  941. return;
  942. }
  943. LogStringToConsole(szTemp, ELogType::eAlways, true);
  944. }
  945. #endif // !defined(EXCLUDE_NORMAL_LOG)
  946. //////////////////////////////////////////////////////////////////////
  947. [[maybe_unused]] static AZStd::string_view RemoveColorCode(AZStd::string_view rStr)
  948. {
  949. if (rStr.size() >= 2 && rStr.starts_with('$') && rStr[1] >= '0' && rStr[1] <= '9')
  950. {
  951. rStr.remove_prefix(2);
  952. }
  953. return rStr;
  954. }
  955. #if defined(SUPPORT_LOG_IDENTER)
  956. //////////////////////////////////////////////////////////////////////
  957. void CLog::BuildIndentString()
  958. {
  959. m_indentWithString = "";
  960. for (uint8 i = 0; i < m_indentation; ++i)
  961. {
  962. m_indentWithString = indentString + m_indentWithString;
  963. }
  964. }
  965. //////////////////////////////////////////////////////////////////////
  966. void CLog::Indent(CLogIndenter* indenter)
  967. {
  968. indenter->SetNextIndenter(m_topIndenter);
  969. m_topIndenter = indenter;
  970. ++m_indentation;
  971. BuildIndentString();
  972. }
  973. //////////////////////////////////////////////////////////////////////
  974. void CLog::Unindent(CLogIndenter* indenter)
  975. {
  976. assert (indenter == m_topIndenter);
  977. assert (m_indentation);
  978. m_topIndenter = m_topIndenter->GetNextIndenter();
  979. --m_indentation;
  980. BuildIndentString();
  981. }
  982. //////////////////////////////////////////////////////////////////////////
  983. void CLog::PushAssetScopeName(const char* sAssetType, const char* sName)
  984. {
  985. assert(sAssetType);
  986. assert(sName);
  987. SAssetScopeInfo as;
  988. as.sType = sAssetType;
  989. as.sName = sName;
  990. AZStd::scoped_lock scope_lock(m_assetScopeQueueLock);
  991. m_assetScopeQueue.push_back(as);
  992. }
  993. void CLog::PopAssetScopeName()
  994. {
  995. AZStd::scoped_lock scope_lock(m_assetScopeQueueLock);
  996. assert(!m_assetScopeQueue.empty());
  997. if (!m_assetScopeQueue.empty())
  998. {
  999. m_assetScopeQueue.pop_back();
  1000. }
  1001. }
  1002. //////////////////////////////////////////////////////////////////////////
  1003. const char* CLog::GetAssetScopeString()
  1004. {
  1005. AZStd::scoped_lock scope_lock(m_assetScopeQueueLock);
  1006. m_assetScopeString.clear();
  1007. for (size_t i = 0; i < m_assetScopeQueue.size(); i++)
  1008. {
  1009. m_assetScopeString += "[";
  1010. m_assetScopeString += m_assetScopeQueue[i].sType;
  1011. m_assetScopeString += "]";
  1012. m_assetScopeString += m_assetScopeQueue[i].sName;
  1013. if (i < m_assetScopeQueue.size() - 1)
  1014. {
  1015. m_assetScopeString += " > ";
  1016. }
  1017. }
  1018. return m_assetScopeString.c_str();
  1019. };
  1020. #endif
  1021. bool CLog::LogToMainThread(AZStd::string_view szString, ELogType logType, bool appendToPrevLine, SLogMsg::Destination destination)
  1022. {
  1023. if (CryGetCurrentThreadId() != m_nMainThreadId)
  1024. {
  1025. // When logging from other thread then main, push all log strings to queue.
  1026. constexpr size_t fixedBufferMaxSize = AZStd::variant_alternative_t<0, SLogMsg::MessageString>{}.max_size();
  1027. SLogMsg msg;
  1028. if (szString.size() <= fixedBufferMaxSize)
  1029. {
  1030. // Store string in fixed buffer if less than the fixed_string::max_size
  1031. msg.msg.emplace<0>(szString);
  1032. }
  1033. else
  1034. {
  1035. msg.msg.emplace<AZStd::string>(szString);
  1036. }
  1037. msg.m_appendToPreviousLine = appendToPrevLine;
  1038. msg.destination = destination;
  1039. msg.logType = logType;
  1040. m_threadSafeMsgQueue.push(msg);
  1041. return true;
  1042. }
  1043. return false;
  1044. }
  1045. //////////////////////////////////////////////////////////////////////
  1046. #if !defined(EXCLUDE_NORMAL_LOG)
  1047. void CLog::LogStringToFile(AZStd::string_view message, ELogType logType, bool appendToPrevLine, [[maybe_unused]] MessageQueueState queueState)
  1048. {
  1049. #if defined(_RELEASE) && defined(EXCLUDE_NORMAL_LOG) // no file logging in release
  1050. return;
  1051. #endif
  1052. if (message.empty())
  1053. {
  1054. return;
  1055. }
  1056. if (!m_pSystem || !AZ::IO::FileIOBase::GetInstance())
  1057. {
  1058. return;
  1059. }
  1060. // this is a temp timeStr, it is reused in many branches(moved here to reduce stack usage)
  1061. LogStringType timeStr;
  1062. message = RemoveColorCode(message);
  1063. bool bIsMainThread = LogToMainThread(message, logType, appendToPrevLine, SLogMsg::Destination::File) == false;
  1064. #if defined(_RELEASE)
  1065. if (!bIsMainThread)
  1066. {
  1067. return;
  1068. }
  1069. #endif
  1070. #if defined(SUPPORT_LOG_IDENTER)
  1071. if (bIsMainThread)
  1072. {
  1073. if (m_topIndenter)
  1074. {
  1075. m_topIndenter->DisplaySectionText();
  1076. }
  1077. tempString = m_indentWithString + tempString;
  1078. }
  1079. #endif
  1080. auto console = AZ::Interface<AZ::IConsole>::Get();
  1081. if (uint32_t dwCVarState;
  1082. console != nullptr
  1083. && console->GetCvarValue("log_IncludeTime", dwCVarState) == AZ::GetValueResult::Success)
  1084. {
  1085. // See the log_IncludeTime CVar description as to what
  1086. // values correspond to what time strings
  1087. switch (dwCVarState)
  1088. {
  1089. case 1:
  1090. case 5:
  1091. timeStr = GetHourMinuteSeconds();
  1092. break;
  1093. case 2:
  1094. timeStr = GetElapsedTimeInSeconds();
  1095. break;
  1096. case 3:
  1097. timeStr = GetHourMinuteSeconds();
  1098. timeStr += GetElapsedTimeInSeconds();
  1099. break;
  1100. case 4:
  1101. timeStr = GetElapsedTimeSinceStartInSeconds();
  1102. break;
  1103. case 6:
  1104. timeStr = GetDateAndHourMinuteSeconds();
  1105. break;
  1106. default:
  1107. break;
  1108. }
  1109. }
  1110. // do not output in release.
  1111. #if !defined(_RELEASE)
  1112. if (queueState == MessageQueueState::NotQueued)
  1113. {
  1114. if (!timeStr.empty())
  1115. {
  1116. AZ::Debug::Trace::Instance().OutputToRawAndDebugger(nullptr, timeStr.data());
  1117. }
  1118. AZ::Debug::Trace::Instance().OutputToRawAndDebugger(nullptr, message.data());
  1119. if (!message.ends_with('\n'))
  1120. {
  1121. AZ::Debug::Trace::Instance().OutputToRawAndDebugger(nullptr, "\n");
  1122. }
  1123. }
  1124. if (!bIsMainThread)
  1125. {
  1126. return;
  1127. }
  1128. #endif // !defined(_RELEASE)
  1129. //////////////////////////////////////////////////////////////////////////
  1130. // Call callback function.
  1131. for (auto callback : m_callbacks)
  1132. {
  1133. callback->OnWriteToFile(message, !appendToPrevLine);
  1134. }
  1135. ////////////////////////////////////////////////
  1136. //////////////////////////////////////////////////////////////////////////
  1137. // Write to file.
  1138. //////////////////////////////////////////////////////////////////////////
  1139. int logToFile = m_pLogWriteToFile ? m_pLogWriteToFile->GetIVal() : 1;
  1140. if (logToFile)
  1141. {
  1142. if (!m_logFileHandle.IsOpen())
  1143. {
  1144. OpenLogFile(m_szFilename, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath);
  1145. }
  1146. if (m_logFileHandle.IsOpen())
  1147. {
  1148. if (appendToPrevLine)
  1149. {
  1150. // if adding to a prior line erase the \n at the end.
  1151. m_logFileHandle.Seek(-2, AZ::IO::GenericStream::SeekMode::ST_SEEK_END);
  1152. }
  1153. if (!timeStr.empty())
  1154. {
  1155. m_logFileHandle.Write(timeStr.size(), timeStr.data());
  1156. }
  1157. m_logFileHandle.Write(message.size(), message.data());
  1158. if (!message.ends_with('\n'))
  1159. {
  1160. constexpr AZStd::string_view newline("\n");
  1161. m_logFileHandle.Write(newline.size(), newline.data());
  1162. }
  1163. #if !defined(KEEP_LOG_FILE_OPEN)
  1164. CloseLogFile();
  1165. #endif
  1166. // do not use FLUSH on log files. Doing so will slow the engine down greatly when logging.
  1167. // (the log is flushed automatically when an unhandled exception occurs)
  1168. }
  1169. }
  1170. }
  1171. void CLog::LogString(AZStd::string_view szString, ELogType logType)
  1172. {
  1173. if (szString.empty())
  1174. {
  1175. return;
  1176. }
  1177. if (LogToMainThread(szString, logType, false, SLogMsg::Destination::Default))
  1178. {
  1179. return;
  1180. }
  1181. for (auto callback : m_callbacks)
  1182. {
  1183. callback->OnWrite(szString, logType);
  1184. }
  1185. }
  1186. //same as above but to a file
  1187. //////////////////////////////////////////////////////////////////////
  1188. void CLog::LogToFileAppendWithPrevLine(const char* szFormat, ...)
  1189. {
  1190. if (!CheckLogFormatter(szFormat))
  1191. {
  1192. return;
  1193. }
  1194. if (m_pLogVerbosity && m_pLogVerbosity->GetIVal() < 0)
  1195. {
  1196. return;
  1197. }
  1198. if (!m_szFilename[0] || !szFormat)
  1199. {
  1200. return;
  1201. }
  1202. bool bfile = false, bconsole = false;
  1203. const char* szCommand = CheckAgainstVerbosity(szFormat, bfile, bconsole);
  1204. if (!bfile)
  1205. {
  1206. return;
  1207. }
  1208. va_list arglist;
  1209. va_start(arglist, szFormat);
  1210. auto szTemp = AZStd::fixed_string<MAX_TEMP_LENGTH_SIZE>::format_arg(szCommand, arglist);
  1211. va_end(arglist);
  1212. LogStringToFile(szTemp, ELogType::eAlways, true, MessageQueueState::NotQueued);
  1213. }
  1214. //log to the file specified in setfilename
  1215. //////////////////////////////////////////////////////////////////////
  1216. void CLog::LogToFile(const char* szFormat, ...)
  1217. {
  1218. if (!CheckLogFormatter(szFormat))
  1219. {
  1220. return;
  1221. }
  1222. if (m_pLogVerbosity && m_pLogVerbosity->GetIVal() < 0)
  1223. {
  1224. return;
  1225. }
  1226. if (!m_szFilename[0] || !szFormat)
  1227. {
  1228. return;
  1229. }
  1230. bool bfile = false, bconsole = false;
  1231. const char* szCommand = CheckAgainstVerbosity(szFormat, bfile, bconsole);
  1232. if (!bfile)
  1233. {
  1234. return;
  1235. }
  1236. va_list arglist;
  1237. va_start(arglist, szFormat);
  1238. auto szTemp = AZStd::fixed_string<MAX_TEMP_LENGTH_SIZE>::format_arg(szCommand, arglist);
  1239. va_end(arglist);
  1240. LogStringToFile(szTemp, ELogType::eAlways, false, MessageQueueState::NotQueued);
  1241. }
  1242. #endif // !defined(EXCLUDE_NORMAL_LOG)
  1243. //////////////////////////////////////////////////////////////////////
  1244. void CLog::CreateBackupFile() const
  1245. {
  1246. if (!m_backupLogs)
  1247. {
  1248. return;
  1249. }
  1250. #if AZ_LEGACY_CRYSYSTEM_TRAIT_ALLOW_CREATE_BACKUP_LOG_FILE
  1251. // simple:
  1252. // string bakpath = PathUtil::ReplaceExtension(m_szFilename,"bak");
  1253. // CopyFile(m_szFilename,bakpath.c_str(),false);
  1254. // boswej: only create a backup if logging to the engine root, otherwise the
  1255. // log output has been overridden and the user is responsible
  1256. AZStd::string logDir = PathUtil::RemoveSlash(PathUtil::ToUnixPath(PathUtil::GetParentDirectory(m_szFilename)));
  1257. AZStd::string sExt = PathUtil::GetExt(m_szFilename);
  1258. AZStd::string sFileWithoutExt = PathUtil::GetFileName(m_szFilename);
  1259. {
  1260. assert(::strstr(sFileWithoutExt.c_str(), ":") == 0);
  1261. assert(::strstr(sFileWithoutExt.c_str(), "\\") == 0);
  1262. }
  1263. PathUtil::RemoveExtension(sFileWithoutExt);
  1264. AZ::IO::FileIOBase* fileSystem = AZ::IO::FileIOBase::GetDirectInstance();
  1265. AZ::IO::HandleType inFileHandle = AZ::IO::InvalidHandle;
  1266. fileSystem->Open(m_szFilename, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, inFileHandle);
  1267. AZStd::string sBackupNameAttachment;
  1268. // parse backup name attachment
  1269. // e.g. BackupNameAttachment="attachment name"
  1270. if (inFileHandle != AZ::IO::InvalidHandle)
  1271. {
  1272. bool bKeyFound = false;
  1273. AZStd::string sName;
  1274. while (!fileSystem->Eof(inFileHandle))
  1275. {
  1276. uint8 c = static_cast<uint8>(AZ::IO::GetC(inFileHandle));
  1277. if (c == '\"')
  1278. {
  1279. if (!bKeyFound)
  1280. {
  1281. bKeyFound = true;
  1282. if (sName.find("BackupNameAttachment=") == AZStd::string::npos)
  1283. {
  1284. AZ::Debug::Platform::OutputToDebugger("CrySystem Log", "Log::CreateBackupFile ERROR '");
  1285. AZ::Debug::Platform::OutputToDebugger({}, sName.c_str());
  1286. AZ::Debug::Platform::OutputToDebugger({}, "' not recognized \n");
  1287. assert(0); // broken log file? - first line should include this name - written by LogVersion()
  1288. return;
  1289. }
  1290. sName.clear();
  1291. }
  1292. else
  1293. {
  1294. sBackupNameAttachment = sName;
  1295. break;
  1296. }
  1297. continue;
  1298. }
  1299. if (c >= ' ')
  1300. {
  1301. sName += c;
  1302. }
  1303. else
  1304. {
  1305. break;
  1306. }
  1307. }
  1308. fileSystem->Close(inFileHandle);
  1309. }
  1310. AZStd::string bakdest = PathUtil::Make(LOG_BACKUP_PATH, sFileWithoutExt + sBackupNameAttachment + "." + sExt);
  1311. fileSystem->CreatePath(LOG_BACKUP_PATH);
  1312. azstrcpy(m_sBackupFilename, AZ_ARRAY_SIZE(m_sBackupFilename), bakdest.c_str());
  1313. // Remove any existing backup file with the same name first since the copy will fail otherwise.
  1314. fileSystem->Remove(m_sBackupFilename);
  1315. fileSystem->Copy(m_szFilename, bakdest.c_str());
  1316. #endif // AZ_LEGACY_CRYSYSTEM_TRAIT_ALLOW_CREATE_BACKUP_LOG_FILE
  1317. }
  1318. void CLog::CheckAndPruneBackupLogs() const
  1319. {
  1320. AZ::IO::FileIOBase* fileSystem = AZ::IO::FileIOBase::GetDirectInstance();
  1321. AZ::u64 totalBackupDirectorySize = 0;
  1322. struct fileInfo
  1323. {
  1324. time_t modTime;
  1325. AZStd::string filename;
  1326. AZ::u64 filesize;
  1327. fileInfo(time_t _modTime, const char* _name, AZ::u64 _size)
  1328. {
  1329. modTime = _modTime;
  1330. filename = _name;
  1331. filesize = _size;
  1332. }
  1333. };
  1334. AZStd::list<fileInfo> fileInfoList;
  1335. // Now that we've copied the new log over, lets check the size of the backup folder and trim it as necessary to keep it within appropriate limits
  1336. fileSystem->FindFiles(LOG_BACKUP_PATH, "*",
  1337. [&totalBackupDirectorySize, &fileSystem, &fileInfoList](const char* fileName)
  1338. {
  1339. AZ::u64 size;
  1340. fileSystem->Size(fileName, size);
  1341. AZ::u64 modTime = fileSystem->ModificationTime(fileName);
  1342. fileInfoList.push_back(fileInfo(modTime, fileName, size));
  1343. totalBackupDirectorySize += size;
  1344. return true;
  1345. });
  1346. AZ::u64 max_size = LogCVars::max_backup_directory_size_mb;
  1347. max_size = max_size << 20; // Convert from MB to bytes
  1348. if (totalBackupDirectorySize > max_size)
  1349. {
  1350. // Sort the list from lowest to highest modtime (oldest to newest logs)
  1351. fileInfoList.sort([](const fileInfo &a, const fileInfo&b) { return a.modTime < b.modTime; });
  1352. while (totalBackupDirectorySize > max_size && fileInfoList.size() > 0)
  1353. {
  1354. fileSystem->Remove(fileInfoList.front().filename.c_str());
  1355. totalBackupDirectorySize -= fileInfoList.front().filesize;
  1356. fileInfoList.pop_front();
  1357. }
  1358. }
  1359. }
  1360. //set the file used to log to disk
  1361. //////////////////////////////////////////////////////////////////////
  1362. bool CLog::SetFileName(const char* fileNameOrAbsolutePath, bool backupLogs)
  1363. {
  1364. m_backupLogs = backupLogs;
  1365. if (!fileNameOrAbsolutePath)
  1366. {
  1367. return false;
  1368. }
  1369. AZStd::string previousFilename = m_szFilename;
  1370. azstrncpy(m_szFilename, AZ_ARRAY_SIZE(m_szFilename), fileNameOrAbsolutePath, sizeof(m_szFilename));
  1371. CreateBackupFile();
  1372. if (m_logFileHandle.IsOpen() && m_szFilename != previousFilename)
  1373. {
  1374. CloseLogFile();
  1375. if (!OpenLogFile(m_szFilename, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath))
  1376. {
  1377. // Failed to open/create the new file. Go back to the previous
  1378. // state of the log appending to the previous file.
  1379. azstrncpy(m_szFilename, AZ_ARRAY_SIZE(m_szFilename), previousFilename.c_str(), sizeof(m_szFilename));
  1380. OpenLogFile(m_szFilename, AZ::IO::OpenMode::ModeAppend);
  1381. return false;
  1382. }
  1383. }
  1384. return true;
  1385. }
  1386. //////////////////////////////////////////////////////////////////////////
  1387. const char* CLog::GetFileName()
  1388. {
  1389. return m_szFilename;
  1390. }
  1391. const char* CLog::GetBackupFileName()
  1392. {
  1393. return m_sBackupFilename;
  1394. }
  1395. //////////////////////////////////////////////////////////////////////
  1396. void CLog::UpdateLoadingScreen(const char* szFormat, ...)
  1397. {
  1398. #if !defined(EXCLUDE_NORMAL_LOG)
  1399. if (szFormat)
  1400. {
  1401. // This function is OK to call with nullptr, but then it does not log anything.
  1402. va_list args;
  1403. va_start(args, szFormat);
  1404. LogV(ILog::eMessage, szFormat, args);
  1405. va_end(args);
  1406. }
  1407. #endif
  1408. }
  1409. //////////////////////////////////////////////////////////////////////////
  1410. int CLog::GetVerbosityLevel()
  1411. {
  1412. if (m_pLogVerbosity)
  1413. {
  1414. return (m_pLogVerbosity->GetIVal());
  1415. }
  1416. return (0);
  1417. }
  1418. //////////////////////////////////////////////////////////////////////////////////////////////////
  1419. // Checks the verbosity of the message and returns NULL if the message must NOT be
  1420. // logged, or the pointer to the part of the message that should be logged
  1421. // NOTE:
  1422. // Normally, this is either the pText pointer itself, or the pText+1, meaning
  1423. // the first verbosity character may be cut off)
  1424. // This is done in order to avoid modification of const char*, which may cause GPF
  1425. // sometimes, or kill the verbosity qualifier in the text that's gonna be passed next time.
  1426. const char* CLog::CheckAgainstVerbosity(const char* pText, bool& logtofile, bool& logtoconsole, const uint8 DefaultVerbosity)
  1427. {
  1428. // the max verbosity (most detailed level)
  1429. #if defined(RELEASE)
  1430. const unsigned char nMaxVerbosity = 0;
  1431. #else // #if defined(RELEASE)
  1432. const unsigned char nMaxVerbosity = 8;
  1433. #endif // #if defined(RELEASE)
  1434. // the current verbosity of the log
  1435. int nLogVerbosityConsole = m_pLogVerbosity ? m_pLogVerbosity->GetIVal() : nMaxVerbosity;
  1436. int nLogVerbosityFile = m_pLogWriteToFileVerbosity ? m_pLogWriteToFileVerbosity->GetIVal() : nMaxVerbosity;
  1437. logtoconsole = (nLogVerbosityConsole >= DefaultVerbosity);
  1438. //to preserve logging to TTY, logWriteToFile logic has been moved to inside logStringToFile
  1439. //int logToFileCVar = m_pLogWriteToFile ? m_pLogWriteToFile->GetIVal() : 1;
  1440. logtofile = (nLogVerbosityFile >= DefaultVerbosity);
  1441. return pText;
  1442. }
  1443. //////////////////////////////////////////////////////////////////////////
  1444. void CLog::AddCallback(ILogCallback* pCallback)
  1445. {
  1446. stl::push_back_unique(m_callbacks, pCallback);
  1447. }
  1448. //////////////////////////////////////////////////////////////////////////
  1449. void CLog::RemoveCallback(ILogCallback* pCallback)
  1450. {
  1451. m_callbacks.remove(pCallback);
  1452. }
  1453. //////////////////////////////////////////////////////////////////////////
  1454. void CLog::Update()
  1455. {
  1456. if (CryGetCurrentThreadId() == m_nMainThreadId)
  1457. {
  1458. if (!m_threadSafeMsgQueue.empty())
  1459. {
  1460. AZStd::scoped_lock lock(m_threadSafeMsgQueue.get_lock()); // Get the lock and hold onto it until we clear the entire queue (prevents other threads adding more things in while we clear it)
  1461. // Must be called from main thread
  1462. constexpr auto GetMessageView = [](auto&& messageView) constexpr -> AZStd::string_view
  1463. {
  1464. return messageView;
  1465. };
  1466. SLogMsg msg;
  1467. while (m_threadSafeMsgQueue.try_pop(msg))
  1468. {
  1469. AZStd::string_view messageView = AZStd::visit(GetMessageView, msg.msg);
  1470. if (msg.destination == SLogMsg::Destination::Console)
  1471. {
  1472. LogStringToConsole(messageView, msg.logType, msg.m_appendToPreviousLine);
  1473. }
  1474. else if (msg.destination == SLogMsg::Destination::File)
  1475. {
  1476. LogStringToFile(messageView, msg.logType, msg.m_appendToPreviousLine, MessageQueueState::Queued);
  1477. }
  1478. else
  1479. {
  1480. LogString(messageView, msg.logType);
  1481. }
  1482. }
  1483. stl::free_container(m_threadSafeMsgQueue);
  1484. }
  1485. if (LogCVars::s_log_tick != 0)
  1486. {
  1487. static AZ::TimeUs t0 = AZ::GetElapsedTimeUs();
  1488. const AZ::TimeUs t1 = AZ::GetElapsedTimeUs();
  1489. const float tSec = AZ::TimeUsToSeconds(t1 - t0);
  1490. if (tSec > LogCVars::s_log_tick)
  1491. {
  1492. t0 = t1;
  1493. char sTime[128];
  1494. time_t ltime;
  1495. time(&ltime);
  1496. #ifdef AZ_COMPILER_MSVC
  1497. struct tm today;
  1498. localtime_s(&today, &ltime);
  1499. strftime(sTime, sizeof(sTime) - 1, "<%H:%M:%S> ", &today);
  1500. #else
  1501. auto today = localtime(&ltime);
  1502. strftime(sTime, sizeof(sTime) - 1, "<%H:%M:%S> ", today);
  1503. #endif
  1504. LogAlways("<tick> %s", sTime);
  1505. }
  1506. }
  1507. }
  1508. }
  1509. //////////////////////////////////////////////////////////////////////////
  1510. const char* CLog::GetModuleFilter()
  1511. {
  1512. if (m_pLogModule)
  1513. {
  1514. return m_pLogModule->GetString();
  1515. }
  1516. return "";
  1517. }
  1518. void CLog::FlushAndClose()
  1519. {
  1520. #if defined(KEEP_LOG_FILE_OPEN)
  1521. CloseLogFile();
  1522. #endif
  1523. }
  1524. void CLog::Flush()
  1525. {
  1526. #if defined(KEEP_LOG_FILE_OPEN)
  1527. m_logFileHandle.Flush();
  1528. #endif
  1529. }
  1530. #if defined(KEEP_LOG_FILE_OPEN)
  1531. void CLog::LogFlushFile([[maybe_unused]] IConsoleCmdArgs* pArgs)
  1532. {
  1533. if ((gEnv) && (gEnv->pLog))
  1534. {
  1535. gEnv->pLog->Flush();
  1536. }
  1537. }
  1538. #endif