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:
Nikolaj Geisle
2022-06-02 08:18:31 +02:00
committed by GitHub
parent adcc9a0e1f
commit f4e333c178
838 changed files with 64052 additions and 61173 deletions

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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");
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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 &amp; 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 &amp; 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);
}

View File

@@ -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);
}

View File

@@ -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; }
}

View File

@@ -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;
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}