Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

800 lines
20 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 1993.
  5. //
  6. // File: log.cxx
  7. //
  8. // Contents: Simple logging support.
  9. //
  10. // History: 02-08-94 DavidMun Created
  11. //
  12. //----------------------------------------------------------------------------
  13. #include <headers.hxx>
  14. #pragma hdrstop
  15. #include "jt.hxx"
  16. //
  17. // Forward references for private funcs
  18. //
  19. static const CHAR *GetTimeStamp();
  20. static const CHAR *GetCpuStr();
  21. static const CHAR *GetVideoStr();
  22. //+---------------------------------------------------------------------------
  23. //
  24. // Member: CLog::CLog, public
  25. //
  26. // Synopsis: Initialize member variables
  27. //
  28. // Arguments: [szTestTitle] - title of test being logged
  29. // [szLogFile] - filename to use if LOG_TOFILE used
  30. // [flDefaultDestinations] - LOG_TO* bits to use if none are
  31. // specified in call to Write()
  32. // [flLogInfoLevel] - if bitwise and of this mask and bits
  33. // passed to Write != 0, line is
  34. // logged.
  35. //
  36. // Modifies: All member vars.
  37. //
  38. // History: 02-11-94 DavidMun Created
  39. //
  40. //----------------------------------------------------------------------------
  41. CLog::CLog(
  42. const CHAR *szTestTitle,
  43. const CHAR *szLogFile,
  44. ULONG flDefaultDestinations,
  45. ULONG flLogInfoLevel)
  46. {
  47. // make copies of test title and log filename
  48. strncpy(_szTestTitle, szTestTitle, sizeof(_szTestTitle));
  49. _szTestTitle[sizeof(_szTestTitle) - 1] = '\0';
  50. strcpy(_szLogFile, szLogFile);
  51. // copy other args
  52. _flDefaultDestinations = flDefaultDestinations;
  53. _flInfoLevel = flLogInfoLevel;
  54. // Zero count of each type of message logged
  55. _cLogPass = 0;
  56. _cLogFail = 0;
  57. _cLogWarn = 0;
  58. _cLogStart = 0;
  59. _cLogInfo = 0;
  60. _cLogSkip = 0;
  61. _cLogAbort = 0;
  62. _cLogError = 0;
  63. _cLogOther = 0;
  64. //
  65. // Indicate that we haven't logged the header yet, and that logging of
  66. // header and footer is not suppressed.
  67. //
  68. _fLoggedHeader = FALSE;
  69. _fSuppress = FALSE;
  70. InitializeCriticalSection(&_critsec);
  71. }
  72. //+---------------------------------------------------------------------------
  73. //
  74. // Member: CLog::~CLog, public
  75. //
  76. // Synopsis: Log the footer before terminating
  77. //
  78. // History: 02-11-94 DavidMun Created
  79. //
  80. //----------------------------------------------------------------------------
  81. CLog::~CLog()
  82. {
  83. //
  84. // Make sure the footer is the last thing logged, unless its being
  85. // suppressed.
  86. //
  87. if (!_fSuppress)
  88. {
  89. _LogFooter();
  90. }
  91. }
  92. //+---------------------------------------------------------------------------
  93. //
  94. // Member: CLog::SetFile, public
  95. //
  96. // Synopsis: Set the filename to use when LOG_TOFILE is specified
  97. //
  98. // Arguments: [szNewFilename] - new file
  99. //
  100. // Modifies: [_szLogFile]
  101. //
  102. // History: 02-11-94 DavidMun Created
  103. //
  104. //----------------------------------------------------------------------------
  105. VOID CLog::SetFile(const CHAR *szNewFilename)
  106. {
  107. strcpy(_szLogFile, szNewFilename);
  108. }
  109. //+---------------------------------------------------------------------------
  110. //
  111. // Member: CLog::SetFile
  112. //
  113. // Synopsis: Wide char wrapper
  114. //
  115. // History: 04-06-95 DavidMun Created
  116. //
  117. //----------------------------------------------------------------------------
  118. VOID CLog::SetFile(const WCHAR *wszNewFilename)
  119. {
  120. CHAR szNewFilename[MAX_PATH + 1];
  121. wcstombs(szNewFilename, wszNewFilename, MAX_PATH);
  122. SetFile(szNewFilename);
  123. }
  124. //+---------------------------------------------------------------------------
  125. //
  126. // Member: CLog::SetInfoLevel, public
  127. //
  128. // Synopsis: Set infolevel mask bits
  129. //
  130. // Arguments: [flNewInfoLevel] - new mask
  131. //
  132. // Returns: Previous infolevel
  133. //
  134. // Modifies: [_flInfoLevel]
  135. //
  136. // History: 02-11-94 DavidMun Created
  137. //
  138. //----------------------------------------------------------------------------
  139. ULONG CLog::SetInfoLevel(ULONG flNewInfoLevel)
  140. {
  141. ULONG flOldInfoLevel = _flInfoLevel;
  142. _flInfoLevel = flNewInfoLevel & ~LOG_DESTINATIONBITS;
  143. return flOldInfoLevel;
  144. }
  145. //+---------------------------------------------------------------------------
  146. //
  147. // Member: CLog::_LogHeader, private
  148. //
  149. // Synopsis: Called the first time Write() is invoked, and never called
  150. // again.
  151. //
  152. // Modifies: [_fLoggedHeader]
  153. //
  154. // History: 02-11-94 DavidMun Created
  155. //
  156. //----------------------------------------------------------------------------
  157. VOID CLog::_LogHeader()
  158. {
  159. if (_fLoggedHeader)
  160. {
  161. return;
  162. }
  163. //
  164. // _fLoggedHeader MUST be set before calling Write to avoid infinite
  165. // recursion!
  166. //
  167. _fLoggedHeader = TRUE;
  168. //
  169. // Write the header.
  170. //
  171. Write(LOG_TEXT, "");
  172. Write(LOG_START, "Header");
  173. Write(LOG_TEXT, BANNER_WIDTH_EQUALS);
  174. Write(LOG_TEXT, " Run of '%s' starting at %s", _szTestTitle, GetTimeStamp());
  175. Write(LOG_TEXT, "");
  176. Write(LOG_TEXT, " Processors: %s", GetCpuStr());
  177. Write(LOG_TEXT, " Video: %s", GetVideoStr());
  178. Write(LOG_TEXT, "");
  179. Write(LOG_TEXT, BANNER_WIDTH_DASH);
  180. Write(LOG_END, "");
  181. }
  182. //+---------------------------------------------------------------------------
  183. //
  184. // Member: CLog::Write
  185. //
  186. // Synopsis: Write the printf style arguments to the destinations
  187. // specified in [flLevelAndDest] iff the bitwise and of
  188. // [_flInfoLevel] and [flLevelAndDest] != 0.
  189. //
  190. // Arguments: [flLevelAndDest] - LOG_TO* bits and at most 1 infolevel bit.
  191. // [szFormat] - printf style format
  192. // [...] - args for printf
  193. //
  194. // History: 02-11-94 DavidMun Created
  195. //
  196. //----------------------------------------------------------------------------
  197. VOID CLog::Write(ULONG flLevelAndDest, const CHAR *szFormat, ...)
  198. {
  199. EnterCriticalSection(&_critsec);
  200. if (!_fLoggedHeader && !_fSuppress)
  201. {
  202. _LogHeader();
  203. }
  204. static CHAR szLastStart[BANNER_WIDTH + 1];
  205. va_list varArgs;
  206. ULONG flDestinations;
  207. FILE *fp = NULL;
  208. CHAR szMessage[CCH_MAX_LOG_STRING]; // _vsnwprintf of args
  209. CHAR szToLog[CCH_MAX_LOG_STRING]; // prefix plus message
  210. CHAR szCurLine[BANNER_WIDTH + 1];
  211. CHAR *pszNextLine;
  212. ULONG cchPrefix;
  213. ULONG cchToLog;
  214. //
  215. // If the caller set any of the destination bits in flLevelAndDest, then
  216. // they override the default destinations in flLogDefaultDestinations.
  217. //
  218. // Return without logging anything if flLevelAndDest has no destination
  219. // bits set AND the default destination bits are also cleared. Also do
  220. // nothing if the intersection of infolevel bits in flLevelAndDest and
  221. // _flInfoLevel is nil.
  222. //
  223. flDestinations = flLevelAndDest & LOG_DESTINATIONBITS;
  224. if (0 == flDestinations)
  225. {
  226. flDestinations = _flDefaultDestinations;
  227. if (0 == flDestinations)
  228. {
  229. LeaveCriticalSection(&_critsec);
  230. return;
  231. }
  232. }
  233. if (0 == (flLevelAndDest & _flInfoLevel))
  234. {
  235. LeaveCriticalSection(&_critsec);
  236. return;
  237. }
  238. //
  239. // If we've reached this point then the message will be logged. sprintf
  240. // the args into szMessage.
  241. //
  242. va_start(varArgs, szFormat);
  243. _vsnprintf(szMessage, CCH_MAX_LOG_STRING, szFormat, varArgs);
  244. szMessage[CCH_MAX_LOG_STRING - 1] = '\0';
  245. va_end(varArgs);
  246. //
  247. // If we're starting a new section, close out the previous one if
  248. // necessary, then output a blank line to separate the new section from
  249. // the old one visually. Save the new section name. Note strncpy does
  250. // not guarantee null termination.
  251. //
  252. if (flLevelAndDest & LOG_START)
  253. {
  254. if (szLastStart[0] != '\0')
  255. {
  256. Write((flLevelAndDest & ~LOG_START) | LOG_END, "");
  257. }
  258. Write((flLevelAndDest & ~LOG_START) | LOG_TEXT, "");
  259. strncpy(szLastStart, szMessage, CCH_MAX_START_MESSAGE);
  260. szLastStart[CCH_MAX_START_MESSAGE - 1] = '\0';
  261. }
  262. if (flDestinations & LOG_TOFILE)
  263. {
  264. fp = fopen(_szLogFile, "a");
  265. }
  266. if (flLevelAndDest & LOG_TEXT)
  267. {
  268. //
  269. // LOG_TEXT strings are special: they are not wrapped, prefixed, or
  270. // counted. Logging them is therefore easy: just write szMessage.
  271. //
  272. if (fp)
  273. {
  274. fprintf(fp, "%s\n", szMessage);
  275. }
  276. if (flDestinations & LOG_TOCONSOLE)
  277. {
  278. printf("%s\n", szMessage);
  279. }
  280. if (flDestinations & LOG_TODEBUG)
  281. {
  282. OutputDebugStringA(szMessage);
  283. OutputDebugStringA("\n");
  284. }
  285. }
  286. else
  287. {
  288. //
  289. // Generate the prefix for this log entry, then fill cchPrefix with
  290. // its length. This will be the amount to indent portions of the line
  291. // that must be wrapped.
  292. //
  293. // If flLevelAndDest has LOG_START then szLastStart has just been set.
  294. // If it has LOG_END, then szLastStart has the message from the last
  295. // time a LOG_START was logged. In either case the message has
  296. // already been included in the prefix, so don't append it to szToLog.
  297. // Also, in the case of LOG_END, zero out the last start message,
  298. // since it's part of the prefix now, and the next LOG_START will
  299. // think this LOG_END wasn't logged if szLastStart isn't empty.
  300. //
  301. // Otherwise flLevelAndDest has neither LOG_START nor LOG_END, so the
  302. // string to log will be the prefix and the message.
  303. //
  304. _LogPrefix(flLevelAndDest, szLastStart, szToLog);
  305. cchPrefix = strlen(szToLog);
  306. if (0 == (flLevelAndDest & (LOG_START | LOG_END)))
  307. {
  308. strncat(szToLog, szMessage, CCH_MAX_LOG_STRING - cchPrefix);
  309. szToLog[CCH_MAX_LOG_STRING - 1] = '\0';
  310. }
  311. else if (flLevelAndDest & LOG_END)
  312. {
  313. szLastStart[0] = '\0';
  314. }
  315. //
  316. // szToLog contains the string to be logged. This will be output
  317. // BANNER_WIDTH characters at a time.
  318. //
  319. cchToLog = strlen(szToLog);
  320. pszNextLine = szToLog;
  321. do
  322. {
  323. //
  324. // Fill szCurLine with a BANNER_WIDTH chunk of szToLog, starting
  325. // at pszNextLine. If pszNextLine points to the start of szToLog,
  326. // then this is the first pass and no indent is necessary,
  327. // otherwise indent with spaces by the size of the prefix for this
  328. // log entry.
  329. //
  330. if (pszNextLine == szToLog)
  331. {
  332. strncpy(szCurLine, szToLog, BANNER_WIDTH);
  333. szCurLine[BANNER_WIDTH] = '\0';
  334. cchToLog -= min(cchToLog, BANNER_WIDTH);
  335. pszNextLine += BANNER_WIDTH;
  336. }
  337. else
  338. {
  339. sprintf(szCurLine,
  340. "%*s%.*s",
  341. cchPrefix,
  342. "",
  343. BANNER_WIDTH - cchPrefix,
  344. pszNextLine);
  345. szCurLine[BANNER_WIDTH] = '\0';
  346. cchToLog -= min(cchToLog, (ULONG) (BANNER_WIDTH - cchPrefix));
  347. pszNextLine += BANNER_WIDTH - cchPrefix;
  348. }
  349. if (fp)
  350. {
  351. fprintf(fp, "%s\n", szCurLine);
  352. }
  353. if (flDestinations & LOG_TOCONSOLE)
  354. {
  355. printf("%s\n", szCurLine);
  356. }
  357. if (flDestinations & LOG_TODEBUG)
  358. {
  359. OutputDebugStringA(szCurLine);
  360. OutputDebugStringA("\n");
  361. }
  362. } while (cchToLog);
  363. }
  364. if (fp)
  365. {
  366. fclose(fp);
  367. }
  368. LeaveCriticalSection(&_critsec);
  369. }
  370. //+---------------------------------------------------------------------------
  371. //
  372. // Member: CLog::_LogPrefix, private
  373. //
  374. // Synopsis: Fill [pszPrefix] with the prefix string corresponding to the
  375. // infolevel bit set in [flLevel].
  376. //
  377. // Arguments: [flLevel] - exactly one LOG_* infolevel bit
  378. // [szStart] - forms part of prefix for LOG_START and LOG_END
  379. // [pszPrefix] - output
  380. //
  381. // Modifies: [pszPrefix]
  382. //
  383. // History: 02-09-94 DavidMun Created
  384. //
  385. // Notes: Caller must ensure that pswzPrefix points to a buffer large
  386. // enough to hold START_PREFIX and szStart together.
  387. //
  388. // Neither TRACE nor TEXT levels are tallied.
  389. //
  390. //----------------------------------------------------------------------------
  391. VOID CLog::_LogPrefix(ULONG flLevel, const CHAR *szStart, CHAR *pszPrefix)
  392. {
  393. if (flLevel & LOG_PASS)
  394. {
  395. _cLogPass++;
  396. strcpy(pszPrefix, PASS_PREFIX);
  397. }
  398. else if (flLevel & LOG_FAIL)
  399. {
  400. _cLogFail++;
  401. strcpy(pszPrefix, FAIL_PREFIX);
  402. }
  403. else if (flLevel & LOG_WARN)
  404. {
  405. _cLogWarn++;
  406. strcpy(pszPrefix, WARN_PREFIX);
  407. }
  408. else if (flLevel & LOG_START)
  409. {
  410. _cLogStart++;
  411. sprintf(pszPrefix, START_PREFIX, szStart);
  412. }
  413. else if (flLevel & LOG_END)
  414. {
  415. sprintf(pszPrefix, END_PREFIX, szStart);
  416. }
  417. else if (flLevel & LOG_INFO)
  418. {
  419. _cLogInfo++;
  420. strcpy(pszPrefix, INFO_PREFIX);
  421. }
  422. else if (flLevel & LOG_SKIP)
  423. {
  424. _cLogSkip++;
  425. strcpy(pszPrefix, SKIP_PREFIX);
  426. }
  427. else if (flLevel & LOG_ABORT)
  428. {
  429. _cLogAbort++;
  430. strcpy(pszPrefix, ABORT_PREFIX);
  431. }
  432. else if (flLevel & LOG_ERROR)
  433. {
  434. _cLogError++;
  435. strcpy(pszPrefix, ERROR_PREFIX);
  436. }
  437. else if (flLevel & LOG_TRACE)
  438. {
  439. strcpy(pszPrefix, TRACE_PREFIX);
  440. }
  441. else if (flLevel & LOG_PERF)
  442. {
  443. strcpy(pszPrefix, PERF_PREFIX);
  444. }
  445. else if (flLevel & LOG_DEBUG)
  446. {
  447. strcpy(pszPrefix, DEBUG_PREFIX);
  448. }
  449. else if (flLevel & LOG_TEXT)
  450. {
  451. pszPrefix[0] = '\0';
  452. }
  453. else
  454. {
  455. _cLogOther++;
  456. pszPrefix[0] = '\0';
  457. }
  458. }
  459. //+---------------------------------------------------------------------------
  460. //
  461. // Member: CLog::_LogFooter, private
  462. //
  463. // Synopsis: Write a footer to the log.
  464. //
  465. // History: 02-11-94 DavidMun Created
  466. //
  467. // Notes: Called by dtor.
  468. //
  469. //----------------------------------------------------------------------------
  470. VOID CLog::_LogFooter()
  471. {
  472. Write(LOG_START, "Footer");
  473. Write(LOG_TEXT, BANNER_WIDTH_DASH);
  474. Write(LOG_TEXT, " Run of '%s' finished at %s", _szTestTitle, GetTimeStamp());
  475. Write(LOG_TEXT, "");
  476. Write(LOG_TEXT, " Total messages logged, by type:");
  477. Write(LOG_TEXT, "");
  478. if (_cLogStart)
  479. {
  480. Write(LOG_TEXT, " Start: %u", _cLogStart);
  481. }
  482. if (_cLogPass)
  483. {
  484. Write(LOG_TEXT, " Pass: %u", _cLogPass);
  485. }
  486. if (_cLogFail)
  487. {
  488. Write(LOG_TEXT, " Fail: %u", _cLogFail);
  489. }
  490. if (_cLogAbort)
  491. {
  492. Write(LOG_TEXT, " Abort: %u", _cLogAbort);
  493. }
  494. if (_cLogError)
  495. {
  496. Write(LOG_TEXT, " Error: %u", _cLogError);
  497. }
  498. if (_cLogSkip)
  499. {
  500. Write(LOG_TEXT, " Skip: %u", _cLogSkip);
  501. }
  502. if (_cLogWarn)
  503. {
  504. Write(LOG_TEXT, " Warning: %u", _cLogWarn);
  505. }
  506. if (_cLogInfo)
  507. {
  508. Write(LOG_TEXT, " Information: %u", _cLogInfo);
  509. }
  510. if (_cLogOther)
  511. {
  512. Write(LOG_TEXT, " User-defined: %u", _cLogOther);
  513. }
  514. Write(LOG_TEXT, "");
  515. Write(LOG_TEXT, BANNER_WIDTH_EQUALS);
  516. Write(LOG_END, "");
  517. }
  518. //+---------------------------------------------------------------------------
  519. //
  520. // Function: GetCpuStr, private
  521. //
  522. // Synopsis: Return a string describing CPU.
  523. //
  524. // Returns: Pointer to static string
  525. //
  526. // History: 02-11-94 DavidMun Created
  527. // 05-01-95 DavidMun Update for change to GetSystemInfo
  528. //
  529. //----------------------------------------------------------------------------
  530. static const CHAR *GetCpuStr()
  531. {
  532. static CHAR s_szCpuStr[BANNER_WIDTH];
  533. SYSTEM_INFO siSystemInfo;
  534. CHAR *pszCpuType;
  535. GetSystemInfo(&siSystemInfo);
  536. switch (siSystemInfo.wProcessorArchitecture)
  537. {
  538. case PROCESSOR_ARCHITECTURE_INTEL:
  539. pszCpuType = " Intel";
  540. break;
  541. case PROCESSOR_ARCHITECTURE_MIPS:
  542. pszCpuType = " MIPS";
  543. break;
  544. case PROCESSOR_ARCHITECTURE_ALPHA:
  545. pszCpuType = " ALPHA";
  546. break;
  547. default:
  548. pszCpuType = "Unknown Processor(s)";
  549. break;
  550. }
  551. sprintf(s_szCpuStr, "%u %s", siSystemInfo.dwNumberOfProcessors, pszCpuType);
  552. return s_szCpuStr;
  553. }
  554. //+---------------------------------------------------------------------------
  555. //
  556. // Function: GetVideoStr
  557. //
  558. // Synopsis: Return a pointer to a string describing video resolution and
  559. // color (i.e., XxYxC).
  560. //
  561. // Returns: Pointer to static string.
  562. //
  563. // History: 02-11-94 DavidMun Created
  564. //
  565. //----------------------------------------------------------------------------
  566. static const CHAR *GetVideoStr()
  567. {
  568. static CHAR s_szVideo[BANNER_WIDTH];
  569. HDC hdcDisplay;
  570. ULONG cPlanes;
  571. ULONG cBitsPerPixel;
  572. //
  573. // Get a DC for the display, then find out its horizontal and vertical
  574. // resolutions. Determine the number of colors per Petzold 3.1, p. 513.
  575. //
  576. hdcDisplay = CreateDC(TEXT("DISPLAY"), TEXT(""), TEXT(""), NULL);
  577. cPlanes = GetDeviceCaps(hdcDisplay, PLANES);
  578. cBitsPerPixel = GetDeviceCaps(hdcDisplay, BITSPIXEL);
  579. sprintf(s_szVideo,
  580. "%ux%ux%u",
  581. GetDeviceCaps(hdcDisplay, HORZRES),
  582. GetDeviceCaps(hdcDisplay, VERTRES),
  583. 1 << (cPlanes * cBitsPerPixel));
  584. DeleteDC(hdcDisplay);
  585. return s_szVideo;
  586. }
  587. //+---------------------------------------------------------------------------
  588. //
  589. // Function: GetTimeStamp
  590. //
  591. // Synopsis: Return a pointer to a string containing current time and date.
  592. //
  593. // Returns: Pointer to a static string.
  594. //
  595. // History: 02-11-94 DavidMun Created
  596. //
  597. //----------------------------------------------------------------------------
  598. static const CHAR *GetTimeStamp()
  599. {
  600. static CHAR s_szTimeStamp[20]; // space for time & date in format below
  601. SYSTEMTIME tmStart;
  602. GetLocalTime(&tmStart);
  603. sprintf(s_szTimeStamp,
  604. "%02d:%02d:%02d %d/%02d/%d",
  605. tmStart.wHour,
  606. tmStart.wMinute,
  607. tmStart.wSecond,
  608. tmStart.wMonth,
  609. tmStart.wDay,
  610. tmStart.wYear);
  611. return s_szTimeStamp;
  612. }
  613. //+---------------------------------------------------------------------------
  614. //
  615. // Function: LogIt
  616. //
  617. // Synopsis: Log success or failure.
  618. //
  619. // Arguments: [hrFound] - hresult returned from some operation
  620. // [hrExpected] - EXPECT_SUCCEEDED or a valid HRESULT
  621. // [szFormat] - printf style format string
  622. // [...] - args specified in [szFormat]
  623. //
  624. // History: 08-24-94 DavidMun Created
  625. //
  626. //----------------------------------------------------------------------------
  627. VOID LogIt(HRESULT hrFound, HRESULT hrExpected, CHAR *szFormat, ...)
  628. {
  629. va_list varg;
  630. va_start(varg, szFormat);
  631. if (hrExpected == EXPECT_SUCCEEDED && SUCCEEDED(hrFound) ||
  632. hrFound == hrExpected)
  633. {
  634. CHAR szBuf[MAX_LOGIT_MSG] = "Succeeded in ";
  635. CHAR *pBuf;
  636. pBuf = strchr(szBuf, '\0');
  637. _vsnprintf(pBuf, MAX_LOGIT_MSG - (pBuf - szBuf), szFormat, varg);
  638. g_Log.Write(LOG_TRACE, szBuf);
  639. }
  640. else
  641. {
  642. CHAR szBuf[MAX_LOGIT_MSG] = "Didn't succeed in ";
  643. CHAR *pBuf;
  644. pBuf = strchr(szBuf, '\0');
  645. _vsnprintf(pBuf, MAX_LOGIT_MSG - (pBuf - szBuf), szFormat, varg);
  646. g_Log.Write(LOG_FAIL, szBuf);
  647. }
  648. va_end( varg );
  649. }
  650. #if 0
  651. void __cdecl main()
  652. {
  653. CLog Log("Unit Test", "test.log", LOG_TOCONSOLE | LOG_TOFILE);
  654. Log.Write(LOG_START, "variation");
  655. Log.Write(LOG_INFO, "Here is some info: %d %s", 1, "foo");
  656. Log.Write(LOG_WARN, "a wide char warning '%S'", L"wide string");
  657. Log.Write(LOG_TRACE, "line to trace");
  658. Log.Write(LOG_ABORT, "Abort message");
  659. }
  660. #endif