summaryrefslogtreecommitdiff
path: root/src/ToolBox/superpmi/superpmi-shared/logging.cpp
blob: 69c321bb39a4b3f89ffc48c5630c080d1abe0ae9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

//----------------------------------------------------------
// Logging.cpp - Common logging and console output infrastructure
//----------------------------------------------------------

#include "standardpch.h"
#include "logging.h"
#include "errorhandling.h"
#include <time.h>

//
// NOTE: Since the logging system is at the core of the error handling infrastructure, any errors
// that occur while logging will print a message to the console. Fatal errors trigger a debugbreak.
//

bool Logger::s_initialized = false;
UINT32 Logger::s_logLevel = LOGMASK_DEFAULT;
HANDLE Logger::s_logFile = INVALID_HANDLE_VALUE;
char *Logger::s_logFilePath = nullptr;
CRITICAL_SECTION Logger::s_critSec;

//
// Initializes the logging subsystem. This must be called before invoking any of the logging functionality.
//
/* static */
void Logger::Initialize()
{
    if (!s_initialized)
    {
        InitializeCriticalSection(&s_critSec);
        s_initialized = true;
    }
}

//
// Shuts down the logging subsystem, freeing resources, closing handles, and such.
//
/* static */
void Logger::Shutdown()
{
    if (s_initialized)
    {
        DeleteCriticalSection(&s_critSec);
        CloseLogFile();
        s_initialized = false;
    }
}

//
// Opens a log file at the given path and enables file-based logging, if the given path is valid.
//
/* static */
void Logger::OpenLogFile(char *logFilePath)
{
    if (s_logFile == INVALID_HANDLE_VALUE && logFilePath != nullptr)
    {
        s_logFile = CreateFileA(logFilePath,
                                GENERIC_WRITE,
                                FILE_SHARE_READ | FILE_SHARE_DELETE,
                                NULL,
                                CREATE_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                                NULL);

        if (s_logFile != INVALID_HANDLE_VALUE)
        {
            // We may need the file path later in order to delete the log file
            s_logFilePath = _strdup(logFilePath);
        }
        else
        {
            fprintf(stderr, "WARNING: [Logger::OpenLogFile] Failed to open log file '%s'. GetLastError()=%u\n", logFilePath, GetLastError());
        }
    }
}

//
// Closes the currently open log file, if one is open.
//
/* static */
void Logger::CloseLogFile()
{
    if (s_logFile != INVALID_HANDLE_VALUE)
    {
        // Avoid polluting the file system with empty log files
        if (GetFileSize(s_logFile, nullptr) == 0 && s_logFilePath != nullptr)
        {
            // We can call this before closing the handle because DeleteFile just marks the file
            // for deletion, i.e. it does not actually get deleted until its last handle is closed.
            if (!DeleteFileA(s_logFilePath))
                fprintf(stderr, "WARNING: [Logger::CloseLogFile] DeleteFile failed. GetLastError()=%u\n", GetLastError());
        }

        if (!CloseHandle(s_logFile))
            fprintf(stderr, "WARNING: [Logger::CloseLogFile] CloseHandle failed. GetLastError()=%u\n", GetLastError());

        s_logFile = INVALID_HANDLE_VALUE;

        free(s_logFilePath);
        s_logFilePath = nullptr;
    }
}

//
// Returns a bitmask representing the logging levels that are specified by the given string. The string
// format is described explicitly in the command-line usage documentation for SuperPMI and MCS.
//
// In essence, each log level has a corresponding character representing it, and the presence of that
// character in the specifier string indicates that the log mask should include that log level. The
// "quiet" level will override any other level specified: that is, if a "q" is present in the string,
// all other levels specified will be disregarded.
//
// If "q" is not specified, and "a" is specified, then all log levels are enabled. This is a shorthand
// to avoid looking up all the log levels and enabling them all by specifying all the individual characters.
//
/* static */
UINT32 Logger::ParseLogLevelString(const char *specifierStr)
{
    UINT32 logLevelMask = LOGMASK_NONE;

    if (strchr(specifierStr, 'q') == nullptr)  // "Quiet" overrides all other specifiers
    {
        if (strchr(specifierStr, 'a') != nullptr)  // "All" overrides the other specifiers
        {
            logLevelMask |= LOGMASK_ALL;
        }
        else
        {
            if (strchr(specifierStr, 'e') != nullptr)
                logLevelMask |= LOGLEVEL_ERROR;

            if (strchr(specifierStr, 'w') != nullptr)
                logLevelMask |= LOGLEVEL_WARNING;

            if (strchr(specifierStr, 'm') != nullptr)
                logLevelMask |= LOGLEVEL_MISSING;

            if (strchr(specifierStr, 'i') != nullptr)
                logLevelMask |= LOGLEVEL_ISSUE;

            if (strchr(specifierStr, 'n') != nullptr)
                logLevelMask |= LOGLEVEL_INFO;

            if (strchr(specifierStr, 'v') != nullptr)
                logLevelMask |= LOGLEVEL_VERBOSE;

            if (strchr(specifierStr, 'd') != nullptr)
                logLevelMask |= LOGLEVEL_DEBUG;
        }
    }

    return logLevelMask;
}

/* static */
void Logger::LogPrintf(const char *function, const char *file, int line,
                       LogLevel level, const char *msg, ...)
{
    va_list argList;
    va_start(argList, msg);
    LogVprintf(function, file, line, level, argList, msg);
}

//
// Logs a message, if the given log level is enabled, to both the console and the log file. This is the
// main logging function that all other logging functions eventually funnel into.
//
/* static */
void Logger::LogVprintf(const char *function, const char *file, int line,
                        LogLevel level, va_list argList, const char *msg)
{
    if (!s_initialized)
    {
        fprintf(stderr, "ERROR: [Logger::LogVprintf] Invoked the logging system before initializing it.\n");
        __debugbreak();
    }

    // Capture this first to make the timestamp more accurately reflect the actual time of logging
    time_t timestamp = time(nullptr);

    int fullMsgLen = _vscprintf(msg, argList) + 1;  // This doesn't count the null terminator
    char *fullMsg = new char[fullMsgLen];

    _vsnprintf_s(fullMsg, fullMsgLen, fullMsgLen, msg, argList);
    va_end(argList);

    const char *logLevelStr = "INVALID_LOGLEVEL";
    switch (level)
    {
    case LOGLEVEL_ERROR:
        logLevelStr = "ERROR";
        break;

    case LOGLEVEL_WARNING:
        logLevelStr = "WARNING";
        break;

    case LOGLEVEL_MISSING:
        logLevelStr = "MISSING";
        break;

    case LOGLEVEL_ISSUE:
        logLevelStr = "ISSUE";
        break;

    case LOGLEVEL_INFO:
        logLevelStr = "INFO";
        break;

    case LOGLEVEL_VERBOSE:
        logLevelStr = "VERBOSE";
        break;

    case LOGLEVEL_DEBUG:
        logLevelStr = "DEBUG";
        break;
    }

    // NOTE: This implementation doesn't guarantee that log messages will be written in chronological
    // order, since Windows doesn't guarantee FIFO behavior when a thread relinquishes a lock. If
    // maintaining chronological order is crucial, then we can implement a priority queueing system
    // for log messages.

    EnterCriticalSection(&s_critSec);

    if (level & GetLogLevel())
    {
        // Sends error messages to stderr instead out stdout
        FILE *dest = (level <= LOGLEVEL_WARNING) ? stderr : stdout;

        if (level < LOGLEVEL_INFO)
            fprintf(dest, "%s: ", logLevelStr);

        fprintf(dest, "%s\n", fullMsg);

        if (s_logFile != INVALID_HANDLE_VALUE)
        {
#ifndef FEATURE_PAL // TODO: no localtime_s() or strftime() in PAL
            tm timeInfo;
            errno_t err = localtime_s(&timeInfo, &timestamp);
            if (err != 0)
            {
                fprintf(stderr, "WARNING: [Logger::LogVprintf] localtime failed with error %d.\n", err);
                goto CleanUp;
            }

            size_t timeStrBuffSize = 20 * sizeof(char);
            char *timeStr = (char *)malloc(timeStrBuffSize);  // Use malloc so we can realloc if necessary

            // This particular format string should always generate strings of the same size, but
            // for the sake of robustness, we shouldn't rely on that assumption.
            while (strftime(timeStr, timeStrBuffSize, "%Y-%m-%d %H:%M:%S", &timeInfo) == 0)
            {
                timeStrBuffSize *= 2;
                timeStr = (char *)realloc(timeStr, timeStrBuffSize);
            }
#else // FEATURE_PAL
            const char *timeStr = "";
#endif // FEATURE_PAL

            const char logEntryFmtStr[] = "%s - %s [%s:%d] - %s - %s\r\n";
            size_t logEntryBuffSize = sizeof(logEntryFmtStr) + strlen(timeStr) + strlen(function) +
                                      strlen(file) + 10 + strlen(logLevelStr) + strlen(fullMsg);

            char *logEntry = new char[logEntryBuffSize];
            sprintf_s(logEntry, logEntryBuffSize, logEntryFmtStr,
                      timeStr, function, file, line, logLevelStr, fullMsg);

            DWORD bytesWritten;

            if (!WriteFile(s_logFile, logEntry, (DWORD)logEntryBuffSize - 1, &bytesWritten, nullptr))
                fprintf(stderr, "WARNING: [Logger::LogVprintf] Failed to write to log file. GetLastError()=%u\n", GetLastError());

            if (!FlushFileBuffers(s_logFile))
                fprintf(stderr, "WARNING: [Logger::LogVprintf] Failed to flush log file. GetLastError()=%u\n", GetLastError());

            delete[] logEntry;

#ifndef FEATURE_PAL
            free((void*)timeStr);
#endif // !FEATURE_PAL
        }
    }

#ifndef FEATURE_PAL
CleanUp:
#endif // !FEATURE_PAL

    LeaveCriticalSection(&s_critSec);
    delete[] fullMsg;
}

//
// Special helper for logging exceptions. This logs the exception message given as a debug message.
//
/* static */
void Logger::LogExceptionMessage(const char *function, const char *file, int line,
                                 DWORD exceptionCode, const char *msg, ...)
{
    std::string fullMsg = "Exception thrown: ";
    fullMsg += msg;

    va_list argList;
    va_start(argList, msg);
    LogVprintf(function, file, line, LOGLEVEL_DEBUG, argList, fullMsg.c_str());
}

//
// Logger for JIT issues. Identifies the issue type and logs the given message normally.
//
/* static */
void IssueLogger::LogIssueHelper(const char *function, const char *file, int line,
                                 IssueType issue, const char *msg, ...)
{
    std::string fullMsg;

    switch (issue)
    {
    case ISSUE_ASSERT:
        fullMsg += "<ASSERT>";
        break;

    case ISSUE_ASM_DIFF:
        fullMsg += "<ASM_DIFF>";
        break;

    default:
        fullMsg += "<UNKNOWN_ISSUE_TYPE>";
        break;
    }

    fullMsg += " ";
    fullMsg += msg;

    va_list argList;
    va_start(argList, msg);
    Logger::LogVprintf(function, file, line, LOGLEVEL_ISSUE, argList, fullMsg.c_str());
}