-
Notifications
You must be signed in to change notification settings - Fork 829
Level override check is case-insensitive. #2045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Level override check is case-insensitive. #2045
Conversation
@serilog/maintainers I'm on board with this one but suspect it needs another set of eyes on it, if just to spot other downstream places this could break. I'll merge in a week otherwise :-) Thanks @tillig! |
src/Serilog/Core/LevelOverrideMap.cs
Outdated
@@ -68,7 +68,7 @@ public LevelOverride(string context, LoggingLevelSwitch levelSwitch) | |||
{ | |||
foreach (var levelOverride in _overrides) | |||
{ | |||
if (context.StartsWith(levelOverride.Context) && | |||
if (context.StartsWith(levelOverride.Context, StringComparison.OrdinalIgnoreCase) && | |||
(context.Length == levelOverride.Context.Length || context[levelOverride.Context.Length] == '.')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely the second part of this condition is more selective and cheaper to compute ?
i.e.
var oLen = levelOverride.Context.Length;
if ((context.Length == oLen || (context.Length > oLen && context[oLen] == '.'))
&& context.StartsWith(levelOverride.Context, StringComparison.OrdinalIgnoreCase))
(my C# is too rusty to convert that to an is
expression here!)
Also maybe using context.Equals(levelOverride.Context, StringComparison.OrdinalIgnoreCase)
when the lens are equal might be cheaper (though I'm guessing startswith probably special cases that internally already)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yes, good call @bartelink. The clause would need a slight modification:
if (context.Length >= levelOverride.Context.Length &&
(context.Length == levelOverride.Context.Length || context[levelOverride.Context.Length] == '.') &&
context.StartsWith(levelOverride.Context, StringComparison.OrdinalIgnoreCase))
@tillig I wonder if this would be enough to claw back a few % of the performance regression related to the case-sensitivity check? I'll dig in and take a look when I have a chance, if you don't beat me to it :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm out of town for a week or so, so it'll be a bit before I'm back here. Happy to make the change, just will be a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm back and got this. Sorry for the delay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm. It appears swapping those has started causing tests to fail. I'll have to look into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm dumb, I missed the thing @nblumhardt mentioned.
I split the ==
and >
so in the full match scenario it should be just slightly faster since it won't have to check for equality again. I'm sure it's on the order of microseconds, but 🤷 .
Thank you @tillig! 👍 |
(context.Length == levelOverride.Context.Length || context[levelOverride.Context.Length] == '.')) | ||
if ( | ||
( | ||
context.Length == levelOverride.Context.Length || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the necroposting on something that's already overkill, but I will forever be pondering whether using a String.Equals into the same length case will be cheaper, or whether StartsWith
internally special cases this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what the current StartsWith
implementation does (when passing OrdinalIgnoreCase
):
case StringComparison.OrdinalIgnoreCase:
if (this.Length < value.Length)
{
return false;
}
return Ordinal.EqualsIgnoreCase(ref this.GetRawStringData(), ref value.GetRawStringData(), value.Length);
And this is (effectively, I inlined a method call for brevity) what Equals
does:
case StringComparison.OrdinalIgnoreCase:
if (a.Length != b.Length)
{
return false;
}
return Ordinal.EqualsIgnoreCase(ref a.GetRawStringData(), ref b.GetRawStringData(), b.Length);
In the case where the length is the same, they both call into the same machinery with the same length, so I think they should be equal here (no pun intended).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Kristian, this has made my day (especially for saving Travis the lookup from an annoying drive by commenter!)
(I had some idea this was the case, but you can never be sure whether your >40 hours of reading Stephen Toub posts has actually had any effect!)
Fix #1944.
Changes
LevelOverrideMap.GetEffectiveLevel()
to use a case-insensitive comparison for context names, correcting some challenges with setting overrides through mechanisms like environment variables where ALL_CAPS may be used.