@@ -273,7 +273,8 @@ def load_ini(self, config):
273273 try :
274274 if config .has_option (self .section , self .setting ):
275275 self .set (config .get (self .section , self .setting ))
276- except configparser .InterpolationSyntaxError as e :
276+ except (configparser .InterpolationSyntaxError ,
277+ configparser .InterpolationMissingOptionError ) as e :
277278 raise ParsingOptionError (
278279 _ ("Error in %(filepath)s with section [%(section)s] at "
279280 "option %(option)s: %(message)s" ) % {
@@ -575,6 +576,48 @@ def get(self):
575576 return None
576577
577578
579+ class LoggingFormatOption (Option ):
580+ """Replace escaped % (as %%) with single %.
581+
582+ Config file parsing allows variable interpolation using
583+ %(keyname)s. However this is exactly the format that we need
584+ for creating a logging format string. So we tell the user to
585+ quote the string using %%(...). Then we turn %%( -> %( when
586+ retrieved.
587+ """
588+
589+ class_description = ("Allowed value: Python logging module named "
590+ "attributes with % sign doubled." )
591+
592+ def str2value (self , value ):
593+ """Check format of unquoted string looking for missing specifiers.
594+
595+ This does a dirty check to see if a token is missing a
596+ specifier. So "%(ascdate)s %(level) " would fail because of
597+ the 's' missing after 'level)'. But "%(ascdate)s %(level)s"
598+ would pass.
599+
600+ Note that %(foo)s generates a error from the ini parser
601+ with a less than wonderful message.
602+ """
603+ unquoted_val = value .replace ("%%(" , "%(" )
604+
605+ # regexp matches all current logging record object attribute names.
606+ scanned_result = re .sub (r'%\([A-Za-z_]+\)\S' ,'' , unquoted_val )
607+ if scanned_result .find ('%(' ) != - 1 :
608+ raise OptionValueError (
609+ self , unquoted_val ,
610+ "Check that all substitution tokens have a format "
611+ "specifier after the ). Unrecognized use of %%(...) in: "
612+ "%s" % scanned_result )
613+
614+ return str (unquoted_val )
615+
616+ def _value2str (self , value ):
617+ """Replace %( with %%( to quote the format substitution param.
618+ """
619+ return value .replace ("%(" , "%%(" )
620+
578621class OriginHeadersListOption (Option ):
579622
580623 """List of space seperated origin header values.
@@ -1614,6 +1657,10 @@ def str2value(self, value):
16141657 "Minimal severity level of messages written to log file.\n "
16151658 "If above 'config' option is set, this option has no effect.\n "
16161659 "Allowed values: DEBUG, INFO, WARNING, ERROR" ),
1660+ (LoggingFormatOption , "format" ,
1661+ "%(asctime)s %(levelname)s %(message)s" ,
1662+ "Format of the logging messages with all '%' signs\n "
1663+ "doubled so they are not interpreted by the config file." ),
16171664 (BooleanOption , "disable_loggers" , "no" ,
16181665 "If set to yes, only the loggers configured in this section will\n "
16191666 "be used. Yes will disable gunicorn's --access-logfile.\n " ),
@@ -2448,8 +2495,7 @@ def init_logging(self):
24482495 hdlr = logging .FileHandler (_file ) if _file else \
24492496 logging .StreamHandler (sys .stdout )
24502497
2451- formatter = logging .Formatter (
2452- '%(asctime)s %(levelname)s %(message)s' )
2498+ formatter = logging .Formatter (self ["LOGGING_FORMAT" ])
24532499 hdlr .setFormatter (formatter )
24542500 # no logging API to remove all existing handlers!?!
24552501 for h in logger .handlers :
0 commit comments