Output.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "CommonCorePch.h"
  6. #ifndef USING_PAL_STDLIB
  7. #include <string.h>
  8. #include <stdarg.h>
  9. #endif
  10. // Initialization order
  11. // AB AutoSystemInfo
  12. // AD PerfCounter
  13. // AE PerfCounterSet
  14. // AM Output/Configuration
  15. // AN MemProtectHeap
  16. // AP DbgHelpSymbolManager
  17. // AQ CFGLogger
  18. // AR LeakReport
  19. // AS JavascriptDispatch/RecyclerObjectDumper
  20. // AT HeapAllocator/RecyclerHeuristic
  21. // AU RecyclerWriteBarrierManager
  22. #pragma warning(disable:4075) // initializers put in unrecognized initialization area on purpose
  23. #pragma init_seg(".CRT$XCAM")
  24. bool Output::s_useDebuggerWindow = false;
  25. CriticalSection Output::s_critsect;
  26. AutoFILE Output::s_outputFile; // Create a separate output file that is not thread-local.
  27. #ifdef ENABLE_TRACE
  28. Js::ILogger* Output::s_inMemoryLogger = nullptr;
  29. #ifdef STACK_BACK_TRACE
  30. Js::IStackTraceHelper* Output::s_stackTraceHelper = nullptr;
  31. #endif
  32. unsigned int Output::s_traceEntryId = 0;
  33. #endif
  34. THREAD_ST FILE* Output::s_file = nullptr;
  35. #ifdef _WIN32
  36. THREAD_ST char16* Output::buffer = nullptr;
  37. THREAD_ST size_t Output::bufferAllocSize = 0;
  38. THREAD_ST size_t Output::bufferFreeSize = 0;
  39. #endif
  40. THREAD_ST size_t Output::s_Column = 0;
  41. THREAD_ST WORD Output::s_color = 0;
  42. THREAD_ST bool Output::s_hasColor = false;
  43. THREAD_ST bool Output::s_capture = false;
  44. THREAD_ST bool Output::hasDoneAlignPrefixForThisLine = false;
  45. THREAD_ST bool Output::usingCustomAlignAndPrefix = false;
  46. THREAD_ST size_t Output::align = 0;
  47. THREAD_ST const char16* Output::prefix = nullptr;
  48. #define MAX_OUTPUT_BUFFER_SIZE 10 * 1024 * 1024 // 10 MB maximum before we force a flush
  49. size_t __cdecl
  50. Output::VerboseNote(const char16 * format, ...)
  51. {
  52. #ifdef ENABLE_TRACE
  53. if (Js::Configuration::Global.flags.Verbose)
  54. {
  55. AutoCriticalSection autocs(&s_critsect);
  56. va_list argptr;
  57. va_start(argptr, format);
  58. size_t size = vfwprintf(stdout, format, argptr);
  59. fflush(stdout);
  60. va_end(argptr);
  61. return size;
  62. }
  63. #endif
  64. return 0;
  65. }
  66. #ifdef ENABLE_TRACE
  67. size_t __cdecl
  68. Output::Trace(Js::Phase phase, const char16 *form, ...)
  69. {
  70. size_t retValue = 0;
  71. if(Js::Configuration::Global.flags.Trace.IsEnabled(phase))
  72. {
  73. va_list argptr;
  74. va_start(argptr, form);
  75. retValue += Output::VTrace(_u("%s: "), Js::PhaseNames[static_cast<int>(phase)], form, argptr);
  76. va_end(argptr);
  77. }
  78. return retValue;
  79. }
  80. size_t __cdecl
  81. Output::Trace2(Js::Phase phase, const char16 *form, ...)
  82. {
  83. size_t retValue = 0;
  84. if (Js::Configuration::Global.flags.Trace.IsEnabled(phase))
  85. {
  86. va_list argptr;
  87. va_start(argptr, form);
  88. retValue += Output::VPrint(form, argptr);
  89. va_end(argptr);
  90. }
  91. return retValue;
  92. }
  93. size_t __cdecl
  94. Output::TraceWithPrefix(Js::Phase phase, const char16 prefix[], const char16 *form, ...)
  95. {
  96. size_t retValue = 0;
  97. if (Js::Configuration::Global.flags.Trace.IsEnabled(phase))
  98. {
  99. va_list argptr;
  100. va_start(argptr, form);
  101. WCHAR prefixValue[512];
  102. _snwprintf_s(prefixValue, _countof(prefixValue), _TRUNCATE, _u("%s: %s: "), Js::PhaseNames[static_cast<int>(phase)], prefix);
  103. retValue += Output::VTrace(_u("%s"), prefixValue, form, argptr);
  104. va_end(argptr);
  105. }
  106. return retValue;
  107. }
  108. size_t __cdecl
  109. Output::TraceWithFlush(Js::Phase phase, const char16 *form, ...)
  110. {
  111. size_t retValue = 0;
  112. if(Js::Configuration::Global.flags.Trace.IsEnabled(phase))
  113. {
  114. va_list argptr;
  115. va_start(argptr, form);
  116. retValue += Output::VTrace(_u("%s:"), Js::PhaseNames[static_cast<int>(phase)], form, argptr);
  117. Output::Flush();
  118. va_end(argptr);
  119. }
  120. return retValue;
  121. }
  122. size_t __cdecl
  123. Output::TraceWithFlush(Js::Flag flag, const char16 *form, ...)
  124. {
  125. size_t retValue = 0;
  126. if (Js::Configuration::Global.flags.IsEnabled(flag))
  127. {
  128. va_list argptr;
  129. va_start(argptr, form);
  130. retValue += Output::VTrace(_u("[-%s]::"), Js::FlagNames[static_cast<int>(flag)], form, argptr);
  131. Output::Flush();
  132. va_end(argptr);
  133. }
  134. return retValue;
  135. }
  136. size_t
  137. Output::VTrace(const char16* shortPrefixFormat, const char16* prefix, const char16 *form, va_list argptr)
  138. {
  139. size_t retValue = 0;
  140. #if CONFIG_RICH_TRACE_FORMAT
  141. if (CONFIG_FLAG(RichTraceFormat))
  142. {
  143. InterlockedIncrement(&s_traceEntryId);
  144. retValue += Output::Print(_u("[%d ~%d %s] "), s_traceEntryId, ::GetCurrentThreadId(), prefix);
  145. }
  146. else
  147. #endif
  148. {
  149. retValue += Output::Print(shortPrefixFormat, prefix);
  150. }
  151. retValue += Output::VPrint(form, argptr);
  152. #ifdef STACK_BACK_TRACE
  153. // Print stack trace.
  154. if (s_stackTraceHelper)
  155. {
  156. const ULONG c_framesToSkip = 2; // Skip 2 frames -- Output::VTrace and Output::Trace.
  157. const ULONG c_frameCount = 10; // TODO: make it configurable.
  158. const char16 callStackPrefix[] = _u("call stack:");
  159. if (s_inMemoryLogger)
  160. {
  161. // Trace just addresses of functions, avoid symbol info as it takes too much memory.
  162. // One line for whole stack trace for easier parsing on the jd side.
  163. const size_t c_msgCharCount = _countof(callStackPrefix) + (1 + sizeof(void*) * 2) * c_frameCount; // 2 hexadecimal digits per byte + 1 for space.
  164. char16 callStackMsg[c_msgCharCount];
  165. void* frames[c_frameCount];
  166. size_t start = 0;
  167. size_t temp;
  168. temp = _snwprintf_s(callStackMsg, _countof(callStackMsg), _TRUNCATE, _u("%s"), callStackPrefix);
  169. Assert(temp != -1);
  170. start += temp;
  171. ULONG framesObtained = s_stackTraceHelper->GetStackTrace(c_framesToSkip, c_frameCount, frames);
  172. Assert(framesObtained <= c_frameCount);
  173. for (ULONG i = 0; i < framesObtained && i < c_frameCount; ++i)
  174. {
  175. Assert(_countof(callStackMsg) >= start);
  176. temp = _snwprintf_s(callStackMsg + start, _countof(callStackMsg) - start, _TRUNCATE, _u(" %p"), frames[i]);
  177. Assert(temp != -1);
  178. start += temp;
  179. }
  180. retValue += Output::Print(_u("%s\n"), callStackMsg);
  181. }
  182. else
  183. {
  184. // Trace with full symbol info.
  185. retValue += Output::Print(_u("%s\n"), callStackPrefix);
  186. retValue += s_stackTraceHelper->PrintStackTrace(c_framesToSkip, c_frameCount);
  187. }
  188. }
  189. #endif
  190. return retValue;
  191. }
  192. #ifdef BGJIT_STATS
  193. size_t __cdecl
  194. Output::TraceStats(Js::Phase phase, const char16 *form, ...)
  195. {
  196. if(PHASE_STATS1(phase))
  197. {
  198. va_list argptr;
  199. va_start(argptr, form);
  200. size_t ret_val = Output::VPrint(form, argptr);
  201. va_end(argptr);
  202. return ret_val;
  203. }
  204. return 0;
  205. }
  206. #endif
  207. #endif // ENABLE_TRACE
  208. ///----------------------------------------------------------------------------
  209. ///
  210. /// Output::Print
  211. ///
  212. /// Print the given format string.
  213. ///
  214. ///
  215. ///----------------------------------------------------------------------------
  216. size_t __cdecl
  217. Output::Print(const char16 *form, ...)
  218. {
  219. va_list argptr;
  220. va_start(argptr, form);
  221. size_t ret_val = Output::VPrint(form, argptr);
  222. va_end(argptr);
  223. return ret_val;
  224. }
  225. size_t __cdecl
  226. Output::Print(int column, const char16 *form, ...)
  227. {
  228. Output::SkipToColumn(column);
  229. va_list argptr;
  230. va_start(argptr, form);
  231. size_t ret_val = Output::VPrint(form, argptr);
  232. va_end(argptr);
  233. return ret_val;
  234. }
  235. size_t __cdecl
  236. Output::VPrint(const char16 *form, va_list argptr)
  237. {
  238. char16 buf[2048];
  239. size_t size;
  240. size = _vsnwprintf_s(buf, _countof(buf), _TRUNCATE, form, argptr);
  241. if(size == -1)
  242. {
  243. size = _countof(buf) - 1; // characters written, excluding the terminating null
  244. }
  245. return Output::PrintBuffer(buf, size);
  246. }
  247. //
  248. // buf: a null terminated string
  249. // size: characters in buf, excluding the terminating null ==> wcslen(buf)
  250. //
  251. size_t __cdecl
  252. Output::PrintBuffer(const char16 * buf, size_t size)
  253. {
  254. // Handle custom line prefixing
  255. bool internallyAllocatedBuffer = false;
  256. if (usingCustomAlignAndPrefix)
  257. {
  258. if (hasDoneAlignPrefixForThisLine && wcschr(buf, '\n') == nullptr)
  259. {
  260. // no newlines, and we've already prefixed this line, so nothing to do
  261. }
  262. else
  263. {
  264. size_t newbufsize = size + align;
  265. char16* newbuf = (char16*)calloc(newbufsize, sizeof(char16));
  266. AssertOrFailFastMsg(newbuf != nullptr, "Ran out of memory while printing output");
  267. internallyAllocatedBuffer = true;
  268. const char16* currentReadIndex = buf;
  269. char16* currentWriteIndex = newbuf;
  270. auto ensureSpace = [&currentWriteIndex, &newbuf, &newbufsize](size_t numCharsWantToWrite)
  271. {
  272. size_t charsWritten = (currentWriteIndex - newbuf); // pointer subtraction is number of elements of pointed type between pointers
  273. size_t remaining = newbufsize - charsWritten;
  274. if (numCharsWantToWrite + 1 > remaining)
  275. {
  276. char16* tempbuf = (char16*)realloc(newbuf, newbufsize * sizeof(char16) * 2);
  277. AssertOrFailFastMsg(tempbuf != nullptr, "Ran out of memory while printing output");
  278. newbuf = tempbuf;
  279. newbufsize = newbufsize * 2;
  280. currentWriteIndex = newbuf + charsWritten;
  281. }
  282. };
  283. const size_t prefixlength = wcslen(prefix);
  284. size_t oldS_Column = Output::s_Column;
  285. while (currentReadIndex < buf + size)
  286. {
  287. if (!hasDoneAlignPrefixForThisLine)
  288. {
  289. // attempt to write the alignment
  290. {
  291. unsigned int alignspacesneeded = 1; // always put at least one space
  292. if (oldS_Column < align)
  293. {
  294. alignspacesneeded = (unsigned int)(align - oldS_Column);
  295. }
  296. ensureSpace(alignspacesneeded);
  297. for (unsigned int i = 0; i < alignspacesneeded; i++)
  298. {
  299. *(currentWriteIndex++) = ' ';
  300. }
  301. }
  302. // attempt to write the prefix
  303. ensureSpace(prefixlength);
  304. js_wmemcpy_s(currentWriteIndex, (newbuf + newbufsize) - currentWriteIndex, Output::prefix, prefixlength);
  305. currentWriteIndex += prefixlength;
  306. oldS_Column = align + prefixlength;
  307. hasDoneAlignPrefixForThisLine = true;
  308. }
  309. const char16* endOfLine = wcschr(currentReadIndex, '\n');
  310. size_t charsToCopy = 0;
  311. if (endOfLine != nullptr)
  312. {
  313. charsToCopy = (endOfLine - currentReadIndex) + 1; // We want to grab the newline character as part of this line
  314. oldS_Column = 0; // We're ending this line, and want the next to be calculated properly
  315. hasDoneAlignPrefixForThisLine = false; // The next line will need this
  316. }
  317. else
  318. {
  319. charsToCopy = (buf + size) - currentReadIndex; // the rest of the input buffer
  320. oldS_Column += charsToCopy; // Will be reset anyway later on
  321. }
  322. ensureSpace(endOfLine - currentReadIndex);
  323. js_wmemcpy_s(currentWriteIndex, (newbuf + newbufsize) - currentWriteIndex, currentReadIndex, charsToCopy);
  324. currentReadIndex += charsToCopy;
  325. currentWriteIndex += charsToCopy;
  326. }
  327. // null terminate becuase there's no real reason not to
  328. ensureSpace(1);
  329. *(currentWriteIndex++) = '\0';
  330. buf = newbuf;
  331. size = (currentWriteIndex - newbuf) - 1; // not counting the terminator here though, to align with vsnwprintf_s's behavior
  332. }
  333. }
  334. Output::s_Column += size;
  335. const char16 * endbuf = wcschr(buf, '\n');
  336. while (endbuf != nullptr)
  337. {
  338. Output::s_Column = size - (endbuf - buf) - 1;
  339. endbuf = wcschr(endbuf + 1, '\n');
  340. }
  341. bool useConsoleOrFile = true;
  342. if (!Output::s_capture)
  343. {
  344. if (Output::s_useDebuggerWindow)
  345. {
  346. OutputDebugStringW(buf);
  347. useConsoleOrFile = false;
  348. }
  349. #ifdef ENABLE_TRACE
  350. if (Output::s_inMemoryLogger)
  351. {
  352. s_inMemoryLogger->Write(buf);
  353. useConsoleOrFile = false;
  354. }
  355. #endif
  356. }
  357. if (useConsoleOrFile)
  358. {
  359. if (s_file == nullptr || Output::s_capture)
  360. {
  361. #ifdef _WIN32
  362. bool addToBuffer = true;
  363. if (Output::bufferFreeSize < size + 1)
  364. {
  365. if (Output::bufferAllocSize > MAX_OUTPUT_BUFFER_SIZE && !Output::s_capture)
  366. {
  367. Output::Flush();
  368. if (Output::bufferFreeSize < size + 1)
  369. {
  370. DirectPrint(buf);
  371. addToBuffer = false;
  372. }
  373. }
  374. else
  375. {
  376. size_t oldBufferSize = bufferAllocSize - bufferFreeSize;
  377. size_t newBufferAllocSize = (bufferAllocSize + size + 1) * 4 / 3;
  378. char16 * newBuffer = (char16 *)realloc(buffer, (newBufferAllocSize * sizeof(char16)));
  379. if (newBuffer == nullptr)
  380. {
  381. // See if I can just flush it and print directly
  382. Output::Flush();
  383. // Reset the buffer
  384. free(Output::buffer);
  385. Output::buffer = nullptr;
  386. Output::bufferAllocSize = 0;
  387. Output::bufferFreeSize = 0;
  388. // Print it directly
  389. DirectPrint(buf);
  390. addToBuffer = false;
  391. }
  392. else
  393. {
  394. bufferAllocSize = newBufferAllocSize;
  395. buffer = newBuffer;
  396. bufferFreeSize = bufferAllocSize - oldBufferSize;
  397. }
  398. }
  399. }
  400. if (addToBuffer)
  401. {
  402. Assert(Output::bufferFreeSize >= size + 1);
  403. memcpy_s(Output::buffer + Output::bufferAllocSize - Output::bufferFreeSize, Output::bufferFreeSize * sizeof(char16),
  404. buf, size * sizeof(char16));
  405. bufferFreeSize -= size;
  406. Output::buffer[Output::bufferAllocSize - Output::bufferFreeSize] = _u('\0'); // null terminate explicitly
  407. }
  408. #else
  409. DirectPrint(buf);
  410. #endif
  411. }
  412. else
  413. {
  414. fwprintf_s(Output::s_file, _u("%s"), buf);
  415. }
  416. if(s_outputFile != nullptr && !Output::s_capture)
  417. {
  418. fwprintf_s(s_outputFile, _u("%s"), buf);
  419. }
  420. }
  421. Output::Flush();
  422. return size;
  423. }
  424. void Output::Flush()
  425. {
  426. if (s_capture)
  427. {
  428. return;
  429. }
  430. #ifdef _WIN32
  431. if (bufferFreeSize != bufferAllocSize)
  432. {
  433. DirectPrint(Output::buffer);
  434. bufferFreeSize = bufferAllocSize;
  435. }
  436. #endif
  437. if(s_outputFile != nullptr)
  438. {
  439. fflush(s_outputFile);
  440. }
  441. _flushall();
  442. }
  443. void Output::DirectPrint(char16 const * string)
  444. {
  445. AutoCriticalSection autocs(&s_critsect);
  446. // xplat-todo: support console color
  447. #ifdef _WIN32
  448. WORD oldValue = 0;
  449. BOOL restoreColor = FALSE;
  450. HANDLE hConsole = NULL;
  451. if (Output::s_hasColor)
  452. {
  453. _CONSOLE_SCREEN_BUFFER_INFO info;
  454. hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  455. if (hConsole && GetConsoleScreenBufferInfo(hConsole, &info))
  456. {
  457. oldValue = info.wAttributes;
  458. restoreColor = SetConsoleTextAttribute(hConsole, Output::s_color);
  459. }
  460. }
  461. #endif // _WIN32
  462. fwprintf(stdout, _u("%s"), string);
  463. // xplat-todo: support console color
  464. #ifdef _WIN32
  465. if (restoreColor)
  466. {
  467. SetConsoleTextAttribute(hConsole, oldValue);
  468. }
  469. #endif // _WIN32
  470. }
  471. ///----------------------------------------------------------------------------
  472. ///
  473. /// Output::SkipToColumn
  474. ///
  475. /// Inserts spaces up to the column passed in.
  476. ///
  477. ///----------------------------------------------------------------------------
  478. void
  479. Output::SkipToColumn(size_t column)
  480. {
  481. size_t columnbias = 0;
  482. // If we're using a custom alignment and prefix, we want to do this relative to that
  483. if (usingCustomAlignAndPrefix)
  484. {
  485. // If we've already added the alignment and prefix, we need to add the alignment to our column number here
  486. columnbias = align + wcslen(prefix);
  487. }
  488. size_t reference = 0;
  489. if (Output::s_Column > columnbias)
  490. {
  491. reference = Output::s_Column - columnbias;
  492. }
  493. if (column <= reference)
  494. {
  495. Output::Print(_u(" "));
  496. return;
  497. }
  498. // compute distance to our destination
  499. size_t dist = column - reference;
  500. // Print at least one space
  501. while (dist > 0)
  502. {
  503. Output::Print(_u(" "));
  504. dist--;
  505. }
  506. }
  507. FILE*
  508. Output::GetFile()
  509. {
  510. return Output::s_file;
  511. }
  512. FILE*
  513. Output::SetFile(FILE *file)
  514. {
  515. Output::Flush();
  516. FILE *oldfile = Output::s_file;
  517. Output::s_file = file;
  518. return oldfile;
  519. }
  520. void
  521. Output::SetOutputFile(FILE* file)
  522. {
  523. if(s_outputFile != nullptr)
  524. {
  525. AssertMsg(false, "Output file is being set twice.");
  526. }
  527. else
  528. {
  529. s_outputFile = file;
  530. }
  531. }
  532. FILE*
  533. Output::GetOutputFile()
  534. {
  535. return s_outputFile;
  536. }
  537. #ifdef ENABLE_TRACE
  538. void
  539. Output::SetInMemoryLogger(Js::ILogger* logger)
  540. {
  541. AssertMsg(s_inMemoryLogger == nullptr, "This cannot be called more than once.");
  542. s_inMemoryLogger = logger;
  543. }
  544. #ifdef STACK_BACK_TRACE
  545. void
  546. Output::SetStackTraceHelper(Js::IStackTraceHelper* helper)
  547. {
  548. AssertMsg(s_stackTraceHelper == nullptr, "This cannot be called more than once.");
  549. s_stackTraceHelper = helper;
  550. }
  551. #endif
  552. #endif // ENABLE_TRACE
  553. //
  554. // Sets the foreground color and returns the old color. Returns 0 on failure
  555. //
  556. WORD
  557. Output::SetConsoleForeground(WORD color)
  558. {
  559. AutoCriticalSection autocs(&s_critsect);
  560. // xplat-todo: support console color
  561. #ifdef _WIN32
  562. _CONSOLE_SCREEN_BUFFER_INFO info;
  563. HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  564. if (hConsole && GetConsoleScreenBufferInfo(hConsole, &info))
  565. {
  566. Output::Flush();
  567. Output::s_color = color | (info.wAttributes & ~15);
  568. Output::s_hasColor = Output::s_color != info.wAttributes;
  569. return info.wAttributes;
  570. }
  571. #endif // _WIN32
  572. return 0;
  573. }
  574. void
  575. Output::CaptureStart()
  576. {
  577. Assert(!s_capture);
  578. Output::Flush();
  579. s_capture = true;
  580. }
  581. char16 *
  582. Output::CaptureEnd()
  583. {
  584. Assert(s_capture);
  585. s_capture = false;
  586. #ifdef _WIN32
  587. bufferFreeSize = 0;
  588. bufferAllocSize = 0;
  589. char16 * returnBuffer = buffer;
  590. buffer = nullptr;
  591. #else
  592. char16 * returnBuffer = nullptr;
  593. #endif
  594. return returnBuffer;
  595. }
  596. void
  597. Output::SetAlignAndPrefix(unsigned int align, const char16 *prefix)
  598. {
  599. Output::hasDoneAlignPrefixForThisLine = false;
  600. Output::usingCustomAlignAndPrefix = true;
  601. Output::prefix = prefix;
  602. Output::align = align;
  603. }
  604. void
  605. Output::ResetAlignAndPrefix()
  606. {
  607. Output::hasDoneAlignPrefixForThisLine = false;
  608. Output::usingCustomAlignAndPrefix = false;
  609. Output::prefix = nullptr;
  610. Output::align = 0;
  611. }