V10: fix build warnings infrastructure (#12369)
* Run code cleanup * Run dotnet format * Start manual fixes * Manual fixing of warnings * Fix nullability in columnalias * Fix tests * Fix up after merge * Start updating after review * Update editorconfig to contain new static & const rules * Fix up editorconfig to not contain duplicate rules * Fix up static member names * Fix up according to review * Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Examine/PublishedContentIndexPopulator.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Extensions/InstanceIdentifiableExtensions.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_0_0/AddMemberPropertiesAsColumns.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/AccessMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/AuditEntryMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/MediaMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/MemberMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/NPocoMapperCollectionBuilder.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Update src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Fix [..] to substring * Fix after merge with 10/dev * Fox ContentValueSetValidator.cs * Update LoggerConfigExtensions Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk> Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
@@ -1,52 +1,53 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Parsing;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging
|
||||
namespace Umbraco.Cms.Core.Logging;
|
||||
|
||||
public class MessageTemplates : IMessageTemplates
|
||||
{
|
||||
public class MessageTemplates : IMessageTemplates
|
||||
// Umbraco now uses Message Templates (https://messagetemplates.org/) for logging, which means
|
||||
// we cannot plainly use string.Format() to format them. There is a work-in-progress C# lib,
|
||||
// derived from Serilog, which should help (https://github.com/messagetemplates/messagetemplates-csharp)
|
||||
// but it only has a pre-release NuGet package. So, we've got to use Serilog's code, which
|
||||
// means we cannot get rid of Serilog entirely. We may want to revisit this at some point.
|
||||
|
||||
// TODO: Do we still need this, is there a non-pre release package shipped?
|
||||
private static readonly Lazy<ILogger> _minimalLogger = new(() => new LoggerConfiguration().CreateLogger());
|
||||
|
||||
public string Render(string messageTemplate, params object[] args)
|
||||
{
|
||||
// Umbraco now uses Message Templates (https://messagetemplates.org/) for logging, which means
|
||||
// we cannot plainly use string.Format() to format them. There is a work-in-progress C# lib,
|
||||
// derived from Serilog, which should help (https://github.com/messagetemplates/messagetemplates-csharp)
|
||||
// but it only has a pre-release NuGet package. So, we've got to use Serilog's code, which
|
||||
// means we cannot get rid of Serilog entirely. We may want to revisit this at some point.
|
||||
// resolve a minimal logger instance which is used to bind message templates
|
||||
ILogger logger = _minimalLogger.Value;
|
||||
|
||||
// TODO: Do we still need this, is there a non-pre release package shipped?
|
||||
var bound = logger.BindMessageTemplate(messageTemplate, args, out MessageTemplate? parsedTemplate, out IEnumerable<LogEventProperty>? boundProperties);
|
||||
|
||||
private static readonly Lazy<global::Serilog.ILogger> MinimalLogger = new Lazy<global::Serilog.ILogger>(() => new LoggerConfiguration().CreateLogger());
|
||||
|
||||
public string Render(string messageTemplate, params object[] args)
|
||||
if (!bound)
|
||||
{
|
||||
// resolve a minimal logger instance which is used to bind message templates
|
||||
var logger = MinimalLogger.Value;
|
||||
|
||||
var bound = logger.BindMessageTemplate(messageTemplate, args, out var parsedTemplate, out var boundProperties);
|
||||
|
||||
if (!bound)
|
||||
throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args.");
|
||||
|
||||
var values = boundProperties.ToDictionary(x => x.Name, x => x.Value);
|
||||
|
||||
// this ends up putting every string parameter between quotes
|
||||
//return parsedTemplate.Render(values);
|
||||
|
||||
// this does not
|
||||
var tw = new StringWriter();
|
||||
foreach (var t in parsedTemplate.Tokens)
|
||||
{
|
||||
if (t is PropertyToken pt &&
|
||||
values.TryGetValue(pt.PropertyName, out var propVal) &&
|
||||
(propVal as ScalarValue)?.Value is string s)
|
||||
tw.Write(s);
|
||||
else
|
||||
t.Render(values, tw);
|
||||
}
|
||||
return tw.ToString();
|
||||
throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args.");
|
||||
}
|
||||
|
||||
var values = boundProperties.ToDictionary(x => x.Name, x => x.Value);
|
||||
|
||||
// this ends up putting every string parameter between quotes
|
||||
// return parsedTemplate.Render(values);
|
||||
|
||||
// this does not
|
||||
var tw = new StringWriter();
|
||||
foreach (MessageTemplateToken? t in parsedTemplate.Tokens)
|
||||
{
|
||||
if (t is PropertyToken pt &&
|
||||
values.TryGetValue(pt.PropertyName, out LogEventPropertyValue? propVal) &&
|
||||
(propVal as ScalarValue)?.Value is string s)
|
||||
{
|
||||
tw.Write(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
t.Render(values, tw);
|
||||
}
|
||||
}
|
||||
|
||||
return tw.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,46 @@
|
||||
using System;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
|
||||
/// <summary>
|
||||
/// Enrich log events with a HttpRequestId GUID.
|
||||
/// Original source -
|
||||
/// https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers and extra bits we do not want
|
||||
/// </summary>
|
||||
public class HttpRequestIdEnricher : ILogEventEnricher
|
||||
{
|
||||
/// <summary>
|
||||
/// Enrich log events with a HttpRequestId GUID.
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers and extra bits we do not want
|
||||
/// The property name added to enriched log events.
|
||||
/// </summary>
|
||||
public class HttpRequestIdEnricher : ILogEventEnricher
|
||||
public const string HttpRequestIdPropertyName = "HttpRequestId";
|
||||
|
||||
private readonly IRequestCache _requestCache;
|
||||
|
||||
public HttpRequestIdEnricher(IRequestCache requestCache) =>
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
|
||||
/// <summary>
|
||||
/// Enrich the log event with an id assigned to the currently-executing HTTP request, if any.
|
||||
/// </summary>
|
||||
/// <param name="logEvent">The log event to enrich.</param>
|
||||
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
private readonly IRequestCache _requestCache;
|
||||
|
||||
public HttpRequestIdEnricher(IRequestCache requestCache)
|
||||
if (logEvent == null)
|
||||
{
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
throw new ArgumentNullException(nameof(logEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property name added to enriched log events.
|
||||
/// </summary>
|
||||
public const string HttpRequestIdPropertyName = "HttpRequestId";
|
||||
|
||||
/// <summary>
|
||||
/// Enrich the log event with an id assigned to the currently-executing HTTP request, if any.
|
||||
/// </summary>
|
||||
/// <param name="logEvent">The log event to enrich.</param>
|
||||
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
if (!LogHttpRequest.TryGetCurrentHttpRequestId(out Guid? requestId, _requestCache))
|
||||
{
|
||||
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
|
||||
|
||||
Guid? requestId;
|
||||
if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId, _requestCache))
|
||||
return;
|
||||
|
||||
var requestIdProperty = new LogEventProperty(HttpRequestIdPropertyName, new ScalarValue(requestId));
|
||||
logEvent.AddPropertyIfAbsent(requestIdProperty);
|
||||
return;
|
||||
}
|
||||
|
||||
var requestIdProperty = new LogEventProperty(HttpRequestIdPropertyName, new ScalarValue(requestId));
|
||||
logEvent.AddPropertyIfAbsent(requestIdProperty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
|
||||
/// <summary>
|
||||
/// Enrich log events with a HttpRequestNumber unique within the current
|
||||
/// logging session.
|
||||
/// Original source -
|
||||
/// https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestNumberEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers and extra bits we do not want
|
||||
/// </summary>
|
||||
public class HttpRequestNumberEnricher : ILogEventEnricher
|
||||
{
|
||||
/// <summary>
|
||||
/// Enrich log events with a HttpRequestNumber unique within the current
|
||||
/// logging session.
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestNumberEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers and extra bits we do not want
|
||||
/// The property name added to enriched log events.
|
||||
/// </summary>
|
||||
public class HttpRequestNumberEnricher : ILogEventEnricher
|
||||
private const string HttpRequestNumberPropertyName = "HttpRequestNumber";
|
||||
private static readonly string _requestNumberItemName = typeof(HttpRequestNumberEnricher).Name + "+RequestNumber";
|
||||
|
||||
private static int _lastRequestNumber;
|
||||
private readonly IRequestCache _requestCache;
|
||||
|
||||
public HttpRequestNumberEnricher(IRequestCache requestCache) =>
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
|
||||
/// <summary>
|
||||
/// Enrich the log event with the number assigned to the currently-executing HTTP request, if any.
|
||||
/// </summary>
|
||||
/// <param name="logEvent">The log event to enrich.</param>
|
||||
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
private readonly IRequestCache _requestCache;
|
||||
private static int _lastRequestNumber;
|
||||
private static readonly string _requestNumberItemName = typeof(HttpRequestNumberEnricher).Name + "+RequestNumber";
|
||||
|
||||
/// <summary>
|
||||
/// The property name added to enriched log events.
|
||||
/// </summary>
|
||||
private const string _httpRequestNumberPropertyName = "HttpRequestNumber";
|
||||
|
||||
|
||||
public HttpRequestNumberEnricher(IRequestCache requestCache)
|
||||
if (logEvent == null)
|
||||
{
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
throw new ArgumentNullException(nameof(logEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enrich the log event with the number assigned to the currently-executing HTTP request, if any.
|
||||
/// </summary>
|
||||
/// <param name="logEvent">The log event to enrich.</param>
|
||||
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
|
||||
var requestNumber = _requestCache.Get(
|
||||
_requestNumberItemName,
|
||||
() => Interlocked.Increment(ref _lastRequestNumber));
|
||||
|
||||
var requestNumber = _requestCache.Get(_requestNumberItemName,
|
||||
() => Interlocked.Increment(ref _lastRequestNumber));
|
||||
|
||||
var requestNumberProperty = new LogEventProperty(_httpRequestNumberPropertyName, new ScalarValue(requestNumber));
|
||||
logEvent.AddPropertyIfAbsent(requestNumberProperty);
|
||||
}
|
||||
var requestNumberProperty =
|
||||
new LogEventProperty(HttpRequestNumberPropertyName, new ScalarValue(requestNumber));
|
||||
logEvent.AddPropertyIfAbsent(requestNumberProperty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
using System;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Net;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
|
||||
/// <summary>
|
||||
/// Enrich log events with the HttpSessionId property.
|
||||
/// Original source -
|
||||
/// https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpSessionIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers and extra bits we do not want
|
||||
/// </summary>
|
||||
public class HttpSessionIdEnricher : ILogEventEnricher
|
||||
{
|
||||
/// <summary>
|
||||
/// Enrich log events with the HttpSessionId property.
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpSessionIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers and extra bits we do not want
|
||||
/// The property name added to enriched log events.
|
||||
/// </summary>
|
||||
public class HttpSessionIdEnricher : ILogEventEnricher
|
||||
public const string HttpSessionIdPropertyName = "HttpSessionId";
|
||||
|
||||
private readonly ISessionIdResolver _sessionIdResolver;
|
||||
|
||||
public HttpSessionIdEnricher(ISessionIdResolver sessionIdResolver) => _sessionIdResolver = sessionIdResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Enrich the log event with the current ASP.NET session id, if sessions are enabled.
|
||||
/// </summary>
|
||||
/// <param name="logEvent">The log event to enrich.</param>
|
||||
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
private readonly ISessionIdResolver _sessionIdResolver;
|
||||
|
||||
public HttpSessionIdEnricher(ISessionIdResolver sessionIdResolver)
|
||||
if (logEvent == null)
|
||||
{
|
||||
_sessionIdResolver = sessionIdResolver;
|
||||
throw new ArgumentNullException(nameof(logEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property name added to enriched log events.
|
||||
/// </summary>
|
||||
public const string HttpSessionIdPropertyName = "HttpSessionId";
|
||||
|
||||
/// <summary>
|
||||
/// Enrich the log event with the current ASP.NET session id, if sessions are enabled.</summary>
|
||||
/// <param name="logEvent">The log event to enrich.</param>
|
||||
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
var sessionId = _sessionIdResolver.SessionId;
|
||||
if (sessionId is null)
|
||||
{
|
||||
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
|
||||
|
||||
var sessionId = _sessionIdResolver.SessionId;
|
||||
if (sessionId is null)
|
||||
return;
|
||||
|
||||
var sessionIdProperty = new LogEventProperty(HttpSessionIdPropertyName, new ScalarValue(sessionId));
|
||||
logEvent.AddPropertyIfAbsent(sessionIdProperty);
|
||||
return;
|
||||
}
|
||||
|
||||
var sessionIdProperty = new LogEventProperty(HttpSessionIdPropertyName, new ScalarValue(sessionId));
|
||||
logEvent.AddPropertyIfAbsent(sessionIdProperty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,51 @@
|
||||
using Serilog.Core;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to create a new property in Logs called 'Log4NetLevel'
|
||||
/// So that we can map Serilog levels to Log4Net levels - so log files stay consistent
|
||||
/// </summary>
|
||||
internal class Log4NetLevelMapperEnricher : ILogEventEnricher
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used to create a new property in Logs called 'Log4NetLevel'
|
||||
/// So that we can map Serilog levels to Log4Net levels - so log files stay consistent
|
||||
/// </summary>
|
||||
internal class Log4NetLevelMapperEnricher : ILogEventEnricher
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
string log4NetLevel;
|
||||
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
string log4NetLevel;
|
||||
case LogEventLevel.Debug:
|
||||
log4NetLevel = "DEBUG";
|
||||
break;
|
||||
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
case LogEventLevel.Debug:
|
||||
log4NetLevel = "DEBUG";
|
||||
break;
|
||||
case LogEventLevel.Error:
|
||||
log4NetLevel = "ERROR";
|
||||
break;
|
||||
|
||||
case LogEventLevel.Error:
|
||||
log4NetLevel = "ERROR";
|
||||
break;
|
||||
case LogEventLevel.Fatal:
|
||||
log4NetLevel = "FATAL";
|
||||
break;
|
||||
|
||||
case LogEventLevel.Fatal:
|
||||
log4NetLevel = "FATAL";
|
||||
break;
|
||||
case LogEventLevel.Information:
|
||||
log4NetLevel =
|
||||
"INFO "; // Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely)
|
||||
break;
|
||||
|
||||
case LogEventLevel.Information:
|
||||
log4NetLevel = "INFO "; //Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely)
|
||||
break;
|
||||
case LogEventLevel.Verbose:
|
||||
log4NetLevel =
|
||||
"ALL "; // Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely)
|
||||
break;
|
||||
|
||||
case LogEventLevel.Verbose:
|
||||
log4NetLevel = "ALL "; //Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely)
|
||||
break;
|
||||
|
||||
case LogEventLevel.Warning:
|
||||
log4NetLevel = "WARN "; //Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely)
|
||||
break;
|
||||
default:
|
||||
log4NetLevel = string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Log4NetLevel", log4NetLevel));
|
||||
case LogEventLevel.Warning:
|
||||
log4NetLevel =
|
||||
"WARN "; // Padded string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely)
|
||||
break;
|
||||
default:
|
||||
log4NetLevel = string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Log4NetLevel", log4NetLevel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +1,117 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Diagnostics;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using CoreDebugSettings = Umbraco.Cms.Core.Configuration.Models.CoreDebugSettings;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
|
||||
/// <summary>
|
||||
/// Enriches the log if there are ThreadAbort exceptions and will automatically create a minidump if it can
|
||||
/// </summary>
|
||||
public class ThreadAbortExceptionEnricher : ILogEventEnricher
|
||||
{
|
||||
/// <summary>
|
||||
/// Enriches the log if there are ThreadAbort exceptions and will automatically create a minidump if it can
|
||||
/// </summary>
|
||||
public class ThreadAbortExceptionEnricher : ILogEventEnricher
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IMarchal _marchal;
|
||||
private CoreDebugSettings _coreDebugSettings;
|
||||
|
||||
public ThreadAbortExceptionEnricher(
|
||||
IOptionsMonitor<CoreDebugSettings> coreDebugSettings,
|
||||
IHostingEnvironment hostingEnvironment, IMarchal marchal)
|
||||
{
|
||||
private CoreDebugSettings _coreDebugSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IMarchal _marchal;
|
||||
_coreDebugSettings = coreDebugSettings.CurrentValue;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_marchal = marchal;
|
||||
coreDebugSettings.OnChange(x => _coreDebugSettings = x);
|
||||
}
|
||||
|
||||
public ThreadAbortExceptionEnricher(IOptionsMonitor<CoreDebugSettings> coreDebugSettings, IHostingEnvironment hostingEnvironment, IMarchal marchal)
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
_coreDebugSettings = coreDebugSettings.CurrentValue;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_marchal = marchal;
|
||||
coreDebugSettings.OnChange(x => _coreDebugSettings = x);
|
||||
case LogEventLevel.Error:
|
||||
case LogEventLevel.Fatal:
|
||||
DumpThreadAborts(logEvent, propertyFactory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTimeoutThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
if (abort.ExceptionState == null)
|
||||
{
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
case LogEventLevel.Error:
|
||||
case LogEventLevel.Fatal:
|
||||
DumpThreadAborts(logEvent, propertyFactory);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DumpThreadAborts(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
Type stateType = abort.ExceptionState.GetType();
|
||||
if (stateType.FullName != "System.Web.HttpApplication+CancelModuleException")
|
||||
{
|
||||
if (!IsTimeoutThreadAbortException(logEvent.Exception)) return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var message = "The thread has been aborted, because the request has timed out.";
|
||||
FieldInfo? timeoutField = stateType.GetField("_timeout", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (timeoutField == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// dump if configured, or if stacktrace contains Monitor.ReliableEnter
|
||||
var dump = _coreDebugSettings.DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(logEvent.Exception);
|
||||
return (bool?)timeoutField.GetValue(abort.ExceptionState) ?? false;
|
||||
}
|
||||
|
||||
// dump if it is ok to dump (might have a cap on number of dump...)
|
||||
dump &= MiniDump.OkToDump(_hostingEnvironment);
|
||||
private void DumpThreadAborts(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (!IsTimeoutThreadAbortException(logEvent.Exception))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dump)
|
||||
var message = "The thread has been aborted, because the request has timed out.";
|
||||
|
||||
// dump if configured, or if stacktrace contains Monitor.ReliableEnter
|
||||
var dump = _coreDebugSettings.DumpOnTimeoutThreadAbort ||
|
||||
IsMonitorEnterThreadAbortException(logEvent.Exception);
|
||||
|
||||
// dump if it is ok to dump (might have a cap on number of dump...)
|
||||
dump &= MiniDump.OkToDump(_hostingEnvironment);
|
||||
|
||||
if (!dump)
|
||||
{
|
||||
message += ". No minidump was created.";
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
message += ". No minidump was created.";
|
||||
var dumped = MiniDump.Dump(_marchal, _hostingEnvironment, withException: true);
|
||||
message += dumped
|
||||
? ". A minidump was created in App_Data/MiniDump."
|
||||
: ". Failed to create a minidump.";
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dumped = MiniDump.Dump(_marchal, _hostingEnvironment, withException: true);
|
||||
message += dumped
|
||||
? ". A minidump was created in App_Data/MiniDump."
|
||||
: ". Failed to create a minidump.";
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
message = "Failed to create a minidump. " + ex;
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
message = "Failed to create a minidump. " + ex;
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTimeoutThreadAbortException(Exception exception)
|
||||
private static bool IsMonitorEnterThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort))
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
if (abort.ExceptionState == null) return false;
|
||||
|
||||
var stateType = abort.ExceptionState.GetType();
|
||||
if (stateType.FullName != "System.Web.HttpApplication+CancelModuleException") return false;
|
||||
|
||||
var timeoutField = stateType.GetField("_timeout", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (timeoutField == null) return false;
|
||||
|
||||
return (bool?)timeoutField.GetValue(abort.ExceptionState) ?? false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsMonitorEnterThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
|
||||
var stacktrace = abort.StackTrace;
|
||||
return stacktrace?.Contains("System.Threading.Monitor.ReliableEnter") ?? false;
|
||||
}
|
||||
|
||||
|
||||
var stacktrace = abort.StackTrace;
|
||||
return stacktrace?.Contains("System.Threading.Monitor.ReliableEnter") ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -46,7 +44,7 @@ namespace Umbraco.Extensions
|
||||
IConfiguration configuration,
|
||||
out UmbracoFileConfiguration umbFileConfiguration)
|
||||
{
|
||||
global::Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
|
||||
Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
|
||||
|
||||
//Set this environment variable - so that it can be used in external config file
|
||||
//add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" />
|
||||
@@ -75,8 +73,7 @@ namespace Umbraco.Extensions
|
||||
rollingInterval: umbracoFileConfiguration.RollingInterval,
|
||||
flushToDiskInterval: umbracoFileConfiguration.FlushToDiskInterval,
|
||||
rollOnFileSizeLimit: umbracoFileConfiguration.RollOnFileSizeLimit,
|
||||
retainedFileCountLimit: umbracoFileConfiguration.RetainedFileCountLimit
|
||||
);
|
||||
retainedFileCountLimit: umbracoFileConfiguration.RetainedFileCountLimit);
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
@@ -93,7 +90,7 @@ namespace Umbraco.Extensions
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
UmbracoFileConfiguration umbracoFileConfiguration)
|
||||
{
|
||||
global::Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
|
||||
Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
|
||||
|
||||
//Set this environment variable - so that it can be used in external config file
|
||||
//add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" />
|
||||
@@ -117,8 +114,7 @@ namespace Umbraco.Extensions
|
||||
rollingInterval: umbracoFileConfiguration.RollingInterval,
|
||||
flushToDiskInterval: umbracoFileConfiguration.FlushToDiskInterval,
|
||||
rollOnFileSizeLimit: umbracoFileConfiguration.RollOnFileSizeLimit,
|
||||
retainedFileCountLimit: umbracoFileConfiguration.RetainedFileCountLimit
|
||||
);
|
||||
retainedFileCountLimit: umbracoFileConfiguration.RetainedFileCountLimit);
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
@@ -137,7 +133,8 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
//Main .txt logfile - in similar format to older Log4Net output
|
||||
//Ends with ..txt as Date is inserted before file extension substring
|
||||
logConfig.WriteTo.File(Path.Combine(hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles), $"UmbracoTraceLog.{Environment.MachineName}..txt"),
|
||||
logConfig.WriteTo.File(
|
||||
Path.Combine(hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles), $"UmbracoTraceLog.{Environment.MachineName}..txt"),
|
||||
shared: true,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
restrictedToMinimumLevel: minimumLevel,
|
||||
@@ -162,8 +159,7 @@ namespace Umbraco.Extensions
|
||||
RollingInterval rollingInterval = RollingInterval.Day,
|
||||
bool rollOnFileSizeLimit = false,
|
||||
int? retainedFileCountLimit = 31,
|
||||
Encoding? encoding = null
|
||||
)
|
||||
Encoding? encoding = null)
|
||||
{
|
||||
formatter ??= new CompactJsonFormatter();
|
||||
|
||||
@@ -197,15 +193,19 @@ namespace Umbraco.Extensions
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
/// <param name="loggingConfiguration">The logging configuration</param>
|
||||
/// <param name="minimumLevel">The log level you wish the JSON file to collect - default is Verbose (highest)</param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="retainedFileCount">The number of days to keep log files. Default is set to null which means all logs are kept</param>
|
||||
public static LoggerConfiguration OutputDefaultJsonFile(
|
||||
this LoggerConfiguration logConfig,
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
LogEventLevel minimumLevel = LogEventLevel.Verbose,
|
||||
int? retainedFileCount = null)
|
||||
{
|
||||
// .clef format (Compact log event format, that can be imported into local SEQ & will make searching/filtering logs easier)
|
||||
// Ends with ..txt as Date is inserted before file extension substring
|
||||
logConfig.WriteTo.File(new CompactJsonFormatter(),
|
||||
logConfig.WriteTo.File(
|
||||
new CompactJsonFormatter(),
|
||||
Path.Combine(hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles) ,$"UmbracoTraceLog.{Environment.MachineName}..json"),
|
||||
shared: true,
|
||||
rollingInterval: RollingInterval.Day, // Create a new JSON file every day
|
||||
|
||||
@@ -1,219 +1,155 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Infrastructure.Logging.Serilog;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog
|
||||
namespace Umbraco.Cms.Core.Logging.Serilog;
|
||||
|
||||
/// <summary>
|
||||
/// Implements MS ILogger on top of Serilog.
|
||||
/// </summary>
|
||||
public class SerilogLogger : IDisposable
|
||||
{
|
||||
///<summary>
|
||||
/// Implements MS ILogger on top of Serilog.
|
||||
///</summary>
|
||||
public class SerilogLogger : IDisposable
|
||||
public SerilogLogger(LoggerConfiguration logConfig) =>
|
||||
|
||||
// Configure Serilog static global logger with config passed in
|
||||
SerilogLog = logConfig.CreateLogger();
|
||||
|
||||
public ILogger SerilogLog { get; }
|
||||
|
||||
[Obsolete]
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration) =>
|
||||
CreateWithDefaultConfiguration(hostingEnvironment, loggingConfiguration, configuration, out _);
|
||||
|
||||
public void Dispose() => SerilogLog.DisposeIfDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a logger with some pre-defined configuration and remainder from config file
|
||||
/// </summary>
|
||||
/// <remarks>Used by UmbracoApplicationBase to get its logger.</remarks>
|
||||
[Obsolete]
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration,
|
||||
out UmbracoFileConfiguration umbracoFileConfig)
|
||||
{
|
||||
public global::Serilog.ILogger SerilogLog { get; }
|
||||
|
||||
public SerilogLogger(LoggerConfiguration logConfig)
|
||||
{
|
||||
//Configure Serilog static global logger with config passed in
|
||||
SerilogLog = logConfig.CreateLogger();
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
return CreateWithDefaultConfiguration(hostingEnvironment, loggingConfiguration, configuration, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a logger with some pre-defined configuration and remainder from config file
|
||||
/// </summary>
|
||||
/// <remarks>Used by UmbracoApplicationBase to get its logger.</remarks>
|
||||
[Obsolete]
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration,
|
||||
out UmbracoFileConfiguration umbracoFileConfig)
|
||||
{
|
||||
var serilogConfig = new LoggerConfiguration()
|
||||
.MinimalConfiguration(hostingEnvironment, loggingConfiguration, configuration, out umbracoFileConfig)
|
||||
.ReadFrom.Configuration(configuration);
|
||||
|
||||
return new SerilogLogger(serilogConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a contextualized logger.
|
||||
/// </summary>
|
||||
private global::Serilog.ILogger LoggerFor(Type reporting)
|
||||
=> SerilogLog.ForContext(reporting);
|
||||
|
||||
/// <summary>
|
||||
/// Maps Umbraco's log level to Serilog's.
|
||||
/// </summary>
|
||||
private LogEventLevel MapLevel(LogLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case LogLevel.Debug:
|
||||
return LogEventLevel.Debug;
|
||||
case LogLevel.Error:
|
||||
return LogEventLevel.Error;
|
||||
case LogLevel.Fatal:
|
||||
return LogEventLevel.Fatal;
|
||||
case LogLevel.Information:
|
||||
return LogEventLevel.Information;
|
||||
case LogLevel.Verbose:
|
||||
return LogEventLevel.Verbose;
|
||||
case LogLevel.Warning:
|
||||
return LogEventLevel.Warning;
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"LogLevel \"{level}\" is not supported.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsEnabled(Type reporting, LogLevel level)
|
||||
=> LoggerFor(reporting).IsEnabled(MapLevel(level));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, Exception exception, string message)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, Exception exception)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
var message = "Exception.";
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, string message)
|
||||
{
|
||||
LoggerFor(reporting).Fatal(message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
LoggerFor(reporting).Fatal(messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Error(Type reporting, Exception exception, string message)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Error(Type reporting, Exception exception)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
var message = "Exception";
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Error(Type reporting, string message)
|
||||
{
|
||||
LoggerFor(reporting).Error(message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Error(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
LoggerFor(reporting).Error(messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Error(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warn(Type reporting, string message)
|
||||
{
|
||||
LoggerFor(reporting).Warning(message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warn(Type reporting, string message, params object[] propertyValues)
|
||||
{
|
||||
LoggerFor(reporting).Warning(message, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warn(Type reporting, Exception exception, string message)
|
||||
{
|
||||
LoggerFor(reporting).Warning(exception, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
LoggerFor(reporting).Warning(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Info(Type reporting, string message)
|
||||
{
|
||||
LoggerFor(reporting).Information(message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Info(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
LoggerFor(reporting).Information(messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Debug(Type reporting, string message)
|
||||
{
|
||||
LoggerFor(reporting).Debug(message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Debug(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
LoggerFor(reporting).Debug(messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Verbose(Type reporting, string message)
|
||||
{
|
||||
LoggerFor(reporting).Verbose(message);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
LoggerFor(reporting).Verbose(messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SerilogLog.DisposeIfDisposable();
|
||||
}
|
||||
LoggerConfiguration? serilogConfig = new LoggerConfiguration()
|
||||
.MinimalConfiguration(hostingEnvironment, loggingConfiguration, configuration, out umbracoFileConfig)
|
||||
.ReadFrom.Configuration(configuration);
|
||||
|
||||
return new SerilogLogger(serilogConfig);
|
||||
}
|
||||
|
||||
public bool IsEnabled(Type reporting, LogLevel level)
|
||||
=> LoggerFor(reporting).IsEnabled(MapLevel(level));
|
||||
|
||||
/// <summary>
|
||||
/// Gets a contextualized logger.
|
||||
/// </summary>
|
||||
private ILogger LoggerFor(Type reporting)
|
||||
=> SerilogLog.ForContext(reporting);
|
||||
|
||||
/// <summary>
|
||||
/// Maps Umbraco's log level to Serilog's.
|
||||
/// </summary>
|
||||
private LogEventLevel MapLevel(LogLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case LogLevel.Debug:
|
||||
return LogEventLevel.Debug;
|
||||
case LogLevel.Error:
|
||||
return LogEventLevel.Error;
|
||||
case LogLevel.Fatal:
|
||||
return LogEventLevel.Fatal;
|
||||
case LogLevel.Information:
|
||||
return LogEventLevel.Information;
|
||||
case LogLevel.Verbose:
|
||||
return LogEventLevel.Verbose;
|
||||
case LogLevel.Warning:
|
||||
return LogEventLevel.Warning;
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"LogLevel \"{level}\" is not supported.");
|
||||
}
|
||||
|
||||
public void Fatal(Type reporting, Exception exception, string message)
|
||||
{
|
||||
ILogger logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
public void Fatal(Type reporting, Exception exception)
|
||||
{
|
||||
ILogger logger = LoggerFor(reporting);
|
||||
var message = "Exception.";
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
public void Fatal(Type reporting, string message) => LoggerFor(reporting).Fatal(message);
|
||||
|
||||
public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues) =>
|
||||
LoggerFor(reporting).Fatal(messageTemplate, propertyValues);
|
||||
|
||||
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
ILogger logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
public void Error(Type reporting, Exception exception, string message)
|
||||
{
|
||||
ILogger logger = LoggerFor(reporting);
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
public void Error(Type reporting, Exception exception)
|
||||
{
|
||||
ILogger logger = LoggerFor(reporting);
|
||||
var message = "Exception";
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
public void Error(Type reporting, string message) => LoggerFor(reporting).Error(message);
|
||||
|
||||
public void Error(Type reporting, string messageTemplate, params object[] propertyValues) =>
|
||||
LoggerFor(reporting).Error(messageTemplate, propertyValues);
|
||||
|
||||
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
ILogger logger = LoggerFor(reporting);
|
||||
logger.Error(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
public void Warn(Type reporting, string message) => LoggerFor(reporting).Warning(message);
|
||||
|
||||
public void Warn(Type reporting, string message, params object[] propertyValues) =>
|
||||
LoggerFor(reporting).Warning(message, propertyValues);
|
||||
|
||||
public void Warn(Type reporting, Exception exception, string message) =>
|
||||
LoggerFor(reporting).Warning(exception, message);
|
||||
|
||||
public void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues) =>
|
||||
LoggerFor(reporting).Warning(exception, messageTemplate, propertyValues);
|
||||
|
||||
public void Info(Type reporting, string message) => LoggerFor(reporting).Information(message);
|
||||
|
||||
public void Info(Type reporting, string messageTemplate, params object[] propertyValues) =>
|
||||
LoggerFor(reporting).Information(messageTemplate, propertyValues);
|
||||
|
||||
public void Debug(Type reporting, string message) => LoggerFor(reporting).Debug(message);
|
||||
|
||||
public void Debug(Type reporting, string messageTemplate, params object[] propertyValues) =>
|
||||
LoggerFor(reporting).Debug(messageTemplate, propertyValues);
|
||||
|
||||
public void Verbose(Type reporting, string message) => LoggerFor(reporting).Verbose(message);
|
||||
|
||||
public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues) =>
|
||||
LoggerFor(reporting).Verbose(messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Logging.Serilog
|
||||
namespace Umbraco.Cms.Infrastructure.Logging.Serilog;
|
||||
|
||||
public class UmbracoFileConfiguration
|
||||
{
|
||||
public class UmbracoFileConfiguration
|
||||
public UmbracoFileConfiguration(IConfiguration configuration)
|
||||
{
|
||||
public UmbracoFileConfiguration(IConfiguration configuration)
|
||||
if (configuration == null)
|
||||
{
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
var appSettings = configuration.GetSection("Serilog:WriteTo");
|
||||
var umbracoFileAppSettings = appSettings.GetChildren().LastOrDefault(x => x.GetValue<string>("Name") == "UmbracoFile");
|
||||
|
||||
if (umbracoFileAppSettings is not null)
|
||||
{
|
||||
var args = umbracoFileAppSettings.GetSection("Args");
|
||||
|
||||
RestrictedToMinimumLevel = args.GetValue(nameof(RestrictedToMinimumLevel), RestrictedToMinimumLevel);
|
||||
FileSizeLimitBytes = args.GetValue(nameof(FileSizeLimitBytes), FileSizeLimitBytes);
|
||||
RollingInterval = args.GetValue(nameof(RollingInterval), RollingInterval);
|
||||
FlushToDiskInterval = args.GetValue(nameof(FlushToDiskInterval), FlushToDiskInterval);
|
||||
RollOnFileSizeLimit = args.GetValue(nameof(RollOnFileSizeLimit), RollOnFileSizeLimit);
|
||||
RetainedFileCountLimit = args.GetValue(nameof(RetainedFileCountLimit), RetainedFileCountLimit);
|
||||
}
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
public LogEventLevel RestrictedToMinimumLevel { get; set; } = LogEventLevel.Verbose;
|
||||
public long FileSizeLimitBytes { get; set; } = 1073741824;
|
||||
public RollingInterval RollingInterval { get; set; } = RollingInterval.Day;
|
||||
public TimeSpan? FlushToDiskInterval { get; set; } = null;
|
||||
public bool RollOnFileSizeLimit { get; set; } = false;
|
||||
public int RetainedFileCountLimit { get; set; } = 31;
|
||||
IConfigurationSection? appSettings = configuration.GetSection("Serilog:WriteTo");
|
||||
IConfigurationSection? umbracoFileAppSettings =
|
||||
appSettings.GetChildren().LastOrDefault(x => x.GetValue<string>("Name") == "UmbracoFile");
|
||||
|
||||
public string GetPath(string logDirectory)
|
||||
if (umbracoFileAppSettings is not null)
|
||||
{
|
||||
return Path.Combine(logDirectory, $"UmbracoTraceLog.{Environment.MachineName}..json");
|
||||
IConfigurationSection? args = umbracoFileAppSettings.GetSection("Args");
|
||||
|
||||
RestrictedToMinimumLevel = args.GetValue(nameof(RestrictedToMinimumLevel), RestrictedToMinimumLevel);
|
||||
FileSizeLimitBytes = args.GetValue(nameof(FileSizeLimitBytes), FileSizeLimitBytes);
|
||||
RollingInterval = args.GetValue(nameof(RollingInterval), RollingInterval);
|
||||
FlushToDiskInterval = args.GetValue(nameof(FlushToDiskInterval), FlushToDiskInterval);
|
||||
RollOnFileSizeLimit = args.GetValue(nameof(RollOnFileSizeLimit), RollOnFileSizeLimit);
|
||||
RetainedFileCountLimit = args.GetValue(nameof(RetainedFileCountLimit), RetainedFileCountLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public LogEventLevel RestrictedToMinimumLevel { get; set; } = LogEventLevel.Verbose;
|
||||
|
||||
public long FileSizeLimitBytes { get; set; } = 1073741824;
|
||||
|
||||
public RollingInterval RollingInterval { get; set; } = RollingInterval.Day;
|
||||
|
||||
public TimeSpan? FlushToDiskInterval { get; set; }
|
||||
|
||||
public bool RollOnFileSizeLimit { get; set; }
|
||||
|
||||
public int RetainedFileCountLimit { get; set; } = 31;
|
||||
|
||||
public string GetPath(string logDirectory) =>
|
||||
Path.Combine(logDirectory, $"UmbracoTraceLog.{Environment.MachineName}..json");
|
||||
}
|
||||
|
||||
@@ -1,49 +1,43 @@
|
||||
using System;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
internal class CountingFilter : ILogFilter
|
||||
{
|
||||
internal class CountingFilter : ILogFilter
|
||||
public CountingFilter() => Counts = new LogLevelCounts();
|
||||
|
||||
public LogLevelCounts Counts { get; }
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
public CountingFilter()
|
||||
switch (e.Level)
|
||||
{
|
||||
Counts = new LogLevelCounts();
|
||||
case LogEventLevel.Debug:
|
||||
Counts.Debug++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Information:
|
||||
Counts.Information++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Warning:
|
||||
Counts.Warning++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Error:
|
||||
Counts.Error++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Fatal:
|
||||
Counts.Fatal++;
|
||||
break;
|
||||
case LogEventLevel.Verbose:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
public LogLevelCounts Counts { get; }
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
|
||||
switch (e.Level)
|
||||
{
|
||||
case LogEventLevel.Debug:
|
||||
Counts.Debug++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Information:
|
||||
Counts.Information++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Warning:
|
||||
Counts.Warning++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Error:
|
||||
Counts.Error++;
|
||||
break;
|
||||
|
||||
case LogEventLevel.Fatal:
|
||||
Counts.Fatal++;
|
||||
break;
|
||||
case LogEventLevel.Verbose:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
//Don't add it to the list
|
||||
return false;
|
||||
}
|
||||
// Don't add it to the list
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
using Serilog.Events;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
internal class ErrorCounterFilter : ILogFilter
|
||||
{
|
||||
internal class ErrorCounterFilter : ILogFilter
|
||||
public int Count { get; private set; }
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
if (e.Level == LogEventLevel.Fatal || e.Level == LogEventLevel.Error || e.Exception != null)
|
||||
{
|
||||
if (e.Level == LogEventLevel.Fatal || e.Level == LogEventLevel.Error || e.Exception != null)
|
||||
Count++;
|
||||
|
||||
//Don't add it to the list
|
||||
return false;
|
||||
Count++;
|
||||
}
|
||||
|
||||
// Don't add it to the list
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +1,75 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Serilog.Events;
|
||||
using Serilog.Expressions;
|
||||
using Umbraco.Cms.Infrastructure.Logging.Viewer;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
// Log Expression Filters (pass in filter exp string)
|
||||
internal class ExpressionFilter : ILogFilter
|
||||
{
|
||||
//Log Expression Filters (pass in filter exp string)
|
||||
internal class ExpressionFilter : ILogFilter
|
||||
private const string ExpressionOperators = "()+=*<>%-";
|
||||
private readonly Func<LogEvent, bool>? _filter;
|
||||
|
||||
public ExpressionFilter(string? filterExpression)
|
||||
{
|
||||
private readonly Func<LogEvent, bool>? _filter;
|
||||
private const string s_expressionOperators = "()+=*<>%-";
|
||||
Func<LogEvent, bool>? filter;
|
||||
|
||||
public ExpressionFilter(string? filterExpression)
|
||||
// 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))
|
||||
{
|
||||
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(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 (SerilogExpression.TryCompile(filterExpression, null, customSerilogFunctions, out CompiledExpression? compiled, out var error))
|
||||
{
|
||||
filter = evt =>
|
||||
{
|
||||
LogEventPropertyValue? result = compiled(evt);
|
||||
return ExpressionResult.IsTrue(result);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// '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);
|
||||
}
|
||||
}
|
||||
|
||||
_filter = filter;
|
||||
return;
|
||||
}
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
// 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)))
|
||||
{
|
||||
return _filter == null || _filter(e);
|
||||
filter = PerformMessageLikeFilter(filterExpression);
|
||||
}
|
||||
|
||||
private Func<LogEvent, bool>? PerformMessageLikeFilter(string filterExpression)
|
||||
// check if it's a valid expression
|
||||
else
|
||||
{
|
||||
var filterSearch = $"@Message like '%{SerilogExpression.EscapeLikeExpressionContent(filterExpression)}%'";
|
||||
if (SerilogExpression.TryCompile(filterSearch, out CompiledExpression? compiled, out var error))
|
||||
// If the expression evaluates then make it into a filter
|
||||
if (SerilogExpression.TryCompile(filterExpression, null, customSerilogFunctions, out CompiledExpression? compiled, out var error))
|
||||
{
|
||||
// `compiled` is a function that can be executed against `LogEvent`s:
|
||||
return evt =>
|
||||
filter = evt =>
|
||||
{
|
||||
LogEventPropertyValue? result = compiled(evt);
|
||||
return ExpressionResult.IsTrue(result);
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
else
|
||||
{
|
||||
// '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);
|
||||
}
|
||||
}
|
||||
|
||||
_filter = filter;
|
||||
}
|
||||
|
||||
public bool TakeLogEvent(LogEvent e) => _filter == null || _filter(e);
|
||||
|
||||
private Func<LogEvent, bool>? PerformMessageLikeFilter(string filterExpression)
|
||||
{
|
||||
var filterSearch = $"@Message like '%{SerilogExpression.EscapeLikeExpressionContent(filterExpression)}%'";
|
||||
if (SerilogExpression.TryCompile(filterSearch, out CompiledExpression? compiled, out var error))
|
||||
{
|
||||
// `compiled` is a function that can be executed against `LogEvent`s:
|
||||
return evt =>
|
||||
{
|
||||
LogEventPropertyValue? result = compiled(evt);
|
||||
return ExpressionResult.IsTrue(result);
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Serilog.Events;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public interface ILogFilter
|
||||
{
|
||||
public interface ILogFilter
|
||||
{
|
||||
bool TakeLogEvent(LogEvent e);
|
||||
}
|
||||
bool TakeLogEvent(LogEvent e);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
{
|
||||
public interface ILogLevelLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the Serilog level values of the global minimum and the UmbracoFile one from the config file.
|
||||
/// </summary>
|
||||
ReadOnlyDictionary<string, LogEventLevel?> GetLogLevelsFromSinks();
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
LogEventLevel? GetGlobalMinLogLevel();
|
||||
}
|
||||
public interface ILogLevelLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the Serilog level values of the global minimum and the UmbracoFile one from the config file.
|
||||
/// </summary>
|
||||
ReadOnlyDictionary<string, LogEventLevel?> GetLogLevelsFromSinks();
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
LogEventLevel? GetGlobalMinLogLevel();
|
||||
}
|
||||
|
||||
@@ -1,64 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public interface ILogViewer
|
||||
{
|
||||
public interface ILogViewer
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all saved searches from your chosen data source
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch>? GetSavedSearches();
|
||||
bool CanHandleLargeLogs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new saved search to chosen data source and returns the updated searches
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query);
|
||||
/// <summary>
|
||||
/// Get all saved searches from your chosen data source
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch>? GetSavedSearches();
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a saved search to chosen data source and returns the remaining searches
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query);
|
||||
/// <summary>
|
||||
/// Adds a new saved search to chosen data source and returns the updated searches
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query);
|
||||
|
||||
/// <summary>
|
||||
/// A count of number of errors
|
||||
/// By counting Warnings with Exceptions, Errors & Fatal messages
|
||||
/// </summary>
|
||||
int GetNumberOfErrors(LogTimePeriod logTimePeriod);
|
||||
/// <summary>
|
||||
/// Deletes a saved search to chosen data source and returns the remaining searches
|
||||
/// </summary>
|
||||
IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a number of the different log level entries
|
||||
/// </summary>
|
||||
LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod);
|
||||
/// <summary>
|
||||
/// A count of number of errors
|
||||
/// By counting Warnings with Exceptions, Errors & Fatal messages
|
||||
/// </summary>
|
||||
int GetNumberOfErrors(LogTimePeriod logTimePeriod);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all unique message templates and their counts
|
||||
/// </summary>
|
||||
IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod);
|
||||
/// <summary>
|
||||
/// Returns a number of the different log level entries
|
||||
/// </summary>
|
||||
LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod);
|
||||
|
||||
bool CanHandleLargeLogs { get; }
|
||||
/// <summary>
|
||||
/// Returns a list of all unique message templates and their counts
|
||||
/// </summary>
|
||||
IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod);
|
||||
|
||||
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Serilog minimum log level
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Please use GetLogLevels() instead. Scheduled for removal in V11.")]
|
||||
string GetLogLevel();
|
||||
/// <summary>
|
||||
/// Gets the current Serilog minimum log level
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Please use GetLogLevels() instead. Scheduled for removal in V11.")]
|
||||
string GetLogLevel();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the collection of logs
|
||||
/// </summary>
|
||||
PagedResult<LogMessage> GetLogs(LogTimePeriod logTimePeriod,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 100,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
string? filterExpression = null,
|
||||
string[]? logLevels = null);
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the collection of logs
|
||||
/// </summary>
|
||||
PagedResult<LogMessage> GetLogs(
|
||||
LogTimePeriod logTimePeriod,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 100,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
string? filterExpression = null,
|
||||
string[]? logLevels = null);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
public interface ILogViewerConfig
|
||||
{
|
||||
public interface ILogViewerConfig
|
||||
{
|
||||
IReadOnlyList<SavedLogSearch>? GetSavedSearches();
|
||||
IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query);
|
||||
IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query);
|
||||
}
|
||||
IReadOnlyList<SavedLogSearch>? GetSavedSearches();
|
||||
|
||||
IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query);
|
||||
|
||||
IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public class LogLevelCounts
|
||||
{
|
||||
public class LogLevelCounts
|
||||
{
|
||||
public int Information { get; set; }
|
||||
public int Information { get; set; }
|
||||
|
||||
public int Debug { get; set; }
|
||||
public int Debug { get; set; }
|
||||
|
||||
public int Warning { get; set; }
|
||||
public int Warning { get; set; }
|
||||
|
||||
public int Error { get; set; }
|
||||
public int Error { get; set; }
|
||||
|
||||
public int Fatal { get; set; }
|
||||
}
|
||||
public int Fatal { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,41 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Infrastructure.Logging.Serilog;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public class LogLevelLoader : ILogLevelLoader
|
||||
{
|
||||
public class LogLevelLoader : ILogLevelLoader
|
||||
private readonly UmbracoFileConfiguration _umbracoFileConfig;
|
||||
|
||||
public LogLevelLoader(UmbracoFileConfiguration umbracoFileConfig) => _umbracoFileConfig = umbracoFileConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog level values of the global minimum and the UmbracoFile one from the config file.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, LogEventLevel?> GetLogLevelsFromSinks()
|
||||
{
|
||||
private readonly UmbracoFileConfiguration _umbracoFileConfig;
|
||||
|
||||
public LogLevelLoader(UmbracoFileConfiguration umbracoFileConfig) => _umbracoFileConfig = umbracoFileConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog level values of the global minimum and the UmbracoFile one from the config file.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, LogEventLevel?> GetLogLevelsFromSinks()
|
||||
var configuredLogLevels = new Dictionary<string, LogEventLevel?>
|
||||
{
|
||||
var configuredLogLevels = new Dictionary<string, LogEventLevel?>
|
||||
{
|
||||
{ "Global", GetGlobalMinLogLevel() },
|
||||
{ "UmbracoFile", _umbracoFileConfig.RestrictedToMinimumLevel }
|
||||
};
|
||||
{ "Global", GetGlobalMinLogLevel() }, { "UmbracoFile", _umbracoFileConfig.RestrictedToMinimumLevel },
|
||||
};
|
||||
|
||||
return new ReadOnlyDictionary<string, LogEventLevel?>(configuredLogLevels);
|
||||
}
|
||||
return new ReadOnlyDictionary<string, LogEventLevel?>(configuredLogLevels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
public LogEventLevel? GetGlobalMinLogLevel()
|
||||
{
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.IsEnabled).DefaultIfEmpty(LogEventLevel.Information)?.Min() ?? null;
|
||||
return (LogEventLevel?)logLevel;
|
||||
}
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
public LogEventLevel? GetGlobalMinLogLevel()
|
||||
{
|
||||
LogEventLevel? logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.IsEnabled)
|
||||
.DefaultIfEmpty(LogEventLevel.Information).Min();
|
||||
return logLevel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,40 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public class LogMessage
|
||||
{
|
||||
public class LogMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the log event occurred.
|
||||
/// </summary>
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
/// <summary>
|
||||
/// The time at which the log event occurred.
|
||||
/// </summary>
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The level of the event.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LogEventLevel Level { get; set; }
|
||||
/// <summary>
|
||||
/// The level of the event.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LogEventLevel Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message template describing the log event.
|
||||
/// </summary>
|
||||
public string? MessageTemplateText { get; set; }
|
||||
/// <summary>
|
||||
/// The message template describing the log event.
|
||||
/// </summary>
|
||||
public string? MessageTemplateText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message template filled with the log event properties.
|
||||
/// </summary>
|
||||
public string? RenderedMessage { get; set; }
|
||||
/// <summary>
|
||||
/// The message template filled with the log event properties.
|
||||
/// </summary>
|
||||
public string? RenderedMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties associated with the log event, including those presented in Serilog.Events.LogEvent.MessageTemplate.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, LogEventPropertyValue>? Properties { get; set; }
|
||||
/// <summary>
|
||||
/// Properties associated with the log event, including those presented in Serilog.Events.LogEvent.MessageTemplate.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, LogEventPropertyValue>? Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An exception associated with the log event, or null.
|
||||
/// </summary>
|
||||
public string? Exception { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// An exception associated with the log event, or null.
|
||||
/// </summary>
|
||||
public string? Exception { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
{
|
||||
public class LogTemplate
|
||||
{
|
||||
public string? MessageTemplate { get; set; }
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public int Count { get; set; }
|
||||
}
|
||||
public class LogTemplate
|
||||
{
|
||||
public string? MessageTemplate { get; set; }
|
||||
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
using System;
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
public class LogTimePeriod
|
||||
{
|
||||
public class LogTimePeriod
|
||||
public LogTimePeriod(DateTime startTime, DateTime endTime)
|
||||
{
|
||||
public LogTimePeriod(DateTime startTime, DateTime endTime)
|
||||
{
|
||||
StartTime = startTime;
|
||||
EndTime = endTime;
|
||||
}
|
||||
|
||||
public DateTime StartTime { get; }
|
||||
public DateTime EndTime { get; }
|
||||
StartTime = startTime;
|
||||
EndTime = endTime;
|
||||
}
|
||||
|
||||
public DateTime StartTime { get; }
|
||||
|
||||
public DateTime EndTime { get; }
|
||||
}
|
||||
|
||||
@@ -1,49 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public class LogViewerConfig : ILogViewerConfig
|
||||
{
|
||||
public class LogViewerConfig : ILogViewerConfig
|
||||
private readonly ILogViewerQueryRepository _logViewerQueryRepository;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
|
||||
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, IScopeProvider scopeProvider)
|
||||
{
|
||||
private readonly ILogViewerQueryRepository _logViewerQueryRepository;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
_logViewerQueryRepository = logViewerQueryRepository;
|
||||
_scopeProvider = scopeProvider;
|
||||
}
|
||||
|
||||
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, IScopeProvider scopeProvider)
|
||||
public IReadOnlyList<SavedLogSearch>? GetSavedSearches()
|
||||
{
|
||||
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
IEnumerable<ILogViewerQuery>? logViewerQueries = _logViewerQueryRepository.GetMany();
|
||||
SavedLogSearch[]? result = logViewerQueries?.Select(x => new SavedLogSearch() { Name = x.Name, Query = x.Query }).ToArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query)
|
||||
{
|
||||
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
_logViewerQueryRepository.Save(new LogViewerQuery(name, query));
|
||||
|
||||
return GetSavedSearches();
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query)
|
||||
{
|
||||
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
ILogViewerQuery? item = name is null ? null : _logViewerQueryRepository.GetByName(name);
|
||||
if (item is not null)
|
||||
{
|
||||
_logViewerQueryRepository = logViewerQueryRepository;
|
||||
_scopeProvider = scopeProvider;
|
||||
_logViewerQueryRepository.Delete(item);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch>? GetSavedSearches()
|
||||
{
|
||||
using var scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
var logViewerQueries = _logViewerQueryRepository.GetMany();
|
||||
var result = logViewerQueries?.Select(x => new SavedLogSearch() { Name = x.Name, Query = x.Query }).ToArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query)
|
||||
{
|
||||
using var scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
_logViewerQueryRepository.Save(new LogViewerQuery(name, query));
|
||||
|
||||
return GetSavedSearches();
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query)
|
||||
{
|
||||
using var scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
var item = name is null ? null : _logViewerQueryRepository.GetByName(name);
|
||||
if (item is not null)
|
||||
{
|
||||
_logViewerQueryRepository.Delete(item);
|
||||
}
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return GetSavedSearches();
|
||||
}
|
||||
// Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return GetSavedSearches();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
internal class MessageTemplateFilter : ILogFilter
|
||||
{
|
||||
internal class MessageTemplateFilter : ILogFilter
|
||||
public readonly Dictionary<string, int> Counts = new();
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
{
|
||||
public readonly Dictionary<string, int> Counts = new Dictionary<string, int>();
|
||||
|
||||
public bool TakeLogEvent(LogEvent e)
|
||||
var templateText = e.MessageTemplate.Text;
|
||||
if (Counts.TryGetValue(templateText, out var count))
|
||||
{
|
||||
var templateText = e.MessageTemplate.Text;
|
||||
if (Counts.TryGetValue(templateText, out var count))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
Counts[templateText] = count;
|
||||
|
||||
//Don't add it to the list
|
||||
return false;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
Counts[templateText] = count;
|
||||
|
||||
// Don't add it to the list
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public class SavedLogSearch
|
||||
{
|
||||
public class SavedLogSearch
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string? Name { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonProperty("query")]
|
||||
public string? Query { get; set; }
|
||||
}
|
||||
[JsonProperty("query")]
|
||||
public string? Query { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Logging.Viewer
|
||||
namespace Umbraco.Cms.Infrastructure.Logging.Viewer;
|
||||
|
||||
public class SerilogExpressionsFunctions
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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) => new ScalarValue(value != null);
|
||||
}
|
||||
|
||||
@@ -1,140 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact.Reader;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
internal class SerilogJsonLogViewer : SerilogLogViewerSourceBase
|
||||
{
|
||||
internal class SerilogJsonLogViewer : SerilogLogViewerSourceBase
|
||||
private const int FileSizeCap = 100;
|
||||
private readonly ILogger<SerilogJsonLogViewer> _logger;
|
||||
private readonly string _logsPath;
|
||||
|
||||
public SerilogJsonLogViewer(
|
||||
ILogger<SerilogJsonLogViewer> logger,
|
||||
ILogViewerConfig logViewerConfig,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
ILogLevelLoader logLevelLoader,
|
||||
ILogger serilogLog)
|
||||
: base(logViewerConfig, logLevelLoader, serilogLog)
|
||||
{
|
||||
private readonly string _logsPath;
|
||||
private readonly ILogger<SerilogJsonLogViewer> _logger;
|
||||
_logger = logger;
|
||||
_logsPath = loggingConfiguration.LogDirectory;
|
||||
}
|
||||
|
||||
public SerilogJsonLogViewer(
|
||||
ILogger<SerilogJsonLogViewer> logger,
|
||||
ILogViewerConfig logViewerConfig,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
ILogLevelLoader logLevelLoader,
|
||||
global::Serilog.ILogger serilogLog)
|
||||
: base(logViewerConfig, logLevelLoader, serilogLog)
|
||||
public override bool CanHandleLargeLogs => false;
|
||||
|
||||
public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
// Log Directory
|
||||
var logDirectory = _logsPath;
|
||||
|
||||
// Number of entries
|
||||
long fileSizeCount = 0;
|
||||
|
||||
// foreach full day in the range - see if we can find one or more filenames that end with
|
||||
// yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
|
||||
for (DateTime day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1))
|
||||
{
|
||||
_logger = logger;
|
||||
_logsPath = loggingConfiguration.LogDirectory;
|
||||
// Filename ending to search for (As could be multiple)
|
||||
var filesToFind = GetSearchPattern(day);
|
||||
|
||||
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
|
||||
|
||||
fileSizeCount += filesForCurrentDay.Sum(x => new FileInfo(x).Length);
|
||||
}
|
||||
|
||||
private const int FileSizeCap = 100;
|
||||
// The GetLogSize call on JsonLogViewer returns the total file size in bytes
|
||||
// Check if the log size is not greater than 100Mb (FileSizeCap)
|
||||
var logSizeAsMegabytes = fileSizeCount / 1024 / 1024;
|
||||
return logSizeAsMegabytes <= FileSizeCap;
|
||||
}
|
||||
|
||||
public override bool CanHandleLargeLogs => false;
|
||||
protected override IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip,
|
||||
int take)
|
||||
{
|
||||
var logs = new List<LogEvent>();
|
||||
|
||||
public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod)
|
||||
var count = 0;
|
||||
|
||||
// foreach full day in the range - see if we can find one or more filenames that end with
|
||||
// yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
|
||||
for (DateTime day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1))
|
||||
{
|
||||
//Log Directory
|
||||
var logDirectory = _logsPath;
|
||||
// Filename ending to search for (As could be multiple)
|
||||
var filesToFind = GetSearchPattern(day);
|
||||
|
||||
//Number of entries
|
||||
long fileSizeCount = 0;
|
||||
var filesForCurrentDay = Directory.GetFiles(_logsPath, filesToFind);
|
||||
|
||||
//foreach full day in the range - see if we can find one or more filenames that end with
|
||||
//yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
|
||||
for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1))
|
||||
// Foreach file we find - open it
|
||||
foreach (var filePath in filesForCurrentDay)
|
||||
{
|
||||
//Filename ending to search for (As could be multiple)
|
||||
var filesToFind = GetSearchPattern(day);
|
||||
|
||||
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
|
||||
|
||||
fileSizeCount += filesForCurrentDay.Sum(x => new FileInfo(x).Length);
|
||||
}
|
||||
|
||||
//The GetLogSize call on JsonLogViewer returns the total file size in bytes
|
||||
//Check if the log size is not greater than 100Mb (FileSizeCap)
|
||||
var logSizeAsMegabytes = fileSizeCount / 1024 / 1024;
|
||||
return logSizeAsMegabytes <= FileSizeCap;
|
||||
}
|
||||
|
||||
private string GetSearchPattern(DateTime day)
|
||||
{
|
||||
return $"*{day:yyyyMMdd}*.json";
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take)
|
||||
{
|
||||
var logs = new List<LogEvent>();
|
||||
|
||||
var count = 0;
|
||||
|
||||
//foreach full day in the range - see if we can find one or more filenames that end with
|
||||
//yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
|
||||
for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1))
|
||||
{
|
||||
//Filename ending to search for (As could be multiple)
|
||||
var filesToFind = GetSearchPattern(day);
|
||||
|
||||
var filesForCurrentDay = Directory.GetFiles(_logsPath, filesToFind);
|
||||
|
||||
//Foreach file we find - open it
|
||||
foreach (var filePath in filesForCurrentDay)
|
||||
// Open log file & add contents to the log collection
|
||||
// Which we then use LINQ to page over
|
||||
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
//Open log file & add contents to the log collection
|
||||
//Which we then use LINQ to page over
|
||||
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var stream = new StreamReader(fs))
|
||||
{
|
||||
using (var stream = new StreamReader(fs))
|
||||
var reader = new LogEventReader(stream);
|
||||
while (TryRead(reader, out LogEvent? evt))
|
||||
{
|
||||
var reader = new LogEventReader(stream);
|
||||
while (TryRead(reader, out var evt))
|
||||
// We may get a null if log line is malformed
|
||||
if (evt == null)
|
||||
{
|
||||
//We may get a null if log line is malformed
|
||||
if (evt == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count > skip + take)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (count < skip)
|
||||
{
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filter.TakeLogEvent(evt))
|
||||
{
|
||||
logs.Add(evt);
|
||||
}
|
||||
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count > skip + take)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (count < skip)
|
||||
{
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filter.TakeLogEvent(evt))
|
||||
{
|
||||
logs.Add(evt);
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
private bool TryRead(LogEventReader reader, out LogEvent? evt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return reader.TryRead(out evt);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
// As we are reading/streaming one line at a time in the JSON file
|
||||
// Thus we can not report the line number, as it will always be 1
|
||||
_logger.LogError(ex, "Unable to parse a line in the JSON log file");
|
||||
return logs;
|
||||
}
|
||||
|
||||
evt = null;
|
||||
return true;
|
||||
}
|
||||
private string GetSearchPattern(DateTime day) => $"*{day:yyyyMMdd}*.json";
|
||||
|
||||
private bool TryRead(LogEventReader reader, out LogEvent? evt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return reader.TryRead(out evt);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
// As we are reading/streaming one line at a time in the JSON file
|
||||
// Thus we can not report the line number, as it will always be 1
|
||||
_logger.LogError(ex, "Unable to parse a line in the JSON log file");
|
||||
|
||||
evt = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Serilog.Expressions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Logging.Viewer
|
||||
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
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
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, [MaybeNullWhen(false)] out string target)
|
||||
/// <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, [MaybeNullWhen(false)] out string target)
|
||||
{
|
||||
target = alias switch
|
||||
{
|
||||
target = alias switch
|
||||
{
|
||||
"Exception" => "x",
|
||||
"Level" => "l",
|
||||
"Message" => "m",
|
||||
"MessageTemplate" => "mt",
|
||||
"Properties" => "p",
|
||||
"Timestamp" => "t",
|
||||
_ => null
|
||||
};
|
||||
"Exception" => "x",
|
||||
"Level" => "l",
|
||||
"Message" => "m",
|
||||
"MessageTemplate" => "mt",
|
||||
"Properties" => "p",
|
||||
"Timestamp" => "t",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return target != null;
|
||||
}
|
||||
return target != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,154 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
|
||||
public abstract class SerilogLogViewerSourceBase : ILogViewer
|
||||
{
|
||||
public abstract class SerilogLogViewerSourceBase : ILogViewer
|
||||
private readonly ILogLevelLoader _logLevelLoader;
|
||||
private readonly ILogViewerConfig _logViewerConfig;
|
||||
private readonly ILogger _serilogLog;
|
||||
|
||||
[Obsolete("Please use ctor with all params instead. Scheduled for removal in V11.")]
|
||||
protected SerilogLogViewerSourceBase(ILogViewerConfig logViewerConfig, ILogger serilogLog)
|
||||
{
|
||||
private readonly ILogViewerConfig _logViewerConfig;
|
||||
private readonly ILogLevelLoader _logLevelLoader;
|
||||
private readonly global::Serilog.ILogger _serilogLog;
|
||||
_logViewerConfig = logViewerConfig;
|
||||
_logLevelLoader = StaticServiceProvider.Instance.GetRequiredService<ILogLevelLoader>();
|
||||
_serilogLog = serilogLog;
|
||||
}
|
||||
|
||||
[Obsolete("Please use ctor with all params instead. Scheduled for removal in V11.")]
|
||||
protected SerilogLogViewerSourceBase(ILogViewerConfig logViewerConfig, global::Serilog.ILogger serilogLog)
|
||||
protected SerilogLogViewerSourceBase(ILogViewerConfig logViewerConfig, ILogLevelLoader logLevelLoader, ILogger serilogLog)
|
||||
{
|
||||
_logViewerConfig = logViewerConfig;
|
||||
_logLevelLoader = logLevelLoader;
|
||||
_serilogLog = serilogLog;
|
||||
}
|
||||
|
||||
public abstract bool CanHandleLargeLogs { get; }
|
||||
|
||||
public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch>? GetSavedSearches()
|
||||
=> _logViewerConfig.GetSavedSearches();
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query)
|
||||
=> _logViewerConfig.AddSavedSearch(name, query);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query)
|
||||
=> _logViewerConfig.DeleteSavedSearch(name, query);
|
||||
|
||||
public int GetNumberOfErrors(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var errorCounter = new ErrorCounterFilter();
|
||||
GetLogs(logTimePeriod, errorCounter, 0, int.MaxValue);
|
||||
return errorCounter.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
[Obsolete("Please use LogLevelLoader.GetGlobalMinLogLevel() instead. Scheduled for removal in V11.")]
|
||||
public string GetLogLevel()
|
||||
{
|
||||
LogEventLevel? logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>()
|
||||
.Where(_serilogLog.IsEnabled).DefaultIfEmpty(LogEventLevel.Information).Min();
|
||||
return logLevel?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var counter = new CountingFilter();
|
||||
GetLogs(logTimePeriod, counter, 0, int.MaxValue);
|
||||
return counter.Counts;
|
||||
}
|
||||
|
||||
public IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var messageTemplates = new MessageTemplateFilter();
|
||||
GetLogs(logTimePeriod, messageTemplates, 0, int.MaxValue);
|
||||
|
||||
IOrderedEnumerable<LogTemplate> templates = messageTemplates.Counts
|
||||
.Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value })
|
||||
.OrderByDescending(x => x.Count);
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
public PagedResult<LogMessage> GetLogs(
|
||||
LogTimePeriod logTimePeriod,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 100,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
string? filterExpression = null,
|
||||
string[]? logLevels = null)
|
||||
{
|
||||
var expression = new ExpressionFilter(filterExpression);
|
||||
IReadOnlyList<LogEvent> filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue);
|
||||
|
||||
// This is user used the checkbox UI to toggle which log levels they wish to see
|
||||
// If an empty array or null - its implied all levels to be viewed
|
||||
if (logLevels?.Length > 0)
|
||||
{
|
||||
_logViewerConfig = logViewerConfig;
|
||||
_logLevelLoader = StaticServiceProvider.Instance.GetRequiredService<ILogLevelLoader>();
|
||||
_serilogLog = serilogLog;
|
||||
}
|
||||
|
||||
protected SerilogLogViewerSourceBase(ILogViewerConfig logViewerConfig, ILogLevelLoader logLevelLoader, global::Serilog.ILogger serilogLog)
|
||||
{
|
||||
_logViewerConfig = logViewerConfig;
|
||||
_logLevelLoader = logLevelLoader;
|
||||
_serilogLog = serilogLog;
|
||||
}
|
||||
|
||||
public abstract bool CanHandleLargeLogs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all logs from your chosen data source back as Serilog LogEvents
|
||||
/// </summary>
|
||||
protected abstract IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take);
|
||||
|
||||
public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch>? GetSavedSearches()
|
||||
=> _logViewerConfig.GetSavedSearches();
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query)
|
||||
=> _logViewerConfig.AddSavedSearch(name, query);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query)
|
||||
=> _logViewerConfig.DeleteSavedSearch(name, query);
|
||||
|
||||
public int GetNumberOfErrors(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var errorCounter = new ErrorCounterFilter();
|
||||
GetLogs(logTimePeriod, errorCounter, 0, int.MaxValue);
|
||||
return errorCounter.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level and UmbracoFile-level values from the config file.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, LogEventLevel?> GetLogLevels()
|
||||
{
|
||||
return _logLevelLoader.GetLogLevelsFromSinks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
[Obsolete("Please use LogLevelLoader.GetGlobalMinLogLevel() instead. Scheduled for removal in V11.")]
|
||||
public string GetLogLevel()
|
||||
{
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(_serilogLog.IsEnabled).DefaultIfEmpty(LogEventLevel.Information)?.Min() ?? null;
|
||||
return logLevel?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var counter = new CountingFilter();
|
||||
GetLogs(logTimePeriod, counter, 0, int.MaxValue);
|
||||
return counter.Counts;
|
||||
}
|
||||
|
||||
public IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var messageTemplates = new MessageTemplateFilter();
|
||||
GetLogs(logTimePeriod, messageTemplates, 0, int.MaxValue);
|
||||
|
||||
var templates = messageTemplates.Counts.
|
||||
Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value })
|
||||
.OrderByDescending(x=> x.Count);
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
public PagedResult<LogMessage> GetLogs(LogTimePeriod logTimePeriod,
|
||||
int pageNumber = 1, int pageSize = 100,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
string? filterExpression = null,
|
||||
string[]? logLevels = null)
|
||||
{
|
||||
var expression = new ExpressionFilter(filterExpression);
|
||||
var filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue);
|
||||
|
||||
//This is user used the checkbox UI to toggle which log levels they wish to see
|
||||
//If an empty array or null - its implied all levels to be viewed
|
||||
if (logLevels?.Length > 0)
|
||||
var logsAfterLevelFilters = new List<LogEvent>();
|
||||
var validLogType = true;
|
||||
foreach (var level in logLevels)
|
||||
{
|
||||
var logsAfterLevelFilters = new List<LogEvent>();
|
||||
var validLogType = true;
|
||||
foreach (var level in logLevels)
|
||||
// Check if level string is part of the LogEventLevel enum
|
||||
if (Enum.IsDefined(typeof(LogEventLevel), level))
|
||||
{
|
||||
//Check if level string is part of the LogEventLevel enum
|
||||
if(Enum.IsDefined(typeof(LogEventLevel), level))
|
||||
{
|
||||
validLogType = true;
|
||||
logsAfterLevelFilters.AddRange(filteredLogs.Where(x => string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase)));
|
||||
}
|
||||
else
|
||||
{
|
||||
validLogType = false;
|
||||
}
|
||||
validLogType = true;
|
||||
logsAfterLevelFilters.AddRange(filteredLogs.Where(x =>
|
||||
string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase)));
|
||||
}
|
||||
|
||||
if (validLogType)
|
||||
else
|
||||
{
|
||||
filteredLogs = logsAfterLevelFilters;
|
||||
validLogType = false;
|
||||
}
|
||||
}
|
||||
|
||||
long totalRecords = filteredLogs.Count;
|
||||
|
||||
//Order By, Skip, Take & Select
|
||||
var logMessages = filteredLogs
|
||||
.OrderBy(l => l.Timestamp, orderDirection)
|
||||
.Skip(pageSize * (pageNumber - 1))
|
||||
.Take(pageSize)
|
||||
.Select(x => new LogMessage
|
||||
{
|
||||
Timestamp = x.Timestamp,
|
||||
Level = x.Level,
|
||||
MessageTemplateText = x.MessageTemplate.Text,
|
||||
Exception = x.Exception?.ToString(),
|
||||
Properties = x.Properties,
|
||||
RenderedMessage = x.RenderMessage()
|
||||
});
|
||||
|
||||
return new PagedResult<LogMessage>(totalRecords, pageNumber, pageSize)
|
||||
if (validLogType)
|
||||
{
|
||||
Items = logMessages
|
||||
};
|
||||
filteredLogs = logsAfterLevelFilters;
|
||||
}
|
||||
}
|
||||
|
||||
long totalRecords = filteredLogs.Count;
|
||||
|
||||
// Order By, Skip, Take & Select
|
||||
IEnumerable<LogMessage> logMessages = filteredLogs
|
||||
.OrderBy(l => l.Timestamp, orderDirection)
|
||||
.Skip(pageSize * (pageNumber - 1))
|
||||
.Take(pageSize)
|
||||
.Select(x => new LogMessage
|
||||
{
|
||||
Timestamp = x.Timestamp,
|
||||
Level = x.Level,
|
||||
MessageTemplateText = x.MessageTemplate.Text,
|
||||
Exception = x.Exception?.ToString(),
|
||||
Properties = x.Properties,
|
||||
RenderedMessage = x.RenderMessage(),
|
||||
});
|
||||
|
||||
return new PagedResult<LogMessage>(totalRecords, pageNumber, pageSize) { Items = logMessages };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level and UmbracoFile-level values from the config file.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, LogEventLevel?> GetLogLevels() => _logLevelLoader.GetLogLevelsFromSinks();
|
||||
|
||||
/// <summary>
|
||||
/// Get all logs from your chosen data source back as Serilog LogEvents
|
||||
/// </summary>
|
||||
protected abstract IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user