parse() treats escaped literals as fields for the % and { styles, and reads a { style conversion/format spec as part of the field name.
#69 fixed this for $ ($$ is now skipped). The other two styles have the same class of problem.
The quickstart says the standard-library styles exist so you can use these formatters "with your existing config", so a format that works with logging.Formatter should select the same fields here.
Oracle used: the substitution engine itself — fmt % tracking_dict for %, string.Formatter().parse() for {, string.Template(fmt).get_identifiers() for $. Over 37 formats: $ 7/7 agree, % and { diverge 13 times.
| style |
fmt |
parse() |
actually substituted |
% |
"%(a)s%%(b)s%(c)s" |
a, b, c |
a, c |
% |
"%%(x)s%%(y)s" |
x, y |
none |
{ |
"{a}{{b}}{c}" |
a, {b, c |
a, c |
{ |
"{levelname:>8}" |
levelname:>8 |
levelname |
{ |
"{message!r}" |
message!r |
message |
The { cases look the most likely to bite: "{levelname:>8} {message}" is an ordinary stdlib format (StrFormatStyle.validate() accepts it), and with a JSON formatter it emits a "levelname:>8": null key while dropping levelname entirely.
Happy to put up a PR — the shape I have working is % skipping %% the same way $ skips $$, and { using string.Formatter, which is what logging.StrFormatStyle.validate already parses the same string with (it also handles nested specs like {levelname:>{width}}, which a regex does not).
Investigated with AI assistance; the differential above was run locally and I have reviewed and tested the result.
parse()treats escaped literals as fields for the%and{styles, and reads a{style conversion/format spec as part of the field name.#69 fixed this for
$($$is now skipped). The other two styles have the same class of problem.The quickstart says the standard-library styles exist so you can use these formatters "with your existing config", so a format that works with
logging.Formattershould select the same fields here.Oracle used: the substitution engine itself —
fmt % tracking_dictfor%,string.Formatter().parse()for{,string.Template(fmt).get_identifiers()for$. Over 37 formats:$7/7 agree,%and{diverge 13 times.parse()%"%(a)s%%(b)s%(c)s"a, b, ca, c%"%%(x)s%%(y)s"x, y{"{a}{{b}}{c}"a, {b, ca, c{"{levelname:>8}"levelname:>8levelname{"{message!r}"message!rmessageThe
{cases look the most likely to bite:"{levelname:>8} {message}"is an ordinary stdlib format (StrFormatStyle.validate()accepts it), and with a JSON formatter it emits a"levelname:>8": nullkey while droppinglevelnameentirely.Happy to put up a PR — the shape I have working is
%skipping%%the same way$skips$$, and{usingstring.Formatter, which is whatlogging.StrFormatStyle.validatealready parses the same string with (it also handles nested specs like{levelname:>{width}}, which a regex does not).Investigated with AI assistance; the differential above was run locally and I have reviewed and tested the result.