Dependancy Update: Switch to Serilog.Expressions away from deprecated Serilog.Filters.Expressions (#12000)

* Uninstall Serilog.Filters.Expressions as it is deprecated by the Serilog team

NOTE: This package brought in a dependenacy on a package called SuperPower from Datalust

* Install replacement package Serilog.Expressions

* Update implementation of Serilog.Expressions

* Add new test cases to verify expressions still working

Currently Serilog.Expressions only supports short names such as @x @l @m and @mt as opposed @Exception @Level @Message and @MessageTemplate

* Use Serilog.Expressions NamedFilters extension point, to plug the missing Has() function in expressions that was not ported across by the library

* Update to dev build of Serilog.Expressions to verify new method we can override works

* Update to release build of Serilog.Expressions 3.3.0 now its been pushed to Nuget
This commit is contained in:
Warren Buckley
2022-02-28 07:48:10 +00:00
committed by GitHub
parent 83204edbd4
commit bd6334ecdc
5 changed files with 84 additions and 12 deletions

View File

@@ -1,7 +1,8 @@
using System;
using System;
using System.Linq;
using Serilog.Events;
using Serilog.Filters.Expressions;
using Serilog.Expressions;
using Umbraco.Cms.Infrastructure.Logging.Viewer;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Logging.Viewer
@@ -10,32 +11,43 @@ namespace Umbraco.Cms.Core.Logging.Viewer
internal class ExpressionFilter : ILogFilter
{
private readonly Func<LogEvent, bool> _filter;
private const string ExpressionOperators = "()+=*<>%-";
private const string s_expressionOperators = "()+=*<>%-";
public ExpressionFilter(string filterExpression)
{
Func<LogEvent, bool> filter;
// Our custom Serilog Functions to extend Serilog.Expressions
// In this case we are plugging the gap for the missing Has()
// function from porting away from Serilog.Filters.Expressions to Serilog.Expressions
// Along with patching support for the more verbose built in property names
var customSerilogFunctions = new SerilogLegacyNameResolver(typeof(SerilogExpressionsFunctions));
if (string.IsNullOrEmpty(filterExpression))
{
return;
}
// If the expression is one word and doesn't contain a serilog operator then we can perform a like search
if (!filterExpression.Contains(" ") && !filterExpression.ContainsAny(ExpressionOperators.Select(c => c)))
if (!filterExpression.Contains(" ") && !filterExpression.ContainsAny(s_expressionOperators.Select(c => c)))
{
filter = PerformMessageLikeFilter(filterExpression);
}
else // check if it's a valid expression
{
// If the expression evaluates then make it into a filter
if (FilterLanguage.TryCreateFilter(filterExpression, out var eval, out _))
if (SerilogExpression.TryCompile(filterExpression, null, customSerilogFunctions, out CompiledExpression compiled, out var error))
{
filter = evt => true.Equals(eval(evt));
filter = evt =>
{
LogEventPropertyValue result = compiled(evt);
return ExpressionResult.IsTrue(result);
};
}
else
{
//Assume the expression was a search string and make a Like filter from that
// 'error' describes a syntax error, where it was unable to compile an expression
// Assume the expression was a search string and make a Like filter from that
filter = PerformMessageLikeFilter(filterExpression);
}
}
@@ -50,10 +62,15 @@ namespace Umbraco.Cms.Core.Logging.Viewer
private Func<LogEvent, bool> PerformMessageLikeFilter(string filterExpression)
{
var filterSearch = $"@Message like '%{FilterLanguage.EscapeLikeExpressionContent(filterExpression)}%'";
if (FilterLanguage.TryCreateFilter(filterSearch, out var eval, out _))
var filterSearch = $"@Message like '%{SerilogExpression.EscapeLikeExpressionContent(filterExpression)}%'";
if (SerilogExpression.TryCompile(filterSearch, out CompiledExpression compiled, out var error))
{
return evt => true.Equals(eval(evt));
// `compiled` is a function that can be executed against `LogEvent`s:
return evt =>
{
LogEventPropertyValue result = compiled(evt);
return ExpressionResult.IsTrue(result);
};
}
return null;

View File

@@ -0,0 +1,14 @@
using Serilog.Events;
namespace Umbraco.Cms.Infrastructure.Logging.Viewer
{
public class SerilogExpressionsFunctions
{
// This Has() code is the same as the renamed IsDefined() function
// Added this to help backport and ensure saved queries continue to work if using Has()
public static LogEventPropertyValue? Has(LogEventPropertyValue? value)
{
return new ScalarValue(value != null);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using Serilog.Expressions;
namespace Umbraco.Cms.Infrastructure.Logging.Viewer
{
/// <summary>
/// Inherits Serilog's StaticMemberNameResolver to ensure we get same functionality
/// Of easily allowing any static methods definied in the passed in class/type
/// To extend as functions to use for filtering logs such as Has() and any other custom ones
/// </summary>
public class SerilogLegacyNameResolver : StaticMemberNameResolver
{
public SerilogLegacyNameResolver(Type type) : base(type)
{
}
/// <summary>
/// Allows us to fix the gap from migrating away from Serilog.Filters.Expressions
/// So we can still support the more verbose built in property names such as
/// Exception, Level, MessageTemplate etc
/// </summary>
public override bool TryResolveBuiltInPropertyName(string alias, out string target)
{
target = alias switch
{
"Exception" => "x",
"Level" => "l",
"Message" => "m",
"MessageTemplate" => "mt",
"Properties" => "p",
"Timestamp" => "t",
_ => null
};
return target != null;
}
}
}

View File

@@ -40,8 +40,8 @@
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.2" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Expressions" Version="3.3.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
<PackageReference Include="Serilog.Filters.Expressions" Version="2.1.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="1.0.5" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />

View File

@@ -186,10 +186,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Logging
[TestCase("", 102)]
[TestCase("Has(@Exception)", 1)]
[TestCase("Has(@x)", 1)]
[TestCase("Has(Duration) and Duration > 1000", 2)]
[TestCase("Not(@Level = 'Verbose') and Not(@Level= 'Debug')", 45)]
[TestCase("Not(@Level = 'Verbose') and Not(@Level = 'Debug')", 45)]
[TestCase("Not(@l = 'Verbose') and Not(@l = 'Debug')", 45)]
[TestCase("StartsWith(SourceContext, 'Umbraco.Core')", 86)]
[TestCase("@MessageTemplate = '{EndMessage} ({Duration}ms) [Timing {TimingId}]'", 26)]
[TestCase("@mt = '{EndMessage} ({Duration}ms) [Timing {TimingId}]'", 26)]
[TestCase("SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'", 1)]
[TestCase("Contains(SortedComponentTypes[?], 'DatabaseServer')", 1)]
[Test]