Come scrivere nell'event log da C++

Ultimamente ero alle prese con un nuovo progetto nel quale ho sviluppato un componente per il NAP di Windows in linguaggio C++.

Quando ho iniziato a cimentarmi a scrivere all'interno degli eventi di Windows, ho tristemente scoperto che non era così semplice come avviene in managed code (per esempio C#).

In questo link viene spiegato in modo molto semplice come si scrive negli eventi di Windows utilizzando C++ unmanaged code, io cercherò di aggiungere qualche info in più per facilitare la comprensione di questo argomento.

https://msdn.microsoft.com/en-us/library/windows/desktop/aa363680(v=vs.85).aspx

L'approccio di base è il seguente:

  1. Definizione di tutti gli eventi all'interno di una DLL;
  2. Registrazione del provider degli aventi nel registro di sistema;
  3. Invocazione degli eventi dal programma (Esempio di utilizzo).

1. Definizione di tutti gli eventi all'interno di una DLL

Per definire gli eventi in una DLL partiamo da un file di testo dove elenchiamo gli eventi da creare in un formato ben preciso, e salvare questo file con nome MYEVENTPROVIDER:MC, ecco un esempio:


; // MyEventProvider.mc

; // This is the header section.

SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
               Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
               Warning=0x2:STATUS_SEVERITY_WARNING
               Error=0x3:STATUS_SEVERITY_ERROR
              )

FacilityNames=(System=0x0:FACILITY_SYSTEM
               Runtime=0x2:FACILITY_RUNTIME
               Stubs=0x3:FACILITY_STUBS
               Io=0x4:FACILITY_IO_ERROR_CODE
              )

LanguageNames=(English=0x409:MSG00409)

; // The following are the categories of events.

MessageIdTypedef=WORD

MessageId=0x1
SymbolicName=NETWORK_CATEGORY
Language=English
Network Events
.

MessageId=0x2
SymbolicName=DATABASE_CATEGORY
Language=English
Database Events
.

MessageId=0x3
SymbolicName=UI_CATEGORY
Language=English
UI Events
.

; // The following are the message definitions.

MessageIdTypedef=DWORD

MessageId=0x100
Severity=Error
Facility=Runtime
SymbolicName=MSG_INVALID_COMMAND
Language=English
The command is not valid.
.

MessageId=0x101
Severity=Error
Facility=System
SymbolicName=MSG_BAD_FILE_CONTENTS
Language=English
File %1 contains content that is not valid.
.

MessageId=0x102
Severity=Warning
Facility=System
SymbolicName=MSG_RETRIES
Language=English
There have been %1 retries with %2 success! Disconnect from
the server and try again later.
.

MessageId=0x103
Severity=Informational
Facility=System
SymbolicName=MSG_COMPUTE_CONVERSION
Language=English
%1 %%4096 = %2 %%4097.
.

; // The following are the parameter strings */

MessageId=0x1000
Severity=Success
Facility=System
SymbolicName=QUARTS_UNITS
Language=English
quarts%0
.

MessageId=0x1001
Severity=Success
Facility=System
SymbolicName=GALLONS_UNITS
Language=English
gallons%0
.


ATTENZIONE: Il punto definisce la fine del messaggio o della categoria definita.

E' possibile definire più linguaggi e di conseguenza definire i messaggi nelle varie lingue, ogni messaggio contiene una SEVERITY, FACILITY, SYMBOLICNAME (che  è l'identificativo che andremo ad utilizzare per scrivere il messaggio nel log degli eventi), LANGUAGE e infine il messaggio stesso che può contenere dei parametri %1 %2 e così via.

Una volta scritto il file con la definizione degli eventi, dobbiamo generare la DLL e l'header file che utilizzeremo nel nostro progetto.

Per compilare il file, abbiamo bisogno di tre comandi: mc.exe, rc.exelink.exe

I primi due sono presenti all'interno del Windows SDK mentre il terzo all'interno di visual studio.

Eseguire i comandi nel seguente ordine:

mc -U myeventprovider.mc

Compilare il file di risorse generato dal comando precedente:

rc myeventprovider.rc

Creare la DLL a partire dal file di risorse generato dal comando precedente (il parametro noentry è necessario).

link -dll -noentry myeventprovider.res

Il seguente è il file di header che va incluso nel progetto. Da notare all'interno del file gli identificativi dei messaggi.


// MyEventProvider.mc
// This is the header section.
// The following are categories of events.
//
// Values are 32 bit values laid out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-------------------------------+
// |Sev|C|R| Facility | Code |
// +---+-+-+-----------------------+-------------------------------+
//
// where
//
// Sev - is the severity code
//
// 00 - Success
// 01 - Informational
// 10 - Warning
// 11 - Error
//
// C - is the Customer code flag
//
// R - is a reserved bit
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
//
// Define the facility codes
//
#define FACILITY_SYSTEM                  0x0
#define FACILITY_STUBS                   0x3
#define FACILITY_RUNTIME                 0x2
#define FACILITY_IO_ERROR_CODE           0x4

//
// Define the severity codes
//
#define STATUS_SEVERITY_WARNING          0x2
#define STATUS_SEVERITY_SUCCESS          0x0
#define STATUS_SEVERITY_INFORMATIONAL    0x1
#define STATUS_SEVERITY_ERROR            0x3

//
// MessageId: NETWORK_CATEGORY
//
// MessageText:
//
// Network Events
//
#define NETWORK_CATEGORY                 ((WORD)0x00000001L)

//
// MessageId: DATABASE_CATEGORY
//
// MessageText:
//
// Database Events
//
#define DATABASE_CATEGORY                ((WORD)0x00000002L)

//
// MessageId: UI_CATEGORY
//
// MessageText:
//
// UI Events
//
#define UI_CATEGORY                      ((WORD)0x00000003L)

// The following are message definitions.
//
// MessageId: MSG_INVALID_COMMAND
//
// MessageText:
//
// The command is not valid.
//
#define MSG_INVALID_COMMAND              ((DWORD)0xC0020100L)

//
// MessageId: MSG_BAD_FILE_CONTENTS
//
// MessageText:
//
// File %1 contains content that is not valid.
//
#define MSG_BAD_FILE_CONTENTS            ((DWORD)0xC0000101L)

//
// MessageId: MSG_RETRIES
//
// MessageText:
//
// There have been %1 retries with %2 success! Disconnect from
// the server and try again later.
//
#define MSG_RETRIES                      ((DWORD)0x80000102L)

//
// MessageId: MSG_COMPUTE_CONVERSION
//
// MessageText:
//
// %1 %%4096 = %2 %%4097.
//
#define MSG_COMPUTE_CONVERSION           ((DWORD)0x40000103L)

// The following are the parameter strings */
//
// MessageId: QUARTS_UNITS
//
// MessageText:
//
// quarts%0
//
#define QUARTS_UNITS                     ((DWORD)0x00001000L)

//
// MessageId: GALLONS_UNITS
//
// MessageText:
//
// gallons%0
//
#define GALLONS_UNITS                    ((DWORD)0x00001001L)


2. Registrazione del provider degli aventi nel registro di sistema

Per registrare "MyEventProvider" come source event, modificare il registro del sistema manualmente, in particolare:

  • Se si vuole scrivere gli eventi nell'application log, utilizzare questa chiave: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Application
  • Se si vuole scrivere gli eventi in un log personalizzato, allora utilizzare questa chiave: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\MyAppLog ( MyAppLog deve essere creata manualmente)

La seguente tabella mostra i valori da inserire nelle varie chiavi da creare:

Value name Type Value data Commento
CategoryCount REG_DWORD 0x00000003 Indicare il numero di elementi definiti come categorie all'interno del file di definizione
CategoryMessageFile REG_SZ path\MyEventProvider.dll Indicare il path e nome della DLL da registrare e che contiene la definizione degli eventi
EventMessageFile REG_SZ path\MyEventProvider.dll Indicare il path e nome della DLL da registrare e che contiene la definizione degli eventi
ParameterMessageFile REG_SZ path\MyEventProvider.dll Indicare il path e nome della DLL da registrare e che contiene la definizione degli eventi
TypesSupported REG_DWORD 0x00000007

Questo numero normalmente i tipi di eventi ecco il bitmask:

  • EVENTLOG_AUDIT_FAILURE (0x0010)
  • EVENTLOG_AUDIT_SUCCESS (0x0008)
  • EVENTLOG_ERROR_TYPE (0x0001)
  • EVENTLOG_INFORMATION_TYPE (0x0004)
  • EVENTLOG_WARNING_TYPE (0x0002)

3. Invocazione degli eventi dal programma (Esempio di utilizzo)

Una volta compilata la DLL e registrata nel registro di sistema, possiamo testare il tutto creando una funzione che ci semplifichi la scrittura del codice.

La seguente è una funzione di esempio che ho creato e che è possibile utilizzare nel proprio codice.


#include "stdafx.h"

#include <windows.h>

#include "MyEventProvider.h" // Questo include è fondamentale per riconoscere i MessageId, è l'header generato durante la compilazione della DLL */

#define PROVIDER_NAME L"MyProvider" // Questo è il nome dato al nostro event provider, è possibile cambiarlo*/

int WriteLog(DWORD Type, WORD Category, DWORD Msg, int Dim, LPCWSTR* Param)

{

int err = 0;

HANDLE hEventLog = NULL;

// The source name (provider) must exist as a subkey of Application.

hEventLog = RegisterEventSource(NULL, PROVIDER_NAME);

if (NULL == hEventLog)

{

err = GetLastError();

goto cleanup;

}

// This event includes user-defined data as part of the event. The event message

// does not use insert strings.

if (!ReportEvent(hEventLog, Type, Category, Msg, NULL, Dim, 0, Param, NULL))

{

err = GetLastError();

goto cleanup;

}

cleanup:

if (hEventLog)

DeregisterEventSource(hEventLog);

return err;

}


Per testare il codice, creare un progetto C++, per esempio console application:

  1. Inserire l'include della definizione degli eventi: #include "MyEventProvider.h"
  2. Inserire il prototipo della funzione: int WriteLog(DWORD, WORD, DWORD, int, LPCWSTR*);
  3. Invocare la funzione nel seguente modo:

// This event uses insert strings.

pInsertStrings[0] = L"3";

pInsertStrings[1] = L"0";

if (WriteLog(EVENTLOG_WARNING_TYPE, NETWORK_CATEGORY, MSG_RETRIES, 2, (LPCWSTR*)pInsertStrings) != 0)

{

wprintf(L"ReportEvent failed with 0x%x for event 0x%x.\n", GetLastError(), MSG_RETRIES);

}

Con questo esempio si scrive un evento di warning di categoria Network Events, con messaggio There have been 3 retries with 0 success! Disconnect from
the server and try again later.