Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
54016b1
Add RegisterEventSource and DeregisterEventSource in _winapi
aisk Aug 16, 2025
427587f
Add ReportEvent to _winapi
aisk Aug 16, 2025
d78e2db
Update test
aisk Aug 16, 2025
30242e0
Update test
aisk Aug 16, 2025
3a7aca4
Add constants
aisk Aug 16, 2025
545657d
Add news entry
aisk Aug 16, 2025
f6ce06c
Fix news entry
aisk Aug 16, 2025
bfa41e5
Fix test
aisk Aug 18, 2025
093d248
Debug the test
aisk Aug 19, 2025
a186b71
Debug test
aisk Aug 19, 2025
1e3479a
Change log type to sucess type in test
aisk Aug 19, 2025
b167204
Fix log type
aisk Aug 19, 2025
7d26942
Remove ReportEvent with invalid handle cuz it will crash the process …
aisk Aug 19, 2025
a0ca21a
Merge remote-tracking branch 'upstream/main' into nteventlog
aisk Aug 21, 2025
89e7cf8
Apply suggestions from code review
aisk Sep 8, 2025
12d999d
Merge remote-tracking branch 'origin/nteventlog' into nteventlog
aisk Sep 8, 2025
696ca3d
Update generated file
aisk Sep 8, 2025
0e9953e
Update by review
aisk Sep 9, 2025
a0e331a
Fix raw_data signature
aisk Sep 9, 2025
d691c6c
Update NTEventLogHandler to use _winapi to emit event logs
aisk Oct 19, 2025
14a9b86
Update news entry
aisk Oct 19, 2025
3d06503
Update NTEventLogHandler by review comments
aisk Oct 23, 2025
9d3f66e
Only support one single string for _winapi.ReposrtEvent
aisk Oct 23, 2025
7503e18
Update by review comments
aisk Oct 24, 2025
7aa607a
Update Lib/test/test_winapi.py
aisk Oct 24, 2025
d4a8a33
Using errno
aisk Oct 24, 2025
4df7da4
Update Modules/_winapi.c
aisk Oct 24, 2025
f786db6
Merge remote-tracking branch 'origin/nteventlog' into nteventlog
aisk Oct 24, 2025
c82e5ac
Using LPCWSTR convertor
aisk Oct 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(env)
STRUCT_FOR_ID(errors)
STRUCT_FOR_ID(event)
STRUCT_FOR_ID(event_id)
STRUCT_FOR_ID(eventmask)
STRUCT_FOR_ID(exc)
STRUCT_FOR_ID(exc_type)
Expand Down Expand Up @@ -692,7 +691,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(query)
STRUCT_FOR_ID(quotetabs)
STRUCT_FOR_ID(raw)
STRUCT_FOR_ID(raw_data)
STRUCT_FOR_ID(read)
STRUCT_FOR_ID(read1)
STRUCT_FOR_ID(readable)
Expand Down Expand Up @@ -773,7 +771,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(strict)
STRUCT_FOR_ID(strict_mode)
STRUCT_FOR_ID(string)
STRUCT_FOR_ID(strings)
STRUCT_FOR_ID(sub_key)
STRUCT_FOR_ID(subcalls)
STRUCT_FOR_ID(symmetric_difference_update)
Expand Down
3 changes: 0 additions & 3 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 0 additions & 12 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 19 additions & 20 deletions Lib/logging/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1156,14 +1156,15 @@ def __init__(self, appname, dllname=None, logtype="Application"):
pass
self.deftype = _winapi.EVENTLOG_ERROR_TYPE
self.typemap = {
logging.DEBUG : _winapi.EVENTLOG_INFORMATION_TYPE,
logging.INFO : _winapi.EVENTLOG_INFORMATION_TYPE,
logging.WARNING : _winapi.EVENTLOG_WARNING_TYPE,
logging.ERROR : _winapi.EVENTLOG_ERROR_TYPE,
logging.DEBUG: _winapi.EVENTLOG_INFORMATION_TYPE,
logging.INFO: _winapi.EVENTLOG_INFORMATION_TYPE,
logging.WARNING: _winapi.EVENTLOG_WARNING_TYPE,
logging.ERROR: _winapi.EVENTLOG_ERROR_TYPE,
logging.CRITICAL: _winapi.EVENTLOG_ERROR_TYPE,
}

def _add_source_to_registry(self, appname, dllname, logtype):
@staticmethod
def _add_source_to_registry(appname, dllname, logtype):
import winreg

key_path = f"SYSTEM\\CurrentControlSet\\Services\\EventLog\\{logtype}\\{appname}"
Expand Down Expand Up @@ -1212,22 +1213,20 @@ def emit(self, record):
Determine the message ID, event category and event type. Then
log the message in the NT event log.
"""
if self._winapi:
try:
id = self.getMessageID(record)
cat = self.getEventCategory(record)
type = self.getEventType(record)
msg = self.format(record)

# Get a handle to the event log
handle = self._winapi.RegisterEventSource(None, self.appname)
try:
id = self.getMessageID(record)
cat = self.getEventCategory(record)
type = self.getEventType(record)
msg = self.format(record)

# Get a handle to the event log
handle = self._winapi.RegisterEventSource(None, self.appname)
if handle != self._winapi.INVALID_HANDLE_VALUE:
try:
self._winapi.ReportEvent(handle, type, cat, id, [msg])
finally:
self._winapi.DeregisterEventSource(handle)
except Exception:
self.handleError(record)
self._winapi.ReportEvent(handle, type, cat, id, msg)
finally:
self._winapi.DeregisterEventSource(handle)
except Exception:
self.handleError(record)

def close(self):
"""
Expand Down
6 changes: 3 additions & 3 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -7221,14 +7221,14 @@ def test_basic(self):
self.assertTrue(found, msg=msg)

@unittest.skipUnless(sys.platform == "win32", "Windows required for this test")
def test_updated_implementation(self):
h = logging.handlers.NTEventLogHandler('test_updated')
def test_without_pywin32(self):
h = logging.handlers.NTEventLogHandler('python_test')
self.addCleanup(h.close)

# Verify that the handler uses _winapi module
self.assertIsNotNone(h._winapi, "_winapi module should be available")

r = logging.makeLogRecord({'msg': 'Test Updated Implementation'})
r = logging.makeLogRecord({'msg': 'Hello!'})
h.emit(r)


Expand Down
25 changes: 14 additions & 11 deletions Lib/test/test_winapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,16 @@ def test_event_source_registration(self):
source_name = "PythonTestEventSource"

handle = _winapi.RegisterEventSource(None, source_name)
self.addCleanup(_winapi.DeregisterEventSource, handle)
self.assertNotEqual(handle, _winapi.INVALID_HANDLE_VALUE)

with self.assertRaisesRegex(OSError, '[WinError 87]'):
with self.assertRaises(OSError) as cm:
_winapi.RegisterEventSource(None, "")
self.assertEqual(cm.exception.winerror, 87)

with self.assertRaisesRegex(OSError, '[WinError 6]'):
with self.assertRaises(OSError) as cm:
_winapi.DeregisterEventSource(_winapi.INVALID_HANDLE_VALUE)
self.assertEqual(cm.exception.winerror, 6)

def test_report_event(self):
source_name = "PythonTestEventSource"
Expand All @@ -176,15 +179,15 @@ def test_report_event(self):
self.assertNotEqual(handle, _winapi.INVALID_HANDLE_VALUE)
self.addCleanup(_winapi.DeregisterEventSource, handle)

# Test with strings and raw data
test_strings = ["Test message 1", "Test message 2"]
test_data = b"test raw data"
_winapi.ReportEvent(handle, _winapi.EVENTLOG_SUCCESS, 1, 1002,
test_strings, test_data)
"Test message 1")

with self.assertRaises(TypeError):
_winapi.ReportEvent(handle, _winapi.EVENTLOG_SUCCESS, 1, 1002, 42)

# Test with empty strings list
_winapi.ReportEvent(handle, _winapi.EVENTLOG_AUDIT_FAILURE, 2, 1003, [])
with self.assertRaises(TypeError):
_winapi.ReportEvent(handle, _winapi.EVENTLOG_SUCCESS, 1, 1002, None)

with self.assertRaisesRegex(TypeError, 'expected a list of strings, not int'):
_winapi.ReportEvent(handle, _winapi.EVENTLOG_ERROR_TYPE, 0, 1001,
["string", 123])
with self.assertRaises(ValueError):
_winapi.ReportEvent(handle, _winapi.EVENTLOG_SUCCESS, 1, 1002,
"Test message \0 with embedded null character")
92 changes: 19 additions & 73 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -3029,8 +3029,9 @@ _winapi_DeregisterEventSource_impl(PyObject *module, HANDLE handle)
success = DeregisterEventSource(handle);
Py_END_ALLOW_THREADS

if (!success)
if (!success) {
return PyErr_SetFromWindowsErr(0);
}

Py_RETURN_NONE;
}
Expand All @@ -3046,99 +3047,44 @@ _winapi.ReportEvent
The event category.
event_id: unsigned_int(bitwise=False)
The event identifier.
strings: object(subclass_of='&PyList_Type')
A list of strings to be inserted into the event message.
raw_data: Py_buffer(accept={str, buffer, NoneType}) = None
The raw data for the event.
string: object
A string to be inserted into the event message.
/

Writes an entry at the end of the specified event log.
[clinic start generated code]*/

static PyObject *
_winapi_ReportEvent_impl(PyObject *module, HANDLE handle,
unsigned short type, unsigned short category,
unsigned int event_id, PyObject *strings,
Py_buffer *raw_data)
/*[clinic end generated code: output=fc3bbbde78cffd6c input=abcc01d4fc284975]*/
unsigned int event_id, PyObject *string)
/*[clinic end generated code: output=8eb5e919369c9c6d input=6db2c51252f95b93]*/
{
BOOL success;
LPCWSTR *string_array = NULL;
WORD num_strings = 0;
LPVOID data = NULL;
DWORD data_size = 0;

// Handle strings list
Py_ssize_t size = PyList_Size(strings);
if (size > USHRT_MAX) {
PyErr_SetString(PyExc_ValueError, "strings is too long");
return NULL;
}
num_strings = (WORD)size;
LPCWSTR wide_string = NULL;

// Handle raw data
if (raw_data->len > PY_DWORD_MAX) {
PyErr_SetString(PyExc_ValueError, "raw_data is too large");
if (!PyUnicode_Check(string)) {
PyErr_SetString(PyExc_TypeError, "string must be a str");
return NULL;
}
if (raw_data->obj != NULL) {
data = raw_data->buf;
data_size = (DWORD)raw_data->len;
}

if (num_strings > 0) {
string_array = (LPCWSTR *)PyMem_New(LPCWSTR, num_strings);
if (string_array == NULL) {
return PyErr_NoMemory();
}

for (WORD i = 0; i < num_strings; i++) {
PyObject *item = PyList_GetItemRef(strings, i);
if (item == NULL) {
// Clean up already allocated strings
for (WORD j = 0; j < i; j++) {
PyMem_Free((void *)string_array[j]);
}
PyMem_Free(string_array);
return NULL;
}
if (!PyUnicode_Check(item)) {
for (WORD j = 0; j < i; j++) {
PyMem_Free((void *)string_array[j]);
}
PyMem_Free(string_array);
PyErr_Format(PyExc_TypeError,
"expected a list of strings, not %T", item);
return NULL;
}
string_array[i] = PyUnicode_AsWideCharString(item, NULL);
Py_DECREF(item);
if (!string_array[i]) {
for (WORD j = 0; j < i; j++) {
PyMem_Free((void *)string_array[j]);
}
PyMem_Free(string_array);
return NULL;
}
}
wide_string = PyUnicode_AsWideCharString(string, NULL);
if (!wide_string) {
return NULL;
}

Py_BEGIN_ALLOW_THREADS
success = ReportEventW(handle, type, category, event_id,
NULL, num_strings, data_size,
string_array, data);
success = ReportEventW(handle, type, category, event_id, NULL, 1, 0,
&wide_string, NULL);
Py_END_ALLOW_THREADS

int ret = GetLastError();
// Clean up allocated strings
if (string_array) {
for (WORD i = 0; i < num_strings; i++) {
PyMem_Free((void *)string_array[i]);
}
PyMem_Free(string_array);
}

if (!success)
PyMem_Free((void *)wide_string);

if (!success) {
return PyErr_SetFromWindowsErr(ret);
}

Py_RETURN_NONE;
}
Expand Down
Loading
Loading