main
cpp 864 lines 22.7 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5
6 // Exit macros
7 #define LoguExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
8 #define LoguExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
9 #define LoguExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
10 #define LoguExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
11 #define LoguExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
12 #define LoguExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_LOGUTIL, x, s, __VA_ARGS__)
13 #define LoguExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_LOGUTIL, p, x, e, s, __VA_ARGS__)
14 #define LoguExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_LOGUTIL, p, x, s, __VA_ARGS__)
15 #define LoguExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_LOGUTIL, p, x, e, s, __VA_ARGS__)
16 #define LoguExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_LOGUTIL, p, x, s, __VA_ARGS__)
17 #define LoguExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_LOGUTIL, e, x, s, __VA_ARGS__)
18 #define LoguExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_LOGUTIL, g, x, s, __VA_ARGS__)
19
20 // globals
21 static HMODULE LogUtil_hModule = NULL;
22 static BOOL LogUtil_fDisabled = FALSE;
23 static HANDLE LogUtil_hLog = INVALID_HANDLE_VALUE;
24 static LPWSTR LogUtil_sczLogPath = NULL;
25 static LPSTR LogUtil_sczPreInitBuffer = NULL;
26 static REPORT_LEVEL LogUtil_rlCurrent = REPORT_STANDARD;
27 static CRITICAL_SECTION LogUtil_csLog = { };
28 static BOOL LogUtil_fInitializedCriticalSection = FALSE;
29
30 // Customization of certain parts of the string, within a line
31 static LPWSTR LogUtil_sczSpecialBeginLine = NULL;
32 static LPWSTR LogUtil_sczSpecialEndLine = NULL;
33 static LPWSTR LogUtil_sczSpecialAfterTimeStamp = NULL;
34
35 static LPCSTR LOGUTIL_UNKNOWN = "unknown";
36 static LPCSTR LOGUTIL_WARNING = "warning";
37 static LPCSTR LOGUTIL_STANDARD = "standard";
38 static LPCSTR LOGUTIL_VERBOSE = "verbose";
39 static LPCSTR LOGUTIL_DEBUG = "debug";
40 static LPCSTR LOGUTIL_NONE = "none";
41
42 // prototypes
43 static HRESULT LogStringWorkRawUnsynchronized(
44 __in_z LPCSTR szLogData
45 );
46 static HRESULT LogIdWork(
47 __in REPORT_LEVEL rl,
48 __in_opt HMODULE hModule,
49 __in DWORD dwLogId,
50 __in va_list args,
51 __in BOOL fLOGUTIL_NEWLINE
52 );
53 static HRESULT LogStringWorkArgs(
54 __in REPORT_LEVEL rl,
55 __in_z __format_string LPCSTR szFormat,
56 __in va_list args,
57 __in BOOL fLOGUTIL_NEWLINE
58 );
59 static HRESULT LogStringWork(
60 __in REPORT_LEVEL rl,
61 __in DWORD dwLogId,
62 __in_z LPCWSTR sczString,
63 __in BOOL fLOGUTIL_NEWLINE
64 );
65
66 // Hook to allow redirecting LogStringWorkRaw function calls
67 static PFN_LOGSTRINGWORKRAW s_vpfLogStringWorkRaw = NULL;
68 static LPVOID s_vpvLogStringWorkRawContext = NULL;
69
70
71 extern "C" BOOL DAPI IsLogInitialized()
72 {
73 return LogUtil_fInitializedCriticalSection;
74 }
75
76 extern "C" BOOL DAPI IsLogOpen()
77 {
78 return (INVALID_HANDLE_VALUE != LogUtil_hLog && NULL != LogUtil_sczLogPath);
79 }
80
81
82 extern "C" void DAPI LogInitialize(
83 __in_opt HMODULE hModule
84 )
85 {
86 AssertSz(INVALID_HANDLE_VALUE == LogUtil_hLog && !LogUtil_sczLogPath, "LogInitialize() or LogOpen() - already called.");
87
88 LogUtil_hModule = hModule;
89 LogUtil_fDisabled = FALSE;
90
91 ::InitializeCriticalSection(&LogUtil_csLog);
92 LogUtil_fInitializedCriticalSection = TRUE;
93 }
94
95
96 extern "C" HRESULT DAPI LogOpen(
97 __in_z_opt LPCWSTR wzDirectory,
98 __in_z LPCWSTR wzLog,
99 __in_z_opt LPCWSTR wzPostfix,
100 __in_z_opt LPCWSTR wzExt,
101 __in BOOL fAppend,
102 __in BOOL fHeader,
103 __out_z_opt LPWSTR* psczLogPath
104 )
105 {
106 HRESULT hr = S_OK;
107 BOOL fEnteredCriticalSection = FALSE;
108 LPWSTR sczCombined = NULL;
109 LPWSTR sczLogDirectory = NULL;
110
111 ::EnterCriticalSection(&LogUtil_csLog);
112 fEnteredCriticalSection = TRUE;
113
114 if (wzExt && *wzExt)
115 {
116 hr = PathCreateTimeBasedTempFile(wzDirectory, wzLog, wzPostfix, wzExt, &LogUtil_sczLogPath, &LogUtil_hLog);
117 LoguExitOnFailure(hr, "Failed to create log based on current system time.");
118 }
119 else
120 {
121 hr = PathConcat(wzDirectory, wzLog, &sczCombined);
122 LoguExitOnFailure(hr, "Failed to combine the log path.");
123
124 if (!PathIsFullyQualified(sczCombined))
125 {
126 hr = PathExpand(&LogUtil_sczLogPath, sczCombined, PATH_EXPAND_FULLPATH);
127 LoguExitOnFailure(hr, "Failed to expand the log path.");
128 }
129 else
130 {
131 LogUtil_sczLogPath = sczCombined;
132 sczCombined = NULL;
133 }
134
135 hr = PathGetDirectory(LogUtil_sczLogPath, &sczLogDirectory);
136 LoguExitOnFailure(hr, "Failed to get log directory.");
137
138 hr = DirEnsureExists(sczLogDirectory, NULL);
139 LoguExitOnFailure(hr, "Failed to ensure log file directory exists: %ls", sczLogDirectory);
140
141 LogUtil_hLog = ::CreateFileW(LogUtil_sczLogPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, (fAppend) ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
142 if (INVALID_HANDLE_VALUE == LogUtil_hLog)
143 {
144 LoguExitOnLastError(hr, "failed to create log file: %ls", LogUtil_sczLogPath);
145 }
146
147 if (fAppend)
148 {
149 ::SetFilePointer(LogUtil_hLog, 0, 0, FILE_END);
150 }
151 }
152
153 LogUtil_fDisabled = FALSE;
154
155 if (fHeader)
156 {
157 LogHeader();
158 }
159
160 if (NULL != LogUtil_sczPreInitBuffer)
161 {
162 // Log anything that was logged before LogOpen() was called.
163 LogStringWorkRaw(LogUtil_sczPreInitBuffer);
164 ReleaseNullStr(LogUtil_sczPreInitBuffer);
165 }
166
167 if (psczLogPath)
168 {
169 hr = StrAllocString(psczLogPath, LogUtil_sczLogPath, 0);
170 LoguExitOnFailure(hr, "Failed to copy log path.");
171 }
172
173 LExit:
174 if (fEnteredCriticalSection)
175 {
176 ::LeaveCriticalSection(&LogUtil_csLog);
177 }
178
179 ReleaseStr(sczCombined);
180 ReleaseStr(sczLogDirectory);
181
182 return hr;
183 }
184
185
186 void DAPI LogDisable()
187 {
188 ::EnterCriticalSection(&LogUtil_csLog);
189
190 LogUtil_fDisabled = TRUE;
191
192 ReleaseFileHandle(LogUtil_hLog);
193 ReleaseNullStr(LogUtil_sczLogPath);
194 ReleaseNullStr(LogUtil_sczPreInitBuffer);
195
196 ::LeaveCriticalSection(&LogUtil_csLog);
197 }
198
199
200 void DAPI LogRedirect(
201 __in_opt PFN_LOGSTRINGWORKRAW vpfLogStringWorkRaw,
202 __in_opt LPVOID pvContext
203 )
204 {
205 ::EnterCriticalSection(&LogUtil_csLog);
206
207 s_vpfLogStringWorkRaw = vpfLogStringWorkRaw;
208 s_vpvLogStringWorkRawContext = pvContext;
209
210 ::LeaveCriticalSection(&LogUtil_csLog);
211 }
212
213
214 HRESULT DAPI LogRename(
215 __in_z LPCWSTR wzNewPath
216 )
217 {
218 HRESULT hr = S_OK;
219 BOOL fEnteredCriticalSection = FALSE;
220
221 ::EnterCriticalSection(&LogUtil_csLog);
222 fEnteredCriticalSection = TRUE;
223
224 ReleaseFileHandle(LogUtil_hLog);
225
226 hr = FileEnsureMove(LogUtil_sczLogPath, wzNewPath, TRUE, TRUE);
227 LoguExitOnFailure(hr, "Failed to move logfile to new location: %ls", wzNewPath);
228
229 hr = StrAllocString(&LogUtil_sczLogPath, wzNewPath, 0);
230 LoguExitOnFailure(hr, "Failed to store new logfile path: %ls", wzNewPath);
231
232 LogUtil_hLog = ::CreateFileW(LogUtil_sczLogPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
233 if (INVALID_HANDLE_VALUE == LogUtil_hLog)
234 {
235 LoguExitOnLastError(hr, "failed to create log file: %ls", LogUtil_sczLogPath);
236 }
237
238 // Enable "append" mode by moving file pointer to the end
239 ::SetFilePointer(LogUtil_hLog, 0, 0, FILE_END);
240
241 LExit:
242 if (fEnteredCriticalSection)
243 {
244 ::LeaveCriticalSection(&LogUtil_csLog);
245 }
246
247 return hr;
248 }
249
250
251 extern "C" HRESULT DAPI LogFlush()
252 {
253 HRESULT hr = S_OK;
254
255 ::EnterCriticalSection(&LogUtil_csLog);
256
257 if (INVALID_HANDLE_VALUE == LogUtil_hLog)
258 {
259 ExitFunction1(hr = S_FALSE);
260 }
261
262 if (!::FlushFileBuffers(LogUtil_hLog))
263 {
264 LoguExitWithLastError(hr, "Failed to flush log file buffers.");
265 }
266
267 LExit:
268 ::LeaveCriticalSection(&LogUtil_csLog);
269
270 return hr;
271 }
272
273
274 extern "C" void DAPI LogClose(
275 __in BOOL fFooter
276 )
277 {
278 if (INVALID_HANDLE_VALUE != LogUtil_hLog && fFooter)
279 {
280 LogFooter();
281 }
282
283 ReleaseFileHandle(LogUtil_hLog);
284 ReleaseNullStr(LogUtil_sczLogPath);
285 ReleaseNullStr(LogUtil_sczPreInitBuffer);
286 }
287
288
289 extern "C" void DAPI LogUninitialize(
290 __in BOOL fFooter
291 )
292 {
293 LogClose(fFooter);
294
295 if (LogUtil_fInitializedCriticalSection)
296 {
297 ::DeleteCriticalSection(&LogUtil_csLog);
298 LogUtil_fInitializedCriticalSection = FALSE;
299 }
300
301 LogUtil_hModule = NULL;
302 LogUtil_fDisabled = FALSE;
303
304 ReleaseNullStr(LogUtil_sczSpecialBeginLine);
305 ReleaseNullStr(LogUtil_sczSpecialAfterTimeStamp);
306 ReleaseNullStr(LogUtil_sczSpecialEndLine);
307 }
308
309
310 extern "C" BOOL DAPI LogIsOpen()
311 {
312 return INVALID_HANDLE_VALUE != LogUtil_hLog;
313 }
314
315
316 HRESULT DAPI LogSetSpecialParams(
317 __in_z_opt LPCWSTR wzSpecialBeginLine,
318 __in_z_opt LPCWSTR wzSpecialAfterTimeStamp,
319 __in_z_opt LPCWSTR wzSpecialEndLine
320 )
321 {
322 HRESULT hr = S_OK;
323
324 // Handle special string to be prepended before every full line
325 if (NULL == wzSpecialBeginLine)
326 {
327 ReleaseNullStr(LogUtil_sczSpecialBeginLine);
328 }
329 else
330 {
331 hr = StrAllocConcat(&LogUtil_sczSpecialBeginLine, wzSpecialBeginLine, 0);
332 LoguExitOnFailure(hr, "Failed to allocate copy of special beginline string");
333 }
334
335 // Handle special string to be appended to every time stamp
336 if (NULL == wzSpecialAfterTimeStamp)
337 {
338 ReleaseNullStr(LogUtil_sczSpecialAfterTimeStamp);
339 }
340 else
341 {
342 hr = StrAllocConcat(&LogUtil_sczSpecialAfterTimeStamp, wzSpecialAfterTimeStamp, 0);
343 LoguExitOnFailure(hr, "Failed to allocate copy of special post-timestamp string");
344 }
345
346 // Handle special string to be appended before every full line
347 if (NULL == wzSpecialEndLine)
348 {
349 ReleaseNullStr(LogUtil_sczSpecialEndLine);
350 }
351 else
352 {
353 hr = StrAllocConcat(&LogUtil_sczSpecialEndLine, wzSpecialEndLine, 0);
354 LoguExitOnFailure(hr, "Failed to allocate copy of special endline string");
355 }
356
357 LExit:
358 return hr;
359 }
360
361 extern "C" REPORT_LEVEL DAPI LogSetLevel(
362 __in REPORT_LEVEL rl,
363 __in BOOL fLogChange
364 )
365 {
366 AssertSz(REPORT_ERROR != rl, "REPORT_ERROR is not a valid logging level to set");
367
368 REPORT_LEVEL rlPrev = LogUtil_rlCurrent;
369
370 if (LogUtil_rlCurrent != rl)
371 {
372 LogUtil_rlCurrent = rl;
373
374 if (fLogChange)
375 {
376 LPCSTR szLevel = LOGUTIL_UNKNOWN;
377 switch (LogUtil_rlCurrent)
378 {
379 case REPORT_WARNING:
380 szLevel = LOGUTIL_WARNING;
381 break;
382 case REPORT_STANDARD:
383 szLevel = LOGUTIL_STANDARD;
384 break;
385 case REPORT_VERBOSE:
386 szLevel = LOGUTIL_VERBOSE;
387 break;
388 case REPORT_DEBUG:
389 szLevel = LOGUTIL_DEBUG;
390 break;
391 case REPORT_NONE:
392 szLevel = LOGUTIL_NONE;
393 break;
394 }
395
396 LogStringLine(REPORT_STANDARD, "--- logging level: %hs ---", szLevel);
397 }
398 }
399
400 return rlPrev;
401 }
402
403
404 extern "C" REPORT_LEVEL DAPI LogGetLevel()
405 {
406 return LogUtil_rlCurrent;
407 }
408
409
410 extern "C" HRESULT DAPI LogGetPath(
411 __out_ecount_z(cchLogPath) LPWSTR pwzLogPath,
412 __in DWORD cchLogPath
413 )
414 {
415 Assert(pwzLogPath);
416
417 HRESULT hr = S_OK;
418
419 if (NULL == LogUtil_sczLogPath) // they can't have a path if there isn't one!
420 {
421 ExitFunction1(hr = E_UNEXPECTED);
422 }
423
424 hr = ::StringCchCopyW(pwzLogPath, cchLogPath, LogUtil_sczLogPath);
425
426 LExit:
427 return hr;
428 }
429
430
431 extern "C" HANDLE DAPI LogGetHandle()
432 {
433 return LogUtil_hLog;
434 }
435
436
437 extern "C" HRESULT DAPI LogStringArgs(
438 __in REPORT_LEVEL rl,
439 __in_z __format_string LPCSTR szFormat,
440 __in va_list args
441 )
442 {
443 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid logging level");
444 HRESULT hr = S_OK;
445
446 if (REPORT_ERROR != rl && LogUtil_rlCurrent < rl)
447 {
448 ExitFunction1(hr = S_FALSE);
449 }
450
451 hr = LogStringWorkArgs(rl, szFormat, args, FALSE);
452
453 LExit:
454 return hr;
455 }
456
457 extern "C" HRESULT DAPI LogStringLineArgs(
458 __in REPORT_LEVEL rl,
459 __in_z __format_string LPCSTR szFormat,
460 __in va_list args
461 )
462 {
463 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid logging level");
464 HRESULT hr = S_OK;
465
466 if (REPORT_ERROR != rl && LogUtil_rlCurrent < rl)
467 {
468 ExitFunction1(hr = S_FALSE);
469 }
470
471 hr = LogStringWorkArgs(rl, szFormat, args, TRUE);
472
473 LExit:
474 return hr;
475 }
476
477
478 extern "C" HRESULT DAPI LogIdModuleArgs(
479 __in REPORT_LEVEL rl,
480 __in DWORD dwLogId,
481 __in_opt HMODULE hModule,
482 __in va_list args
483 )
484 {
485 AssertSz(REPORT_NONE != rl, "REPORT_NONE is not a valid logging level");
486 HRESULT hr = S_OK;
487
488 if (REPORT_ERROR != rl && LogUtil_rlCurrent < rl)
489 {
490 ExitFunction1(hr = S_FALSE);
491 }
492
493 hr = LogIdWork(rl, (hModule) ? hModule : LogUtil_hModule, dwLogId, args, TRUE);
494
495 LExit:
496 return hr;
497 }
498
499
500 extern "C" HRESULT DAPI LogErrorStringArgs(
501 __in HRESULT hrError,
502 __in_z __format_string LPCSTR szFormat,
503 __in va_list args
504 )
505 {
506 HRESULT hr = S_OK;
507 LPWSTR sczFormat = NULL;
508 LPWSTR sczMessage = NULL;
509
510 hr = StrAllocStringAnsi(&sczFormat, szFormat, 0, CP_ACP);
511 LoguExitOnFailure(hr, "Failed to convert format string to wide character string");
512
513 // format the string as a unicode string - this is necessary to be able to include
514 // international characters in our output string. This does have the counterintuitive effect
515 // that the caller's "%s" is interpreted differently
516 // (so callers should use %hs for LPSTR and %ls for LPWSTR)
517 hr = StrAllocFormattedArgs(&sczMessage, sczFormat, args);
518 LoguExitOnFailure(hr, "Failed to format error message: \"%ls\"", sczFormat);
519
520 hr = LogStringLine(REPORT_ERROR, "Error 0x%x: %ls", hrError, sczMessage);
521
522 LExit:
523 ReleaseStr(sczFormat);
524 ReleaseStr(sczMessage);
525
526 return hr;
527 }
528
529
530 extern "C" HRESULT DAPI LogErrorIdModule(
531 __in HRESULT hrError,
532 __in DWORD dwLogId,
533 __in_opt HMODULE hModule,
534 __in_z_opt LPCWSTR wzString1 = NULL,
535 __in_z_opt LPCWSTR wzString2 = NULL,
536 __in_z_opt LPCWSTR wzString3 = NULL
537 )
538 {
539 HRESULT hr = S_OK;
540 WCHAR wzError[11];
541 WORD cStrings = 1; // guaranteed wzError is in the list
542
543 hr = ::StringCchPrintfW(wzError, countof(wzError), L"0x%08x", hrError);
544 LoguExitOnFailure(hr, "failed to format error code: \"0%08x\"", hrError);
545
546 cStrings += wzString1 ? 1 : 0;
547 cStrings += wzString2 ? 1 : 0;
548 cStrings += wzString3 ? 1 : 0;
549
550 hr = LogIdModule(REPORT_ERROR, dwLogId, hModule, wzError, wzString1, wzString2, wzString3);
551 LoguExitOnFailure(hr, "Failed to log id module.");
552
553 LExit:
554 return hr;
555 }
556
557 extern "C" HRESULT DAPI LogHeader()
558 {
559 HRESULT hr = S_OK;
560 WCHAR wzComputerName[MAX_COMPUTERNAME_LENGTH + 1] = { };
561 DWORD cchComputerName = countof(wzComputerName);
562 LPWSTR sczPath = NULL;
563 LPCWSTR wzPath = NULL;
564 DWORD dwMajorVersion = 0;
565 DWORD dwMinorVersion = 0;
566 LPCSTR szLevel = LOGUTIL_UNKNOWN;
567 LPWSTR sczCurrentDateTime = NULL;
568
569 //
570 // get the interesting data
571 //
572
573 hr = PathForCurrentProcess(&sczPath, NULL);
574 if (FAILED(hr))
575 {
576 wzPath = L"";
577 }
578 else
579 {
580 wzPath = sczPath;
581
582 hr = FileVersion(wzPath, &dwMajorVersion, &dwMinorVersion);
583 }
584
585 if (FAILED(hr))
586 {
587 dwMajorVersion = 0;
588 dwMinorVersion = 0;
589 }
590
591 if (!::GetComputerNameW(wzComputerName, &cchComputerName))
592 {
593 ::SecureZeroMemory(wzComputerName, sizeof(wzComputerName));
594 }
595
596 TimeCurrentDateTime(&sczCurrentDateTime, FALSE);
597
598 //
599 // write data to the log
600 //
601 LogStringLine(REPORT_STANDARD, "=== Logging started: %ls ===", sczCurrentDateTime);
602 LogStringLine(REPORT_STANDARD, "Executable: %ls v%d.%d.%d.%d", wzPath, dwMajorVersion >> 16, dwMajorVersion & 0xFFFF, dwMinorVersion >> 16, dwMinorVersion & 0xFFFF);
603 LogStringLine(REPORT_STANDARD, "Computer : %ls", wzComputerName);
604 switch (LogUtil_rlCurrent)
605 {
606 case REPORT_WARNING:
607 szLevel = LOGUTIL_WARNING;
608 break;
609 case REPORT_STANDARD:
610 szLevel = LOGUTIL_STANDARD;
611 break;
612 case REPORT_VERBOSE:
613 szLevel = LOGUTIL_VERBOSE;
614 break;
615 case REPORT_DEBUG:
616 szLevel = LOGUTIL_DEBUG;
617 break;
618 case REPORT_NONE:
619 szLevel = LOGUTIL_NONE;
620 break;
621 }
622 LogStringLine(REPORT_STANDARD, "--- logging level: %hs ---", szLevel);
623
624 hr = S_OK;
625
626 ReleaseStr(sczCurrentDateTime);
627 ReleaseStr(sczPath);
628
629 return hr;
630 }
631
632
633
634 static HRESULT LogFooterWork(
635 __in_z __format_string LPCSTR szFormat,
636 ...
637 )
638 {
639 HRESULT hr = S_OK;
640
641 va_list args;
642 va_start(args, szFormat);
643 hr = LogStringWorkArgs(REPORT_STANDARD, szFormat, args, TRUE);
644 va_end(args);
645
646 return hr;
647 }
648
649 extern "C" HRESULT DAPI LogFooter()
650 {
651 HRESULT hr = S_OK;
652 LPWSTR sczCurrentDateTime = NULL;
653 TimeCurrentDateTime(&sczCurrentDateTime, FALSE);
654 hr = LogFooterWork("=== Logging stopped: %ls ===", sczCurrentDateTime);
655 ReleaseStr(sczCurrentDateTime);
656 return hr;
657 }
658
659 extern "C" HRESULT DAPI LogStringWorkRaw(
660 __in_z LPCSTR szLogData
661 )
662 {
663 HRESULT hr = S_OK;
664
665 ::EnterCriticalSection(&LogUtil_csLog);
666
667 hr = LogStringWorkRawUnsynchronized(szLogData);
668
669 ::LeaveCriticalSection(&LogUtil_csLog);
670
671 return hr;
672 }
673
674 //
675 // private worker functions
676 //
677
678 static HRESULT LogStringWorkRawUnsynchronized(
679 __in_z LPCSTR szLogData
680 )
681 {
682 Assert(szLogData && *szLogData);
683
684 HRESULT hr = S_OK;
685 size_t cchLogData = 0;
686 DWORD cbLogData = 0;
687 DWORD cbTotal = 0;
688 DWORD cbWrote = 0;
689
690 hr = ::StringCchLengthA(szLogData, STRSAFE_MAX_CCH, &cchLogData);
691 LoguExitOnRootFailure(hr, "Failed to get length of raw string");
692
693 cbLogData = (DWORD)cchLogData;
694
695 // If the log hasn't been initialized yet, store it in a buffer
696 if (INVALID_HANDLE_VALUE == LogUtil_hLog)
697 {
698 hr = StrAnsiAllocConcat(&LogUtil_sczPreInitBuffer, szLogData, 0);
699 LoguExitOnFailure(hr, "Failed to concatenate string to pre-init buffer");
700
701 ExitFunction1(hr = S_OK);
702 }
703
704 // write the string
705 while (cbTotal < cbLogData)
706 {
707 if (!::WriteFile(LogUtil_hLog, reinterpret_cast<const BYTE*>(szLogData) + cbTotal, cbLogData - cbTotal, &cbWrote, NULL))
708 {
709 LoguExitOnLastError(hr, "Failed to write output to log: %ls - %hs", LogUtil_sczLogPath, szLogData);
710 }
711
712 cbTotal += cbWrote;
713 }
714
715 LExit:
716 return hr;
717 }
718
719 static HRESULT LogIdWork(
720 __in REPORT_LEVEL rl,
721 __in_opt HMODULE hModule,
722 __in DWORD dwLogId,
723 __in va_list args,
724 __in BOOL fLOGUTIL_NEWLINE
725 )
726 {
727 HRESULT hr = S_OK;
728 LPWSTR pwz = NULL;
729 DWORD cch = 0;
730
731 // get the string for the id
732 #pragma prefast(push)
733 #pragma prefast(disable:25028)
734 #pragma prefast(disable:25068)
735 cch = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE,
736 static_cast<LPCVOID>(hModule), dwLogId, 0, reinterpret_cast<LPWSTR>(&pwz), 0, &args);
737 #pragma prefast(pop)
738
739 if (0 == cch)
740 {
741 LoguExitOnLastError(hr, "failed to log id: %d", dwLogId);
742 }
743
744 if (2 <= cch && L'\r' == pwz[cch-2] && L'\n' == pwz[cch-1])
745 {
746 pwz[cch-2] = L'\0'; // remove newline from message table
747 }
748
749 LogStringWork(rl, dwLogId, pwz, fLOGUTIL_NEWLINE);
750
751 LExit:
752 if (pwz)
753 {
754 ::LocalFree(pwz);
755 }
756
757 return hr;
758 }
759
760
761 static HRESULT LogStringWorkArgs(
762 __in REPORT_LEVEL rl,
763 __in_z __format_string LPCSTR szFormat,
764 __in va_list args,
765 __in BOOL fLOGUTIL_NEWLINE
766 )
767 {
768 Assert(szFormat && *szFormat);
769
770 HRESULT hr = S_OK;
771 LPWSTR sczFormat = NULL;
772 LPWSTR sczMessage = NULL;
773
774 hr = StrAllocStringAnsi(&sczFormat, szFormat, 0, CP_ACP);
775 LoguExitOnFailure(hr, "Failed to convert format string to wide character string");
776
777 // format the string as a unicode string
778 hr = StrAllocFormattedArgs(&sczMessage, sczFormat, args);
779 LoguExitOnFailure(hr, "Failed to format message: \"%ls\"", sczFormat);
780
781 hr = LogStringWork(rl, 0, sczMessage, fLOGUTIL_NEWLINE);
782 LoguExitOnFailure(hr, "Failed to write formatted string to log:%ls", sczMessage);
783
784 LExit:
785 ReleaseStr(sczFormat);
786 ReleaseStr(sczMessage);
787
788 return hr;
789 }
790
791
792 static HRESULT LogStringWork(
793 __in REPORT_LEVEL rl,
794 __in DWORD dwLogId,
795 __in_z LPCWSTR sczString,
796 __in BOOL fLOGUTIL_NEWLINE
797 )
798 {
799 Assert(sczString && *sczString);
800
801 HRESULT hr = S_OK;
802 BOOL fEnteredCriticalSection = FALSE;
803 LPWSTR scz = NULL;
804 LPCWSTR wzLogData = NULL;
805 LPSTR sczMultiByte = NULL;
806
807 // If logging is disabled, just bail.
808 if (LogUtil_fDisabled)
809 {
810 ExitFunction();
811 }
812
813 ::EnterCriticalSection(&LogUtil_csLog);
814 fEnteredCriticalSection = TRUE;
815
816 if (fLOGUTIL_NEWLINE)
817 {
818 // get the process and thread id.
819 DWORD dwProcessId = ::GetCurrentProcessId();
820 DWORD dwThreadId = ::GetCurrentThreadId();
821
822 // get the time relative to GMT.
823 SYSTEMTIME st = { };
824 ::GetLocalTime(&st);
825
826 DWORD dwId = dwLogId & 0xFFFFFFF;
827 DWORD dwType = dwLogId & 0xF0000000;
828 LPSTR szType = (0xE0000000 == dwType || REPORT_ERROR == rl) ? "e" : (0xA0000000 == dwType || REPORT_WARNING == rl) ? "w" : "i";
829
830 // add line prefix and trailing newline
831 hr = StrAllocFormatted(&scz, L"%ls[%04X:%04X][%04hu-%02hu-%02huT%02hu:%02hu:%02hu]%hs%03d:%ls %ls%ls", LogUtil_sczSpecialBeginLine ? LogUtil_sczSpecialBeginLine : L"",
832 dwProcessId, dwThreadId, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, szType, dwId,
833 LogUtil_sczSpecialAfterTimeStamp ? LogUtil_sczSpecialAfterTimeStamp : L"", sczString, LogUtil_sczSpecialEndLine ? LogUtil_sczSpecialEndLine : L"\r\n");
834 LoguExitOnFailure(hr, "Failed to format line prefix.");
835 }
836
837 wzLogData = scz ? scz : sczString;
838
839 // Convert to UTF-8 before writing out to the log file
840 hr = StrAnsiAllocString(&sczMultiByte, wzLogData, 0, CP_UTF8);
841 LoguExitOnFailure(hr, "Failed to convert log string to UTF-8");
842
843 if (s_vpfLogStringWorkRaw)
844 {
845 hr = s_vpfLogStringWorkRaw(sczMultiByte, s_vpvLogStringWorkRawContext);
846 LoguExitOnFailure(hr, "Failed to write string to log using redirected function: %ls", sczString);
847 }
848 else
849 {
850 hr = LogStringWorkRaw(sczMultiByte);
851 LoguExitOnFailure(hr, "Failed to write string to log using default function: %ls", sczString);
852 }
853
854 LExit:
855 if (fEnteredCriticalSection)
856 {
857 ::LeaveCriticalSection(&LogUtil_csLog);
858 }
859
860 ReleaseStr(scz);
861 ReleaseStr(sczMultiByte);
862
863 return hr;
864 }