Reputation: 31513
Consider the following snippet of a Python logging
YAML config file:
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
logfile:
class: logging.handlers.TimedRotatingFileHandler
level: DEBUG
filename: some_fancy_import_name.generate_filename_called_error
backupCount: 5
formatter: simple
I would like to load this YAML config file this way:
with open('logging.yaml', 'r') as fd:
config = yaml.safe_load(fd.read())
logging.config.dictConfig(config)
Take special notice of the filename
to which the handler
should write logs. In normal Python code, I would expect some_fancy_import_name.generate_filename_called_errorlog
to generate the string 'error.log'
. All in all, I would like to say that this logging handler should write to the file 'error.log'
in the current directory.
However, as it turns out, this is not the case. When I look at the current directory, I see a file named 'some_fancy_import_name.generate_filename_called_errorlog'
.
I would like filename
to be programmatically determined. I have successfully tried configuring logging using normal Python scripting this way:
# fancy import name
from os import environ as env
# Programmatically determine filename path
log_location = env.get('OPENSHIFT_LOG_DIR', '.')
log_filename = os.path.join(log_location, 'error')
handler = logging.handlers.TimedRotatingFileHandler(log_filename)
See how the log_filename
path was inferred from environment variables.
I would like to translate this to a YAML config file. Is it possible?
Perhaps I might need to dig through the dict
produced by yaml.safe_load(fd.read())
and do some eval()
stuff?
Upvotes: 3
Views: 2413
Reputation: 183
I just now had this issue come up myself and I approached it in a much more boring way. I left out the filename attribute of the logfile handler from my yaml file and added it to the config dictionary created by yaml.load like this:
with open('logging.yaml', 'r') as fd:
config = yaml.load(fd.read())
config['handlers']['logfile']['filename'] = generate_logfile_name(...)
logging.config.dictConfig(config)
Upvotes: 0
Reputation: 31513
Thanks to flyx's answer, this is how I did it:
import logging
import yaml
from os import environ as env
def constructor_logfilename(loader, node):
value = loader.construct_scalar(node)
return os.path.join(env.get('OPENSHIFT_LOG_DIR', '.'), value)
yaml.add_constructor(u'!logfilename', constructor_logfilename)
with open('logging.yaml', 'r') as fd:
config = yaml.load(fd.read())
logging.config.dictConfig(config)
In the logging.yaml
file, here's the important snippet:
...
filename: !logfilename error.log
...
Upvotes: 3
Reputation: 39708
You can add a custom constructor and mark the value with a special tag, so your constructor gets executed when loading it:
import yaml
def eval_constructor(loader, node):
return eval(loader.construct_scalar(node))
yaml.add_constructor(u'!eval', eval_constructor)
some_value = '123'
config = yaml.load("""
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
logfile:
class: logging.handlers.TimedRotatingFileHandler
level: DEBUG
filename: !eval some_value
backupCount: 5
formatter: simple
""")
print config['handlers']['logfile']['filename']
This prints 123
, since the value some_value
has the tag !eval
, and therefore is loaded with eval_constructor
.
Be aware of the security implications of eval
ing configuration data. Arbitrary Python code can be executed by writing it into the YAML file!
Upvotes: 5