35

I would like to change the timestamp in the log file so that it reflects my current time zone so that i can debug errors at a faster rate,

is it possible that i can change the time zone in the log file ?

currently my config is:

logging.basicConfig(filename='audit.log',
                filemode='w',
                level=logging.INFO,
                format='%(asctime)s %(message)s',
                datefmt='%m/%d/%Y %I:%M:%S %p')
25mhz
  • 1,052
  • 2
  • 10
  • 14
  • Logs come in all kinds of formats. You'll probably need to post a sample. That is, if you mean change the time zone on an existing log file. – CivFan Sep 04 '15 at 16:24
  • And what is the timezone that it currently outputs the time in? Is the timezone correctly set on the computer the code is running on? Because according to [logging page](https://docs.python.org/2/library/logging.html) `time.localtime()` is used by default. – CristiFati Sep 04 '15 at 16:37
  • its in Alaska Time Zone UTC-09:00. i cant change the system timezone as there are other applications which are time dependent. – 25mhz Sep 04 '15 at 16:44
  • There was no question of changing the timezone. So the logger outputs the time in UTC-09. And that timezone would you like it to be (what is your timezone)? Can you send the output of the following statement executed in Python: `print time.tzname, time.timezone, time.localtime(), time.gmtime()` (don't forget to `import time` prior). – CristiFati Sep 04 '15 at 16:56
  • As @wyrmwood says, "depending on the application, logging in local time zones can create ambiguity or at least confusion twice a year, where 2 AM is skipped or 1 AM repeats, and possibly others." I wanted to repeat his message up here, as in my opinion this is the wrong way to go, and in my experience we all do better when we always log in UTC. Perhaps you can adjust your tooling some other way to help with your problem? – samjewell Jul 01 '21 at 13:46

7 Answers7

44

How to log the timezone

%Z from strftime format

Windows

>>> import logging
>>> logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%m/%d/%Y %I:%M:%S %p %Z")
>>> logging.error('test')
11/03/2017 02:29:54 PM Mountain Daylight Time test

Linux

>>> import logging
>>> logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%m/%d/%Y %I:%M:%S %p %Z")
>>> logging.error('test')
11/03/2017 02:30:50 PM MDT test

If the question is

How do I log in a different timezone than the local time on the server?

part of the answer is logging.Formatter.converter, however, you have to understand naive and aware datetime objects. Unless you want to write your own timezone module, I highly suggest the pytz library (pip install pytz). Python 3 includes a UTC and UTC offset timezone, but there's rules you'll have to implement for daylight saving or other offsets, so I would suggest the pytz library, even for python 3.

For example,

>>> import datetime
>>> utc_now = datetime.datetime.utcnow()
>>> utc_now.isoformat()
'2019-05-21T02:30:09.422638'
>>> utc_now.tzinfo
(None)

If I apply a timezone to this datetime object, the time won't change (or will issue a ValueError for < python 3.7ish).

>>> mst_now = utc_now.astimezone(pytz.timezone('America/Denver'))
>>> mst_now.isoformat()
'2019-05-21T02:30:09.422638-06:00'
>>> utc_now.isoformat()
'2019-05-21T02:30:09.422638'

However, if instead, I do

>>> import pytz
>>> utc_now = datetime.datetime.now(tz=pytz.timezone('UTC'))
>>> utc_now.tzinfo
<UTC>

now we can create a properly translated datetime object in whatever timezone we wish

>>> mst_now = utc_now.astimezone(pytz.timezone('America/Denver'))
>>> mst_now.isoformat()
'2019-05-20T20:31:44.913939-06:00'

Aha! Now to apply this to the logging module.

Epoch timestamp to string representation with timezone

The LogRecord.created attribute is set to the time when the LogRecord was created (as returned by time.time()), from the time module. This returns a timestamp (seconds since the epoch). You can do your own translation to a given timezone, but again, I suggest pytz, by overriding the converter.

import datetime
import logging
import pytz

class Formatter(logging.Formatter):
    """override logging.Formatter to use an aware datetime object"""
    def converter(self, timestamp):
        dt = datetime.datetime.fromtimestamp(timestamp)
        tzinfo = pytz.timezone('America/Denver')
        return tzinfo.localize(dt)
        
    def formatTime(self, record, datefmt=None):
        dt = self.converter(record.created)
        if datefmt:
            s = dt.strftime(datefmt)
        else:
            try:
                s = dt.isoformat(timespec='milliseconds')
            except TypeError:
                s = dt.isoformat()
        return s

Python 3.5, 2.7

>>> logger = logging.root
>>> handler = logging.StreamHandler()
>>> handler.setFormatter(Formatter("%(asctime)s %(message)s"))
>>> logger.addHandler(handler)
>>> logger.setLevel(logging.DEBUG)
>>> logger.debug('test')
2019-05-20T22:25:10.758782-06:00 test

Python 3.7

>>> logger = logging.root
>>> handler = logging.StreamHandler()
>>> handler.setFormatter(Formatter("%(asctime)s %(message)s"))
>>> logger.addHandler(handler)
>>> logger.setLevel(logging.DEBUG)
>>> logger.debug('test')
2019-05-20T22:29:21.678-06:00 test

Substitute America/Denver with America/Anchorage for the posix timezone as defined by pytz

>>> next(_ for _ in pytz.common_timezones if 'Alaska' in _)
'US/Alaska'

US/Alaska is deprecated

>>> [_ for _ in pytz.all_timezones if 'Anchorage' in _]
['America/Anchorage']

Local

If you got to this question and answers looking for how to log the local timezone, then instead of hardcoding the timezone, get tzlocal (pip install tzlocal) and replace

        tzinfo = pytz.timezone('America/Denver')

with

        tzinfo = tzlocal.get_localzone()

Now it will work on whatever server runs the script, with the timezone on the server.

Caveat when not logging UTC

I should add, depending on the application, logging in local time zones can create ambiguity or at least confusion twice a year, where 2 AM is skipped or 1 AM repeats, and possibly others.

Wyrmwood
  • 3,340
  • 29
  • 33
  • I don't get it. How will this change the timezone? It just prints out the system's timezone. If the system's timezone is set to UTC, which might very well be possible, it will just print out "...UTC test" instead (or whatever is the string for UTC). But that might differ from the timezone I'm debugging in (ie when debugging a remote server). – Daniel F May 16 '19 at 18:35
  • @25mhz states in a comment that _"its in Alaska Time Zone UTC-09:00. i cant change the system timezone as there are other applications which are time dependent."_ If I am not mistaken, your approach will always print out that system's timezone, which is where the script that contains your code is executed. And the system's timezone will then always show as AKST (possibly also AKDT). This is not what he wants, he wants to be able to adjust some code on the server so that the logs show the timezone where he is without affecting anything else but the logger's output. That's how I see it. – Daniel F May 16 '19 at 19:34
  • Once a year. The second time the hour is skipped without creating an ambiguity. – Antony Hatchkins Jun 05 '20 at 13:32
  • @Wyrmwood OMG ... thank you for this very detailed explanation! This is _exactly_ what I needed! – pepoluan Dec 14 '20 at 16:07
24
#!/usr/bin/python

from datetime import datetime
from pytz import timezone
import logging

def timetz(*args):
    return datetime.now(tz).timetuple()

tz = timezone('Asia/Shanghai') # UTC, Asia/Shanghai, Europe/Berlin

logging.Formatter.converter = timetz

logging.basicConfig(
    format="%(asctime)s %(levelname)s: %(message)s",
    level=logging.INFO,
    datefmt="%Y-%m-%d %H:%M:%S",
)

logging.info('Timezone: ' + str(tz))

Using pytz to define a timezone relative to UTC.
Based on the example by: secsilm

stephanwehr
  • 251
  • 2
  • 4
18
#!/usr/bin/env python
from datetime import datetime
import logging
import time

from pytz import timezone, utc


def main():
    logging.basicConfig(format="%(asctime)s %(message)s",
                        datefmt="%Y-%m-%d %H:%M:%S")
    logger = logging.getLogger(__name__)
    logger.error("default")

    logging.Formatter.converter = time.localtime
    logger.error("localtime")

    logging.Formatter.converter = time.gmtime
    logger.error("gmtime")

    def customTime(*args):
        utc_dt = utc.localize(datetime.utcnow())
        my_tz = timezone("US/Eastern")
        converted = utc_dt.astimezone(my_tz)
        return converted.timetuple()

    logging.Formatter.converter = customTime
    logger.error("customTime")

    # to find the string code for your desired tz...
    # print(pytz.all_timezones)
    # print(pytz.common_timezones)


if __name__ == "__main__":
    main()
  • Ostensibly the pytz package is the blessed way of converting time zones in Python. So we start with datetime, convert, then get the (immutable) time_tuple to match return type of the time methods
  • Setting the logging.Formatter.converter function is recommended by this answer: (Python logging: How to set time to GMT).
  • Find your favorite TZ code by uncommenting the end lines
Ryan J McCall
  • 423
  • 3
  • 14
16

just add this pythonic line to your code (using pytz and datetime):

from pytz import timezone
from datetime import datetime
import logging

logging.Formatter.converter = lambda *args: datetime.now(tz=timezone('tz string name')).timetuple()

# quoting Ryan J McCall: to find the string name for your desired timezone...
# print(pytz.all_timezones)
# or print(pytz.common_timezones)
Isaac
  • 161
  • 1
  • 5
  • 1
    Works like a magic! thank you. – Ali Abdi Oct 24 '22 at 12:21
  • 1
    The same line, also works using `from zoneinfo import ZoneInfo`. The complete line will be `logging.Formatter.converter = lambda *args: datetime.now(tz=ZoneInfo('Europe/Paris')).timetuple()` – juanbretti Feb 21 '23 at 20:29
3

An alternative solution if you want to use logging configuration function:

import pytz
import logging
import logging.config
from datetime import datetime

tz = pytz.timezone('Asia/Tokyo')

class TokyoFormatter(logging.Formatter):
    converter = lambda *args: datetime.now(tz).timetuple()

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'Tokyo': {
            '()': TokyoFormatter,
            'format': '%(asctime)s %(levelname)s: %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S'
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'Tokyo'
        },
    },
    'loggers': {
        'foo': {
            'handlers': ['console'],
            'level': 'INFO'
        },
    }
}

logging.config.dictConfig(LOGGING)
logger = logging.getLogger('foo')
logger.info('Just a test.')

Define the logging formatter, e.g., "TokyoFormatter". It has an attibute "converter", finishing the job of converting the time zone. For more details, please refer to Customizing handlers with dictConfig().

DavidLin3
  • 353
  • 3
  • 7
-2
import logging, time
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
converter = lambda x, y: (datetime.utcnow() - timedelta(
    hours=7 if time.localtime().tm_isdst else 6)
).timetuple()
logging.Formatter.converter = converter

Edited as Elias points out the original answer didn't check for DST.

Liyun Li
  • 1
  • 2
-2

If you know your utc offset, you can define a function to correct the time and then pass it to logging.Formatter.converter.

For example, you want to convert the time to UTC+8 timezone, then:

import logging
import datetime


def beijing(sec, what):
    '''sec and what is unused.'''
    beijing_time = datetime.datetime.now() + datetime.timedelta(hours=8)
    return beijing_time.timetuple()


logging.Formatter.converter = beijing

logging.basicConfig(
    format="%(asctime)s %(levelname)s: %(message)s",
    level=logging.INFO,
    datefmt="%Y-%m-%d %H:%M:%S",
)

Just change the hours in datetime.timedelta(hours=8) depending on your situation.

Reference: https://alanlee.fun/2019/01/06/how-to-change-logging-date-timezone/

secsilm
  • 381
  • 5
  • 16