7
\$\begingroup\$

I have written the following function to be a general "error logging" solution for my Windows API projects. Basically, given a Windows system error code (a la GetLastError()), it will log the error message into a file:

#include <Windows.h>
#include <strsafe.h>

// MAX_PATH for file name, 12 for max signed int in base 10 (-2147483648), 20 for datetime string, 5 for brackets/colon/spaces
#define MAX_DFL_LENGTH (MAX_PATH + 37)

// So that it can be used as a callback if desired
typedef VOID(CALLBACK *ERRORPROC)(DWORD, WCHAR *, INT, HANDLE, BOOL);

/**
 * Logs an error message that occurred elsewhere in the program.
 * PARAMETERS:
 *  dwError - The system error code to process.
 *  wszFileName - The name of the file in which the error occurred (pass __FILEW__)
 *  nLine - The line at which the error was detected (pass __LINE__)
 *  hOutFile - A handle to an already open file for writing the error message to.
 *  fRecoverable - If true, this error is recoverable; return to the calling code. If false, exit after logging the error.
 */
VOID CALLBACK ErrorProc(
    DWORD dwError,
    WCHAR *wszFileName,
    INT nLine,
    HANDLE hOutFile,
    BOOL fRecoverable
)
{
    LPWSTR lpMsgBuf;
    DWORD dwWritten;
    HRESULT hr;
    UINT uLen;
    SYSTEMTIME st;
    WCHAR wszDateFileLine[MAX_DFL_LENGTH];
    CONST WCHAR *wRecovMsg[2] = { L" Not recoverable, exiting program.\r\n", L" Recoverable; continuing execution...\r\n" };

    (VOID)FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dwError,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPVOID)&lpMsgBuf,
        0,
        NULL
    );

    GetLocalTime(&st);
    StringCchPrintfW(
        wszDateFileLine, 
        MAX_DFL_LENGTH, 
        L"[%.4hu-%.2hu-%.2huT%.2hu:%.2hu:%.2hu %s:%d] ", 
        st.wYear, 
        st.wMonth, 
        st.wDay, 
        st.wHour, 
        st.wMinute, 
        st.wSecond, 
        wszFileName, 
        nLine
    );

    SetFilePointer(hOutFile, 0, NULL, FILE_END);
    hr = StringCbLengthW(wszDateFileLine, (MAX_DFL_LENGTH) * sizeof(WCHAR), &uLen);
    if (SUCCEEDED(hr))
    {
        WriteFile(hOutFile, wszDateFileLine, uLen, &dwWritten, NULL);
    }

    hr = StringCbLengthW(lpMsgBuf, STRSAFE_MAX_CCH * sizeof(WCHAR), &uLen);
    if (SUCCEEDED(hr))
    {
        // Length - 2 WCHARs to not write out the \r\n at the end of the error message.
        WriteFile(hOutFile, lpMsgBuf, uLen - (2 * sizeof(WCHAR)), &dwWritten, NULL);
    }

    WriteFile(hOutFile, wRecovMsg[!!fRecoverable], (36 + (!!fRecoverable * 3)) * sizeof(WCHAR), &dwWritten, NULL);
    LocalFree((HLOCAL)lpMsgBuf);

    if (FALSE == fRecoverable)
    {
        ExitProcess(dwError);
    }
}

While this historically worked well enough for my personal projects, I am wondering if there are any modifications I should make to the presentation or operation of this function before suggesting its addition to a larger Windows project with an entire team of developers.

\$\endgroup\$
  • 1
    \$\begingroup\$ instead several calls to WriteFile - first format buffer and once call WriteFile. the SetFilePointer never need at all. in case log file - open file with FILE_APPEND_DATA access. the win32 errors sometime ambiguous, frequently it (not injective) derived from NTSTATUS. you can query last status via RtlGetLastNtStatus() and then check are it converted to the same dwError via RtlNtStatusToDosError[NoTeb] if yes - more informative save status and it related error message \$\endgroup\$ – RbMm Mar 25 at 21:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.