Passing 'atTime' to TimedRotatingFileHandler via logging.config.dictConfig

I’m trying to set the atTime parameter of logging.config.TimedRotatingFileHandler. I can do this via standard Python code; however, my project uses the dictionary schema in YAML format.

YAML config:

version: 1
formatters:
  default:
    format: '%(asctime)s — %(module)s — %(levelname)s — %(message)s'
    datefmt: '%Y-%m-%d %H:%M:%S'
handlers:
  trfh:
    class: logging.handlers.TimedRotatingFileHandler
    formatter: default
    filename: app.log
    when: midnight
    atTime: datetime.time(20,30)
    interval: 1
    backupCount: 3
loggers:
  test_logger:
    level: INFO
    handlers: [trfh]
    propagate: no
root:
  level: INFO
  handlers: [trfh]

test.py:

import logging.config
import yaml

with open('config/logging.cfg.yaml') as f:
    c = yaml.safe_load(f.read())

logging.config.dictConfig(c)

logging.config.dictConfig() doesn’t seem to parse the value of atTime, although the string is correctly passed to computeRollover().

  File "/usr/lib/python3.11/logging/handlers.py", line 298, in computeRollover
    rotate_ts = ((self.atTime.hour * 60 + self.atTime.minute)*60 +
                  ^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'hour'

I’ve also tried the following syntax:

    when: midnight
    atTime:
      hour: 22

What is the correct way to set atTime via logging.config.dictConfig()?

The purpose of safe_load is specifically to avoid unsafe interpretation of the YAML contents, such as creating objects of arbitrary types (like datetime.time). If you trust the source, the documentation explains how to describe custom class instances in the YAML, but again keep in mind that this is insecure, in the same way as using Pickle.

If I put yaml datetime.time into a search engine I can fairly easily find a more specific solution:

1 Like

In addition to the advice from @kknechtel you may want to consider not putting python objects into your yaml at all.

You could replace the atTime and class with strings that you parse and map into objects after you safe_load the yaml. That way the details of your implementation are not required in the config.

2 Likes

Thank you. I can see this isn’t as straightforward as I thought it was going to be. Still, it’s a good way to learn how to do things like this correctly.

At the moment, I’m the only person using the config, but that could change, and I’d prefer not to create potential security issues with yaml.load(..., yaml.Loader) if I don’t need to.

Hard-coding a timestamp in the yaml file works with yaml.safe_load(), but I think @barry-scott’s suggestion is more practical.

I really like the sound of this, although perhaps I’m misunderstanding your suggestion. logging.handlers.TimedRotatingFileHandler is a string in the returned dictionary.

Are you saying I should insert the string "logging.handlers.TimedRotatingFileHandler" into the returned dictionary?

For the “class” I would add a log_file_handler paramater that takes one of a set of simple word answers, like timed_rotate, size_rotate, etc.

Then in your code map them into the implementation:

map_log_handler = {
    'timed_rotate': logging.config.TimedRotatingFileHandler,
    }

Does that help?

It does. Thank you!