Home > Software engineering >  Python logger time use updated OS timezone (replacement of time.tzset() in Windows)
Python logger time use updated OS timezone (replacement of time.tzset() in Windows)

Time:11-25

UPDATE: The original question seems invalid as even if I managed to force logger to use datetime.now(), it still does not solve my ultimate goal of making the logging timestamps responsive to OS timezone changes without restarting the python interpreter. I have provided the answer I found below.

How to force the logger to use datetime.now() instead of time.asctime()? I need the log to follow strictly the Windows OS provided time, but time.asctime() attempts to convert the timezone if it thinks is needed. How do I override this behaviour?

Currently i use a custom logging.format subclass with the format string '{asctime}: {levelname} - {message}'

The purpose of doing this is because my python script changes the OS timezone during the execution. I want the log to immediately follow the updated timezone right after the script changes it. I have tried to define a converter function inside the logging.format subclass, but that resulted in the timezone in the logs not being updated even after I changed it.

The code I used (from doc https://docs.python.org/3.9/library/logging.html#logging.Formatter.formatTime):

class custom(logging.Formatter):
    def converter(self, timestamp):
        return datetime.now()

Tried every single answer from here: How to Change the time zone in Python logging?, non works as they all do not update to the new timezone set in the OS by my script. I tried to importlib.reload(tzlocal) and also no use.

The only sensible answer I found was to use time.tzset() but it apparently is not available on Windows

CodePudding user response:

After some research, I solved my own problem using ctypes, which is inspired by this answer. Essentially, I override the logging.Formatter.formatTime function by adding a call to Windows system API to get the current system time. This is a completely different approach than all existing python modules (time, datetime, pytz etc.) as the timezone info is not buffered when the python interpreter is launched.

Code:

class real_time_formatter(logging.Formatter):
    def formatTime(self, record, datefmt=None):
        class SYSTEMTIME(ctypes.Structure):
            _fields_ = [('wYear', ctypes.c_int16),
                        ('wMonth', ctypes.c_int16),
                        ('wDayOfWeek', ctypes.c_int16),
                        ('wDay', ctypes.c_int16),
                        ('wHour', ctypes.c_int16),
                        ('wMinute', ctypes.c_int16),
                        ('wSecond', ctypes.c_int16),
                        ('wMilliseconds', ctypes.c_int16)]

        lpSystemTime = ctypes.pointer(SystemTime)
        ctypes.windll.kernel32.GetLocalTime(lpSystemTime)
        year = '0' * (4 - len(str(SystemTime.wYear)))   str(SystemTime.wYear)
        month = '0' * (2 - len(str(SystemTime.wMonth)))   str(SystemTime.wMonth)
        day = '0' * (2 - len(str(SystemTime.wDay)))   str(SystemTime.wDay)
        hour = '0' * (2 - len(str(SystemTime.wHour)))   str(SystemTime.wHour)
        minute = '0' * (2 - len(str(SystemTime.wMinute)))   str(SystemTime.wMinute)
        second = '0' * (2 - len(str(SystemTime.wSecond)))   str(SystemTime.wSecond)
        ms = '0' * (3 - len(str(SystemTime.wMilliseconds)))   str(SystemTime.wMilliseconds)
        return f'{year}-{month}-{day} {hour}:{minute}:{second}:{ms}'  # 2003-01-23 00:29:50,411

streamHandler.setFormatter(real_time_formatter())

This mimics the python {asctime} formatting, and of course you can change it however you like, since it is just a f-string.

  • Related