Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 52 additions & 39 deletions Lib/logging/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,46 +1129,54 @@ class NTEventLogHandler(logging.Handler):
"""
A handler class which sends events to the NT Event Log. Adds a
registry entry for the specified application name. If no dllname is
provided, win32service.pyd (which contains some basic message
provided and pywin32 installed, win32service.pyd (which contains some basic message
placeholders) is used. Note that use of these placeholders will make
your event logs big, as the entire message source is held in the log.
If you want slimmer logs, you have to pass in the name of your own DLL
which contains the message definitions you want to use in the event log.
"""
def __init__(self, appname, dllname=None, logtype="Application"):
logging.Handler.__init__(self)
try:
import win32evtlogutil, win32evtlog
self.appname = appname
self._welu = win32evtlogutil
if not dllname:
dllname = os.path.split(self._welu.__file__)
import _winapi
self._winapi = _winapi
self.appname = appname
if not dllname:
# backward compatibility
try:
import win32evtlogutil
dllname = os.path.split(win32evtlogutil.__file__)
dllname = os.path.split(dllname[0])
dllname = os.path.join(dllname[0], r'win32service.pyd')
self.dllname = dllname
self.logtype = logtype
# Administrative privileges are required to add a source to the registry.
# This may not be available for a user that just wants to add to an
# existing source - handle this specific case.
try:
self._welu.AddSourceToRegistry(appname, dllname, logtype)
except Exception as e:
# This will probably be a pywintypes.error. Only raise if it's not
# an "access denied" error, else let it pass
if getattr(e, 'winerror', None) != 5: # not access denied
raise
self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
self.typemap = {
logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE,
logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE,
logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE,
logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
}
except ImportError:
print("The Python Win32 extensions for NT (service, event "\
"logging) appear not to be available.")
self._welu = None
except ImportError:
pass
self.dllname = dllname
self.logtype = logtype
# Administrative privileges are required to add a source to the registry.
# This may not be available for a user that just wants to add to an
# existing source - handle this specific case.
try:
self._add_source_to_registry(appname, dllname, logtype)
except PermissionError:
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.CRITICAL: _winapi.EVENTLOG_ERROR_TYPE,
}

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

key_path = f"SYSTEM\\CurrentControlSet\\Services\\EventLog\\{logtype}\\{appname}"

with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
if dllname:
winreg.SetValueEx(key, "EventMessageFile", 0, winreg.REG_EXPAND_SZ, dllname)
winreg.SetValueEx(key, "TypesSupported", 0, winreg.REG_DWORD, 7) # All types are supported

def getMessageID(self, record):
"""
Expand Down Expand Up @@ -1209,15 +1217,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._welu:
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)
self._welu.ReportEvent(self.appname, id, cat, type, [msg])
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
13 changes: 12 additions & 1 deletion Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -7248,8 +7248,8 @@ def test_compute_rollover(self, when=when, interval=interval, exp=exp):
setattr(TimedRotatingFileHandlerTest, name, test_compute_rollover)


@unittest.skipUnless(win32evtlog, 'win32evtlog/win32evtlogutil/pywintypes required for this test.')
class NTEventLogHandlerTest(BaseTest):
@unittest.skipUnless(win32evtlog, 'win32evtlog/win32evtlogutil/pywintypes required for this test.')
def test_basic(self):
logtype = 'Application'
elh = win32evtlog.OpenEventLog(None, logtype)
Expand Down Expand Up @@ -7283,6 +7283,17 @@ def test_basic(self):
msg = 'Record not found in event log, went back %d records' % GO_BACK
self.assertTrue(found, msg=msg)

@unittest.skipUnless(sys.platform == "win32", "Windows required for this test")
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': 'Hello!'})
h.emit(r)


class MiscTestCase(unittest.TestCase):
def test__all__(self):
Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_winapi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Test the Windows-only _winapi module

import errno
import os
import pathlib
import re
Expand Down Expand Up @@ -156,3 +157,38 @@ def test_namedpipe(self):
pipe2.write(b'testdata')
pipe2.flush()
self.assertEqual((b'testdata', 8), _winapi.PeekNamedPipe(pipe, 8)[:2])

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.assertRaises(OSError) as cm:
_winapi.RegisterEventSource(None, "")
self.assertEqual(cm.exception.errno, errno.EINVAL)

with self.assertRaises(OSError) as cm:
_winapi.DeregisterEventSource(_winapi.INVALID_HANDLE_VALUE)
self.assertEqual(cm.exception.errno, errno.EBADF)

def test_report_event(self):
source_name = "PythonTestEventSource"

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

_winapi.ReportEvent(handle, _winapi.EVENTLOG_SUCCESS, 1, 1002,
"Test message 1")

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

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

with self.assertRaises(ValueError):
_winapi.ReportEvent(handle, _winapi.EVENTLOG_SUCCESS, 1, 1002,
"Test message \0 with embedded null character")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add :func:`!_winapi.RegisterEventSource`,
:func:`!_winapi.DeregisterEventSource` and :func:`!_winapi.ReportEvent`.
Using these functions in :class:`~logging.handlers.NTEventLogHandler`
to replace :mod:`!pywin32`.
106 changes: 106 additions & 0 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -2982,6 +2982,103 @@ _winapi_CopyFile2_impl(PyObject *module, LPCWSTR existing_file_name,
Py_RETURN_NONE;
}

/*[clinic input]
_winapi.RegisterEventSource -> HANDLE

unc_server_name: LPCWSTR(accept={str, NoneType})
The UNC name of the server on which the event source should be registered.
If None, registers the event source on the local computer.
source_name: LPCWSTR
The name of the event source to register.
/

Retrieves a registered handle to the specified event log.
[clinic start generated code]*/

static HANDLE
_winapi_RegisterEventSource_impl(PyObject *module, LPCWSTR unc_server_name,
LPCWSTR source_name)
/*[clinic end generated code: output=e376c8950a89ae8f input=9d01059ac2156d0c]*/
{
HANDLE handle;

Py_BEGIN_ALLOW_THREADS
handle = RegisterEventSourceW(unc_server_name, source_name);
Py_END_ALLOW_THREADS

if (handle == NULL) {
PyErr_SetFromWindowsErr(0);
return INVALID_HANDLE_VALUE;
}

return handle;
}

/*[clinic input]
_winapi.DeregisterEventSource

handle: HANDLE
The handle to the event log to be deregistered.
/

Closes the specified event log.
[clinic start generated code]*/

static PyObject *
_winapi_DeregisterEventSource_impl(PyObject *module, HANDLE handle)
/*[clinic end generated code: output=7387ff34c7358bce input=947593cf67641f16]*/
{
BOOL success;

Py_BEGIN_ALLOW_THREADS
success = DeregisterEventSource(handle);
Py_END_ALLOW_THREADS

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

Py_RETURN_NONE;
}

/*[clinic input]
_winapi.ReportEvent

handle: HANDLE
The handle to the event log.
type: unsigned_short(bitwise=False)
The type of event being reported.
category: unsigned_short(bitwise=False)
The event category.
event_id: unsigned_int(bitwise=False)
The event identifier.
string: LPCWSTR
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, LPCWSTR string)
/*[clinic end generated code: output=4281230b70a2470a input=8fb3385b8e7a6d3d]*/
{
BOOL success;

Py_BEGIN_ALLOW_THREADS
success = ReportEventW(handle, type, category, event_id, NULL, 1, 0,
&string, NULL);
Py_END_ALLOW_THREADS

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

Py_RETURN_NONE;
}


static PyMethodDef winapi_functions[] = {
_WINAPI_CLOSEHANDLE_METHODDEF
Expand All @@ -2994,6 +3091,7 @@ static PyMethodDef winapi_functions[] = {
_WINAPI_CREATEPIPE_METHODDEF
_WINAPI_CREATEPROCESS_METHODDEF
_WINAPI_CREATEJUNCTION_METHODDEF
_WINAPI_DEREGISTEREVENTSOURCE_METHODDEF
_WINAPI_DUPLICATEHANDLE_METHODDEF
_WINAPI_EXITPROCESS_METHODDEF
_WINAPI_GETCURRENTPROCESS_METHODDEF
Expand All @@ -3010,6 +3108,8 @@ static PyMethodDef winapi_functions[] = {
_WINAPI_OPENMUTEXW_METHODDEF
_WINAPI_OPENPROCESS_METHODDEF
_WINAPI_PEEKNAMEDPIPE_METHODDEF
_WINAPI_REGISTEREVENTSOURCE_METHODDEF
_WINAPI_REPORTEVENT_METHODDEF
_WINAPI_LCMAPSTRINGEX_METHODDEF
_WINAPI_READFILE_METHODDEF
_WINAPI_RELEASEMUTEX_METHODDEF
Expand Down Expand Up @@ -3082,6 +3182,12 @@ static int winapi_exec(PyObject *m)
WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_CONNECTED);
WINAPI_CONSTANT(F_DWORD, ERROR_PRIVILEGE_NOT_HELD);
WINAPI_CONSTANT(F_DWORD, ERROR_SEM_TIMEOUT);
WINAPI_CONSTANT(F_DWORD, EVENTLOG_SUCCESS);
WINAPI_CONSTANT(F_DWORD, EVENTLOG_AUDIT_FAILURE);
WINAPI_CONSTANT(F_DWORD, EVENTLOG_AUDIT_SUCCESS);
WINAPI_CONSTANT(F_DWORD, EVENTLOG_ERROR_TYPE);
WINAPI_CONSTANT(F_DWORD, EVENTLOG_INFORMATION_TYPE);
WINAPI_CONSTANT(F_DWORD, EVENTLOG_WARNING_TYPE);
WINAPI_CONSTANT(F_DWORD, FILE_FLAG_FIRST_PIPE_INSTANCE);
WINAPI_CONSTANT(F_DWORD, FILE_FLAG_OVERLAPPED);
WINAPI_CONSTANT(F_DWORD, FILE_GENERIC_READ);
Expand Down
Loading
Loading