Merge pull request #7996 from umbraco/netcore/bugfix/logging-fixing
Fixes up all things logging in netcore
This commit is contained in:
@@ -176,3 +176,4 @@ build/temp/
|
||||
/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/lib/*
|
||||
/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/views/*
|
||||
/src/Umbraco.Web.UI.NetCore/wwwroot/App_Data/TEMP/*
|
||||
/src/Umbraco.Web.UI.NetCore/App_Data/Logs/*
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace Umbraco.Core.Hosting
|
||||
{
|
||||
string SiteName { get; }
|
||||
string ApplicationId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Will return the physical path to the root of the application
|
||||
/// </summary>
|
||||
string ApplicationPhysicalPath { get; }
|
||||
|
||||
string LocalTempPath { get; }
|
||||
@@ -27,10 +31,22 @@ namespace Umbraco.Core.Hosting
|
||||
bool IsHosted { get; }
|
||||
|
||||
Version IISVersion { get; }
|
||||
|
||||
// TODO: Should we change this name to MapPathWebRoot ? and also have a new MapPathContentRoot ?
|
||||
|
||||
/// <summary>
|
||||
/// Maps a virtual path to a physical path to the application's web root
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however
|
||||
/// in netcore the web root is /www therefore this will Map to a physical path within www.
|
||||
/// </remarks>
|
||||
string MapPath(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a virtual path to the application's web root
|
||||
/// Converts a virtual path to an absolute URL path based on the application's web root
|
||||
/// </summary>
|
||||
/// <param name="virtualPath">The virtual path. Must start with either ~/ or / else an exception is thrown.</param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
|
||||
public interface ILoggingConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The physical path where logs are stored
|
||||
/// </summary>
|
||||
string LogDirectory { get; }
|
||||
string LogConfigurationFile { get; }
|
||||
string UserLogConfigurationFile { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,12 @@
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Defines the profiling service.
|
||||
/// </summary>
|
||||
public interface IProfiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the profiling results.
|
||||
/// </summary>
|
||||
/// <returns>The profiling results.</returns>
|
||||
/// <remarks>Generally used for HTML rendering.</remarks>
|
||||
string Render();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IDisposable"/> that will time the code between its creation and disposal.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to render a profiler in a web page
|
||||
/// </summary>
|
||||
public interface IProfilerHtml
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the profiling results.
|
||||
/// </summary>
|
||||
/// <returns>The profiling results.</returns>
|
||||
/// <remarks>Generally used for HTML rendering.</remarks>
|
||||
string Render();
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,6 @@ namespace Umbraco.Core.Logging
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Render()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable Step(string name)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
public class LoggingConfiguration : ILoggingConfiguration
|
||||
{
|
||||
public LoggingConfiguration(string logDirectory, string logConfigurationFile, string userLogConfigurationFile)
|
||||
{
|
||||
LogDirectory = logDirectory ?? throw new ArgumentNullException(nameof(logDirectory));
|
||||
LogConfigurationFile = logConfigurationFile ?? throw new ArgumentNullException(nameof(logConfigurationFile));
|
||||
UserLogConfigurationFile = userLogConfigurationFile ?? throw new ArgumentNullException(nameof(userLogConfigurationFile));
|
||||
}
|
||||
|
||||
public string LogDirectory { get; }
|
||||
|
||||
public string LogConfigurationFile { get; }
|
||||
|
||||
public string UserLogConfigurationFile { get; }
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,6 @@ namespace Umbraco.Core.Logging
|
||||
{
|
||||
private readonly VoidDisposable _disposable = new VoidDisposable();
|
||||
|
||||
public string Render()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public IDisposable Step(string name)
|
||||
{
|
||||
return _disposable;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Net
|
||||
{
|
||||
public class NullSessionIdResolver : ISessionIdResolver
|
||||
{
|
||||
public string SessionId => null;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,17 @@ namespace Umbraco.Core.Composing
|
||||
/// <param name="builder"></param>
|
||||
/// <returns></returns>
|
||||
public static IHostBuilder UseUmbraco(this IHostBuilder builder)
|
||||
=> builder.UseUmbraco(new UmbracoServiceProviderFactory());
|
||||
{
|
||||
return builder
|
||||
.UseUmbraco(new UmbracoServiceProviderFactory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a custom service provider factory to use Umbraco's container
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="umbracoServiceProviderFactory"></param>
|
||||
/// <returns></returns>
|
||||
public static IHostBuilder UseUmbraco(this IHostBuilder builder, UmbracoServiceProviderFactory umbracoServiceProviderFactory)
|
||||
=> builder.UseServiceProviderFactory(umbracoServiceProviderFactory);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Diagnostics
|
||||
@@ -100,7 +101,7 @@ namespace Umbraco.Core.Diagnostics
|
||||
return bRet;
|
||||
}
|
||||
|
||||
public static bool Dump(IMarchal marchal, IIOHelper ioHelper, Option options = Option.WithFullMemory, bool withException = false)
|
||||
public static bool Dump(IMarchal marchal, IHostingEnvironment hostingEnvironment, Option options = Option.WithFullMemory, bool withException = false)
|
||||
{
|
||||
lock (LockO)
|
||||
{
|
||||
@@ -110,7 +111,7 @@ namespace Umbraco.Core.Diagnostics
|
||||
// filter everywhere in our code = not!
|
||||
var stacktrace = withException ? Environment.StackTrace : string.Empty;
|
||||
|
||||
var filepath = ioHelper.MapPath("~/App_Data/MiniDump");
|
||||
var filepath = Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data/MiniDump");
|
||||
if (Directory.Exists(filepath) == false)
|
||||
Directory.CreateDirectory(filepath);
|
||||
|
||||
@@ -122,11 +123,11 @@ namespace Umbraco.Core.Diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OkToDump(IIOHelper ioHelper)
|
||||
public static bool OkToDump(IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
lock (LockO)
|
||||
{
|
||||
var filepath = ioHelper.MapPath("~/App_Data/MiniDump");
|
||||
var filepath = Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data/MiniDump");
|
||||
if (Directory.Exists(filepath) == false) return true;
|
||||
var count = Directory.GetFiles(filepath, "*.dmp").Length;
|
||||
return count < 8;
|
||||
|
||||
@@ -16,14 +16,14 @@ namespace Umbraco.Core.Logging
|
||||
// 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<global::Serilog.ILogger> MinimalLogger = new Lazy<global::Serilog.ILogger>(() => new LoggerConfiguration().CreateLogger());
|
||||
|
||||
public string Render(string messageTemplate, params object[] args)
|
||||
{
|
||||
// by default, unless initialized otherwise, Log.Logger is SilentLogger which cannot bind message
|
||||
// templates. Log.Logger is set to a true Logger when initializing Umbraco's logger, but in case
|
||||
// that has not been done already - use a temp minimal logger (eg for tests).
|
||||
var logger = Log.Logger as global::Serilog.Core.Logger ?? MinimalLogger.Value;
|
||||
// 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);
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
|
||||
/// </summary>
|
||||
internal class HttpRequestIdEnricher : ILogEventEnricher
|
||||
public class HttpRequestIdEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly Func<IRequestCache> _requestCacheGetter;
|
||||
private readonly IRequestCache _requestCache;
|
||||
|
||||
public HttpRequestIdEnricher(Func<IRequestCache> requestCacheGetter)
|
||||
public HttpRequestIdEnricher(IRequestCache requestCache)
|
||||
{
|
||||
_requestCacheGetter = requestCacheGetter;
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,11 +34,8 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
{
|
||||
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
|
||||
|
||||
var requestCache = _requestCacheGetter();
|
||||
if(requestCache is null) return;
|
||||
|
||||
Guid requestId;
|
||||
if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId, requestCache))
|
||||
if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId, _requestCache))
|
||||
return;
|
||||
|
||||
var requestIdProperty = new LogEventProperty(HttpRequestIdPropertyName, new ScalarValue(requestId));
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestNumberEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
|
||||
/// </summary>
|
||||
internal class HttpRequestNumberEnricher : ILogEventEnricher
|
||||
public class HttpRequestNumberEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly Func<IRequestCache> _requestCacheGetter;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private static int _lastRequestNumber;
|
||||
private static readonly string _requestNumberItemName = typeof(HttpRequestNumberEnricher).Name + "+RequestNumber";
|
||||
|
||||
@@ -25,9 +25,9 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
private const string _httpRequestNumberPropertyName = "HttpRequestNumber";
|
||||
|
||||
|
||||
public HttpRequestNumberEnricher(Func<IRequestCache> requestCacheGetter)
|
||||
public HttpRequestNumberEnricher(IRequestCache requestCache)
|
||||
{
|
||||
_requestCacheGetter = requestCacheGetter;
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,10 +39,7 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
{
|
||||
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
|
||||
|
||||
var requestCache = _requestCacheGetter();
|
||||
if (requestCache is null) return;
|
||||
|
||||
var requestNumber = requestCache.Get(_requestNumberItemName,
|
||||
var requestNumber = _requestCache.Get(_requestNumberItemName,
|
||||
() => Interlocked.Increment(ref _lastRequestNumber));
|
||||
|
||||
var requestNumberProperty = new LogEventProperty(_httpRequestNumberPropertyName, new ScalarValue(requestNumber));
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpSessionIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
|
||||
/// </summary>
|
||||
internal class HttpSessionIdEnricher : ILogEventEnricher
|
||||
public class HttpSessionIdEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly ISessionIdResolver _sessionIdResolver;
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Diagnostics;
|
||||
using Umbraco.Core.Hosting;
|
||||
|
||||
namespace Umbraco.Infrastructure.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
|
||||
{
|
||||
private readonly ICoreDebugSettings _coreDebugSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IMarchal _marchal;
|
||||
|
||||
public ThreadAbortExceptionEnricher(ICoreDebugSettings coreDebugSettings, IHostingEnvironment hostingEnvironment, IMarchal marchal)
|
||||
{
|
||||
_coreDebugSettings = coreDebugSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_marchal = marchal;
|
||||
}
|
||||
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
case LogEventLevel.Error:
|
||||
case LogEventLevel.Fatal:
|
||||
DumpThreadAborts(logEvent, propertyFactory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpThreadAborts(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (!IsTimeoutThreadAbortException(logEvent.Exception)) return;
|
||||
|
||||
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
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTimeoutThreadAbortException(Exception exception)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private static bool IsMonitorEnterThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
|
||||
var stacktrace = abort.StackTrace;
|
||||
return stacktrace.Contains("System.Threading.Monitor.ReliableEnter");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Serilog;
|
||||
using Serilog.Configuration;
|
||||
@@ -6,11 +7,8 @@ using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting;
|
||||
using Serilog.Formatting.Compact;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Core.Logging.Serilog
|
||||
{
|
||||
@@ -24,27 +22,30 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// It is highly recommended that you keep/use this default in your own logging config customizations
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
public static LoggerConfiguration MinimalConfiguration(this LoggerConfiguration logConfig, IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func<IRequestCache> requestCacheGetter)
|
||||
/// <param name="IHostingEnvironment"></param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
public static LoggerConfiguration MinimalConfiguration(
|
||||
this LoggerConfiguration logConfig,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
global::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" />
|
||||
Environment.SetEnvironmentVariable("BASEDIR", AppDomain.CurrentDomain.BaseDirectory, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("UMBLOGDIR", loggingConfiguration.LogDirectory, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("BASEDIR", hostingEnvironment.ApplicationPhysicalPath, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process);
|
||||
|
||||
logConfig.MinimumLevel.Verbose() //Set to highest level of logging (as any sinks may want to restrict it to Errors only)
|
||||
.Enrich.WithProcessId()
|
||||
.Enrich.WithProcessName()
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.WithProperty(AppDomainId, AppDomain.CurrentDomain.Id)
|
||||
.Enrich.WithProperty(AppDomainId, AppDomain.CurrentDomain.Id)
|
||||
.Enrich.WithProperty("AppDomainAppId", hostingEnvironment.ApplicationId.ReplaceNonAlphanumericChars(string.Empty))
|
||||
.Enrich.WithProperty("MachineName", Environment.MachineName)
|
||||
.Enrich.With<Log4NetLevelMapperEnricher>()
|
||||
.Enrich.With(new HttpSessionIdEnricher(sessionIdResolver))
|
||||
.Enrich.With(new HttpRequestNumberEnricher(requestCacheGetter))
|
||||
.Enrich.With(new HttpRequestIdEnricher(requestCacheGetter));
|
||||
.Enrich.FromLogContext(); // allows us to dynamically enrich
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
@@ -53,13 +54,14 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// Outputs a .txt format log at /App_Data/Logs/
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
/// <param name="minimumLevel">The log level you wish the JSON file to collect - default is Verbose (highest)</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 OutputDefaultTextFile(this LoggerConfiguration logConfig, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
public static LoggerConfiguration OutputDefaultTextFile(this LoggerConfiguration logConfig, ILoggingConfiguration loggingConfiguration, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
{
|
||||
//Main .txt logfile - in similar format to older Log4Net output
|
||||
//Ends with ..txt as Date is inserted before file extension substring
|
||||
logConfig.WriteTo.File($@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\UmbracoTraceLog.{Environment.MachineName}..txt",
|
||||
logConfig.WriteTo.File(Path.Combine(loggingConfiguration.LogDirectory, $@"UmbracoTraceLog.{Environment.MachineName}..txt"),
|
||||
shared: true,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
restrictedToMinimumLevel: minimumLevel,
|
||||
@@ -99,7 +101,8 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
rollingInterval,
|
||||
rollOnFileSizeLimit,
|
||||
retainedFileCountLimit,
|
||||
encoding),
|
||||
encoding,
|
||||
null),
|
||||
sinkMapCountLimit:0)
|
||||
);
|
||||
}
|
||||
@@ -109,13 +112,14 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// Outputs a CLEF format JSON log at /App_Data/Logs/
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
/// <param name="minimumLevel">The log level you wish the JSON file to collect - default is Verbose (highest)</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, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
public static LoggerConfiguration OutputDefaultJsonFile(this LoggerConfiguration logConfig, 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(), $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\UmbracoTraceLog.{Environment.MachineName}..json",
|
||||
logConfig.WriteTo.File(new CompactJsonFormatter(), Path.Combine(loggingConfiguration.LogDirectory, $@"UmbracoTraceLog.{Environment.MachineName}..json"),
|
||||
shared: true,
|
||||
rollingInterval: RollingInterval.Day, //Create a new JSON file every day
|
||||
retainedFileCountLimit: retainedFileCount, //Setting to null means we keep all files - default is 31 days
|
||||
@@ -129,10 +133,11 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// That allows the main logging pipeline to be configured
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
public static LoggerConfiguration ReadFromConfigFile(this LoggerConfiguration logConfig)
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
public static LoggerConfiguration ReadFromConfigFile(this LoggerConfiguration logConfig, ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
//Read from main serilog.config file
|
||||
logConfig.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + @"\config\serilog.config");
|
||||
logConfig.ReadFrom.AppSettings(filePath: loggingConfiguration.LogConfigurationFile);
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
@@ -142,13 +147,15 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// That allows a separate logging pipeline to be configured that will not affect the main Umbraco log
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
public static LoggerConfiguration ReadFromUserConfigFile(this LoggerConfiguration logConfig)
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
public static LoggerConfiguration ReadFromUserConfigFile(this LoggerConfiguration logConfig, ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
//A nested logger - where any user configured sinks via config can not effect the main 'umbraco' logger above
|
||||
logConfig.WriteTo.Logger(cfg =>
|
||||
cfg.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + @"\config\serilog.user.config"));
|
||||
cfg.ReadFrom.AppSettings(filePath: loggingConfiguration.UserLogConfigurationFile));
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Infrastructure.Logging.Serilog.Enrichers;
|
||||
|
||||
namespace Umbraco.Infrastructure.Logging.Serilog
|
||||
{
|
||||
public class SerilogComposer : ICoreComposer
|
||||
{
|
||||
public void Compose(Composition composition)
|
||||
{
|
||||
composition.RegisterUnique<ThreadAbortExceptionEnricher>();
|
||||
composition.RegisterUnique<HttpSessionIdEnricher>();
|
||||
composition.RegisterUnique<HttpRequestNumberEnricher>();
|
||||
composition.RegisterUnique<HttpRequestIdEnricher>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Diagnostics;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Core.Logging.Serilog
|
||||
{
|
||||
|
||||
///<summary>
|
||||
/// Implements <see cref="ILogger"/> on top of Serilog.
|
||||
///</summary>
|
||||
public class SerilogLogger : ILogger, IDisposable
|
||||
{
|
||||
private readonly ICoreDebugSettings _coreDebugSettings;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IMarchal _marchal;
|
||||
public global::Serilog.ILogger SerilogLog { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the <see cref="SerilogLogger"/> class with a configuration file.
|
||||
/// </summary>
|
||||
/// <param name="logConfigFile"></param>
|
||||
public SerilogLogger(ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal, FileInfo logConfigFile)
|
||||
public SerilogLogger(FileInfo logConfigFile)
|
||||
{
|
||||
_coreDebugSettings = coreDebugSettings;
|
||||
_ioHelper = ioHelper;
|
||||
_marchal = marchal;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + logConfigFile)
|
||||
SerilogLog = new LoggerConfiguration()
|
||||
.ReadFrom.AppSettings(filePath: logConfigFile.FullName)
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
public SerilogLogger(ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal, LoggerConfiguration logConfig)
|
||||
public SerilogLogger(LoggerConfiguration logConfig)
|
||||
{
|
||||
_coreDebugSettings = coreDebugSettings;
|
||||
_ioHelper = ioHelper;
|
||||
_marchal = marchal;
|
||||
|
||||
//Configure Serilog static global logger with config passed in
|
||||
Log.Logger = logConfig.CreateLogger();
|
||||
SerilogLog = logConfig.CreateLogger();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a logger with some pre-defined configuration and remainder from config file
|
||||
/// </summary>
|
||||
/// <remarks>Used by UmbracoApplicationBase to get its logger.</remarks>
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func<IRequestCache> requestCacheGetter, ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal)
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(IHostingEnvironment hostingEnvironment, ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
var loggerConfig = new LoggerConfiguration();
|
||||
loggerConfig
|
||||
.MinimalConfiguration(hostingEnvironment, sessionIdResolver, requestCacheGetter)
|
||||
.ReadFromConfigFile()
|
||||
.ReadFromUserConfigFile();
|
||||
.MinimalConfiguration(hostingEnvironment, loggingConfiguration)
|
||||
.ReadFromConfigFile(loggingConfiguration)
|
||||
.ReadFromUserConfigFile(loggingConfiguration);
|
||||
|
||||
return new SerilogLogger(coreDebugSettings, ioHelper, marchal, loggerConfig);
|
||||
return new SerilogLogger(loggerConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a contextualized logger.
|
||||
/// </summary>
|
||||
private global::Serilog.ILogger LoggerFor(Type reporting)
|
||||
=> Log.Logger.ForContext(reporting);
|
||||
=> SerilogLog.ForContext(reporting);
|
||||
|
||||
/// <summary>
|
||||
/// Maps Umbraco's log level to Serilog's.
|
||||
@@ -99,8 +83,7 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, Exception exception, string message)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
@@ -108,8 +91,7 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
public void Fatal(Type reporting, Exception exception)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
var message = "Exception.";
|
||||
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
|
||||
var message = "Exception.";
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
@@ -128,16 +110,14 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref messageTemplate);
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Error(Type reporting, Exception exception, string message)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref message);
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
@@ -146,7 +126,6 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
var message = "Exception";
|
||||
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref message);
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
@@ -166,67 +145,9 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref messageTemplate);
|
||||
logger.Error(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
private void DumpThreadAborts(global::Serilog.ILogger logger, LogEventLevel level, Exception exception, ref string messageTemplate)
|
||||
{
|
||||
var dump = false;
|
||||
|
||||
if (IsTimeoutThreadAbortException(exception))
|
||||
{
|
||||
messageTemplate += "\r\nThe thread has been aborted, because the request has timed out.";
|
||||
|
||||
// dump if configured, or if stacktrace contains Monitor.ReliableEnter
|
||||
dump = _coreDebugSettings.DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception);
|
||||
|
||||
// dump if it is ok to dump (might have a cap on number of dump...)
|
||||
dump &= MiniDump.OkToDump(_ioHelper);
|
||||
}
|
||||
|
||||
if (dump)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dumped = MiniDump.Dump(_marchal, _ioHelper, withException: true);
|
||||
messageTemplate += dumped
|
||||
? "\r\nA minidump was created in App_Data/MiniDump"
|
||||
: "\r\nFailed to create a minidump";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
messageTemplate += "\r\nFailed to create a minidump";
|
||||
|
||||
//Log a new entry (as opposed to appending to same log entry)
|
||||
logger.Write(level, ex, "Failed to create a minidump ({ExType}: {ExMessage})",
|
||||
new object[]{ ex.GetType().FullName, ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsMonitorEnterThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
|
||||
var stacktrace = abort.StackTrace;
|
||||
return stacktrace.Contains("System.Threading.Monitor.ReliableEnter");
|
||||
}
|
||||
|
||||
private static bool IsTimeoutThreadAbortException(Exception exception)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warn(Type reporting, string message)
|
||||
{
|
||||
@@ -289,7 +210,7 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
SerilogLog.DisposeIfDisposable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public interface ILogViewerConfig
|
||||
{
|
||||
IReadOnlyList<SavedLogSearch> GetSavedSearches();
|
||||
IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query);
|
||||
IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public void Compose(Composition composition)
|
||||
{
|
||||
composition.SetLogViewer(factory => new JsonLogViewer(composition.Logger, factory.GetInstance<IIOHelper>()));
|
||||
composition.RegisterUnique<ILogViewerConfig, LogViewerConfig>();
|
||||
composition.SetLogViewer<SerilogJsonLogViewer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public class LogViewerConfig : ILogViewerConfig
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private const string _pathToSearches = "~/Config/logviewer.searches.config.js";
|
||||
private readonly FileInfo _searchesConfig;
|
||||
|
||||
public LogViewerConfig(IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
var trimmedPath = _pathToSearches.TrimStart('~', '/').Replace('/', Path.DirectorySeparatorChar);
|
||||
var absolutePath = Path.Combine(_hostingEnvironment.ApplicationPhysicalPath, trimmedPath);
|
||||
_searchesConfig = new FileInfo(absolutePath);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch> GetSavedSearches()
|
||||
{
|
||||
//Our default implementation
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists();
|
||||
|
||||
var rawJson = System.IO.File.ReadAllText(_searchesConfig.FullName);
|
||||
return JsonConvert.DeserializeObject<SavedLogSearch[]>(rawJson);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Add the new item to the bottom of the list
|
||||
searches.Add(new SavedLogSearch { Name = name, Query = query });
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists();
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
//As opposed to push a new item into the array
|
||||
return searches;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Removes the search
|
||||
searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query));
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return searches;
|
||||
}
|
||||
|
||||
private void EnsureFileExists()
|
||||
{
|
||||
if (_searchesConfig.Exists) return;
|
||||
using (var writer = _searchesConfig.CreateText())
|
||||
{
|
||||
writer.Write("[]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -5,22 +5,25 @@ using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact.Reader;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
internal class JsonLogViewer : LogViewerSourceBase
|
||||
internal class SerilogJsonLogViewer : SerilogLogViewerSourceBase
|
||||
{
|
||||
private readonly string _logsPath;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public JsonLogViewer(ILogger logger, IIOHelper ioHelper, string logsPath = "", string searchPath = "") : base(ioHelper, searchPath)
|
||||
public SerilogJsonLogViewer(
|
||||
ILogger logger,
|
||||
ILogViewerConfig logViewerConfig,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
global::Serilog.ILogger serilogLog)
|
||||
: base(logViewerConfig, serilogLog)
|
||||
{
|
||||
if (string.IsNullOrEmpty(logsPath))
|
||||
logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
|
||||
|
||||
_logsPath = logsPath;
|
||||
_logger = logger;
|
||||
_logsPath = loggingConfiguration.LogDirectory;
|
||||
}
|
||||
|
||||
private const int FileSizeCap = 100;
|
||||
@@ -62,9 +65,6 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
var logs = new List<LogEvent>();
|
||||
|
||||
//Log Directory
|
||||
var logDirectory = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
|
||||
|
||||
var count = 0;
|
||||
|
||||
//foreach full day in the range - see if we can find one or more filenames that end with
|
||||
@@ -74,7 +74,7 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
//Filename ending to search for (As could be multiple)
|
||||
var filesToFind = GetSearchPattern(day);
|
||||
|
||||
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
|
||||
var filesForCurrentDay = Directory.GetFiles(_logsPath, filesToFind);
|
||||
|
||||
//Foreach file we find - open it
|
||||
foreach (var filePath in filesForCurrentDay)
|
||||
@@ -130,7 +130,7 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
// 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.Error<JsonLogViewer>(ex, "Unable to parse a line in the JSON log file");
|
||||
_logger.Error<SerilogJsonLogViewer>(ex, "Unable to parse a line in the JSON log file");
|
||||
|
||||
evt = null;
|
||||
return true;
|
||||
+13
-73
@@ -1,31 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public abstract class LogViewerSourceBase : ILogViewer
|
||||
|
||||
public abstract class SerilogLogViewerSourceBase : ILogViewer
|
||||
{
|
||||
private readonly string _searchesConfigPath;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly ILogViewerConfig _logViewerConfig;
|
||||
private readonly global::Serilog.ILogger _serilogLog;
|
||||
|
||||
protected LogViewerSourceBase(IIOHelper ioHelper, string pathToSearches = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(pathToSearches))
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
pathToSearches = ioHelper.MapPath("~/Config/logviewer.searches.config.js");
|
||||
|
||||
_searchesConfigPath = pathToSearches;
|
||||
_ioHelper = ioHelper;
|
||||
protected SerilogLogViewerSourceBase(ILogViewerConfig logViewerConfig, global::Serilog.ILogger serilogLog)
|
||||
{
|
||||
_logViewerConfig = logViewerConfig;
|
||||
_serilogLog = serilogLog;
|
||||
}
|
||||
|
||||
public abstract bool CanHandleLargeLogs { get; }
|
||||
@@ -38,55 +29,13 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> GetSavedSearches()
|
||||
{
|
||||
//Our default implementation
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists(_searchesConfigPath, "[]", _ioHelper);
|
||||
|
||||
var rawJson = System.IO.File.ReadAllText(_searchesConfigPath);
|
||||
return JsonConvert.DeserializeObject<SavedLogSearch[]>(rawJson);
|
||||
}
|
||||
=> _logViewerConfig.GetSavedSearches();
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Add the new item to the bottom of the list
|
||||
searches.Add(new SavedLogSearch { Name = name, Query = query });
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists(_searchesConfigPath, "[]", _ioHelper);
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfigPath, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
//As opposed to push a new item into the array
|
||||
return searches;
|
||||
}
|
||||
=> _logViewerConfig.AddSavedSearch(name, query);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Removes the search
|
||||
searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query));
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfigPath, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return searches;
|
||||
}
|
||||
=> _logViewerConfig.DeleteSavedSearch(name, query);
|
||||
|
||||
public int GetNumberOfErrors(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
@@ -101,7 +50,7 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
/// <returns></returns>
|
||||
public string GetLogLevel()
|
||||
{
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.Logger.IsEnabled)?.Min() ?? null;
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(_serilogLog.IsEnabled)?.Min() ?? null;
|
||||
return logLevel?.ToString() ?? "";
|
||||
}
|
||||
|
||||
@@ -182,15 +131,6 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
};
|
||||
}
|
||||
|
||||
private static void EnsureFileExists(string path, string contents, IIOHelper ioHelper)
|
||||
{
|
||||
var absolutePath = ioHelper.MapPath(path);
|
||||
if (System.IO.File.Exists(absolutePath)) return;
|
||||
|
||||
using (var writer = System.IO.File.CreateText(absolutePath))
|
||||
{
|
||||
writer.Write(contents);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Examine;
|
||||
using Umbraco.Infrastructure.Media;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Actions;
|
||||
using Umbraco.Web.Cache;
|
||||
@@ -351,10 +352,6 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
// register accessors for cultures
|
||||
composition.RegisterUnique<IDefaultCultureAccessor, DefaultCultureAccessor>();
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,5 +152,14 @@ namespace Umbraco.Tests.Common
|
||||
|
||||
return mock.Object;
|
||||
}
|
||||
|
||||
public ILoggingConfiguration GetLoggingConfiguration(IHostingEnvironment hostingEnv = null)
|
||||
{
|
||||
hostingEnv = hostingEnv ?? GetHostingEnvironment();
|
||||
return new LoggingConfiguration(
|
||||
Path.Combine(hostingEnv.ApplicationPhysicalPath, "App_Data\\Logs"),
|
||||
Path.Combine(hostingEnv.ApplicationPhysicalPath, "config\\serilog.config"),
|
||||
Path.Combine(hostingEnv.ApplicationPhysicalPath, "config\\serilog.user.config"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Web.Common.AspNetCore;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Implementations
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Umbraco.Tests.Integration
|
||||
|
||||
// Add it!
|
||||
services.AddUmbracoConfiguration(hostContext.Configuration);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, null, out _);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
|
||||
});
|
||||
|
||||
var host = await hostBuilder.StartAsync();
|
||||
@@ -140,7 +140,7 @@ namespace Umbraco.Tests.Integration
|
||||
|
||||
// Add it!
|
||||
services.AddUmbracoConfiguration(hostContext.Configuration);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, null, out _);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
|
||||
});
|
||||
|
||||
var host = await hostBuilder.StartAsync();
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
|
||||
// Add it!
|
||||
services.AddUmbracoConfiguration(hostContext.Configuration);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, null, out _);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
|
||||
});
|
||||
|
||||
var host = await hostBuilder.StartAsync();
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Logging.Viewer;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
|
||||
@@ -33,13 +35,16 @@ namespace Umbraco.Tests.Logging
|
||||
//Create an example JSON log file to check results
|
||||
//As a one time setup for all tets in this class/fixture
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var hostingEnv = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var loggingConfiguration = TestHelper.GetLoggingConfiguration(hostingEnv);
|
||||
|
||||
var exampleLogfilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"Logging\", _logfileName);
|
||||
_newLogfileDirPath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"App_Data\Logs\");
|
||||
_newLogfileDirPath = loggingConfiguration.LogDirectory;
|
||||
_newLogfilePath = Path.Combine(_newLogfileDirPath, _logfileName);
|
||||
|
||||
var exampleSearchfilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"Logging\", _searchfileName);
|
||||
_newSearchfileDirPath = Path.Combine(TestContext.CurrentContext.TestDirectory, @"Config\");
|
||||
_newSearchfileDirPath = Path.Combine(hostingEnv.ApplicationPhysicalPath, @"Config\");
|
||||
_newSearchfilePath = Path.Combine(_newSearchfileDirPath, _searchfileName);
|
||||
|
||||
//Create/ensure Directory exists
|
||||
@@ -51,7 +56,8 @@ namespace Umbraco.Tests.Logging
|
||||
File.Copy(exampleSearchfilePath, _newSearchfilePath, true);
|
||||
|
||||
var logger = Mock.Of<Core.Logging.ILogger>();
|
||||
_logViewer = new JsonLogViewer(logger, ioHelper, logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath);
|
||||
var logViewerConfig = new LogViewerConfig(hostingEnv);
|
||||
_logViewer = new SerilogJsonLogViewer(logger, logViewerConfig, loggingConfiguration, Log.Logger);
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
|
||||
@@ -17,6 +17,7 @@ using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Runtime;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Stubs;
|
||||
using Umbraco.Web;
|
||||
@@ -135,6 +136,7 @@ namespace Umbraco.Tests.Runtimes
|
||||
public override IFactory Configure(IRegister container)
|
||||
{
|
||||
container.Register<IApplicationShutdownRegistry, AspNetApplicationShutdownRegistry>(Lifetime.Singleton);
|
||||
container.Register<ISessionIdResolver, NullSessionIdResolver>(Lifetime.Singleton);
|
||||
|
||||
var factory = base.Configure(container);
|
||||
return factory;
|
||||
|
||||
@@ -19,11 +19,6 @@ namespace Umbraco.Tests.TestHelpers.Stubs
|
||||
|
||||
private static bool _enabled;
|
||||
|
||||
public string Render()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public IDisposable Step(string name)
|
||||
{
|
||||
return _enabled ? MiniProfiler.Current.Step(name) : null;
|
||||
|
||||
@@ -324,6 +324,8 @@ namespace Umbraco.Tests.TestHelpers
|
||||
|
||||
public static IHostingEnvironment GetHostingEnvironment() => _testHelperInternal.GetHostingEnvironment();
|
||||
|
||||
public static ILoggingConfiguration GetLoggingConfiguration(IHostingEnvironment hostingEnv) => _testHelperInternal.GetLoggingConfiguration(hostingEnv);
|
||||
|
||||
public static IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _testHelperInternal.GetHostingEnvironmentLifetime();
|
||||
|
||||
public static IIpResolver GetIpResolver() => _testHelperInternal.GetIpResolver();
|
||||
|
||||
@@ -264,7 +264,7 @@ namespace Umbraco.Tests.Testing
|
||||
profiler = Mock.Of<IProfiler>();
|
||||
break;
|
||||
case UmbracoTestOptions.Logger.Serilog:
|
||||
logger = new SerilogLogger(TestHelper.CoreDebugSettings, IOHelper, TestHelper.Marchal, new FileInfo(TestHelper.MapPathForTestFiles("~/unit-test.config")));
|
||||
logger = new SerilogLogger(new FileInfo(TestHelper.MapPathForTestFiles("~/unit-test.config")));
|
||||
profiler = new LogProfiler(logger);
|
||||
break;
|
||||
case UmbracoTestOptions.Logger.Console:
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
public void InitializeFixture()
|
||||
{
|
||||
|
||||
var logger = new SerilogLogger(TestHelper.CoreDebugSettings, IOHelper, TestHelper.Marchal, new FileInfo(TestHelper.MapPathForTestFiles("~/unit-test.config")));
|
||||
var logger = new SerilogLogger(new FileInfo(TestHelper.MapPathForTestFiles("~/unit-test.config")));
|
||||
_profilingLogger = new ProfilingLogger(logger, new LogProfiler(logger));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog.Context;
|
||||
using Smidge;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Infrastructure.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Web.Common.Middleware;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.AspNetCore
|
||||
{
|
||||
@@ -31,6 +35,10 @@ namespace Umbraco.Web.BackOffice.AspNetCore
|
||||
var runtimeShutdown = new CoreRuntimeShutdown(runtime, hostLifetime);
|
||||
hostLifetime.RegisterObject(runtimeShutdown);
|
||||
|
||||
// Register our global threadabort enricher for logging
|
||||
var threadAbortEnricher = app.ApplicationServices.GetRequiredService<ThreadAbortExceptionEnricher>();
|
||||
LogContext.Push(threadAbortEnricher); // NOTE: We are not in a using clause because we are not removing it, it is on the global context
|
||||
|
||||
// Start the runtime!
|
||||
runtime.Start();
|
||||
|
||||
@@ -64,6 +72,15 @@ namespace Umbraco.Web.BackOffice.AspNetCore
|
||||
}
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException(nameof(app));
|
||||
|
||||
app.UseMiddleware<UmbracoRequestLoggingMiddleware>();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseUmbracoRuntimeMinification(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException(nameof(app));
|
||||
|
||||
@@ -5,13 +5,12 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Common.AspNetCore
|
||||
{
|
||||
public class AspNetCoreHostingEnvironment : Umbraco.Core.Hosting.IHostingEnvironment
|
||||
{
|
||||
|
||||
|
||||
private readonly IHostingSettings _hostingSettings;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
|
||||
@@ -28,6 +27,7 @@ namespace Umbraco.Web.Common.AspNetCore
|
||||
|
||||
ApplicationVirtualPath = "/"; //TODO how to find this, This is a server thing, not application thing.
|
||||
IISVersion = new Version(0, 0); // TODO not necessary IIS
|
||||
|
||||
}
|
||||
|
||||
public bool IsHosted { get; } = true;
|
||||
|
||||
@@ -8,6 +8,10 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Hosting;
|
||||
using Serilog.Extensions.Logging;
|
||||
using Smidge;
|
||||
using Smidge.Nuglify;
|
||||
using Umbraco.Composing;
|
||||
@@ -73,13 +77,22 @@ namespace Umbraco.Web.Common.Extensions
|
||||
|
||||
var umbContainer = UmbracoServiceProviderFactory.UmbracoContainer;
|
||||
|
||||
var loggingConfig = new LoggingConfiguration(
|
||||
Path.Combine(webHostEnvironment.ContentRootPath, "App_Data\\Logs"),
|
||||
Path.Combine(webHostEnvironment.ContentRootPath, "config\\serilog.config"),
|
||||
Path.Combine(webHostEnvironment.ContentRootPath, "config\\serilog.user.config"));
|
||||
|
||||
IHttpContextAccessor httpContextAccessor = new HttpContextAccessor();
|
||||
services.AddSingleton<IHttpContextAccessor>(httpContextAccessor);
|
||||
|
||||
var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext.Items);
|
||||
|
||||
services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), requestCache, httpContextAccessor, out factory);
|
||||
services.AddUmbracoCore(webHostEnvironment,
|
||||
umbContainer,
|
||||
Assembly.GetEntryAssembly(),
|
||||
requestCache,
|
||||
loggingConfig,
|
||||
out factory);
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -92,6 +105,8 @@ namespace Umbraco.Web.Common.Extensions
|
||||
/// <param name="umbContainer"></param>
|
||||
/// <param name="entryAssembly"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoCore(
|
||||
@@ -100,7 +115,7 @@ namespace Umbraco.Web.Common.Extensions
|
||||
IRegister umbContainer,
|
||||
Assembly entryAssembly,
|
||||
IRequestCache requestCache,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
out IFactory factory)
|
||||
{
|
||||
if (services is null) throw new ArgumentNullException(nameof(services));
|
||||
@@ -112,7 +127,11 @@ namespace Umbraco.Web.Common.Extensions
|
||||
var configs = serviceProvider.GetService<Configs>();
|
||||
|
||||
|
||||
CreateCompositionRoot(services, configs, httpContextAccessor, webHostEnvironment, out var logger, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler);
|
||||
CreateCompositionRoot(services,
|
||||
configs,
|
||||
webHostEnvironment,
|
||||
loggingConfiguration,
|
||||
out var logger, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler);
|
||||
|
||||
var globalSettings = configs.Global();
|
||||
var umbracoVersion = new UmbracoVersion(globalSettings);
|
||||
@@ -133,7 +152,7 @@ namespace Umbraco.Web.Common.Extensions
|
||||
return services;
|
||||
}
|
||||
|
||||
private static ITypeFinder CreateTypeFinder(ILogger logger, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly)
|
||||
private static ITypeFinder CreateTypeFinder(Core.Logging.ILogger logger, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly)
|
||||
{
|
||||
// TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
|
||||
// this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
|
||||
@@ -144,7 +163,7 @@ namespace Umbraco.Web.Common.Extensions
|
||||
}
|
||||
|
||||
private static IRuntime GetCoreRuntime(
|
||||
Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger,
|
||||
Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, Core.Logging.ILogger logger,
|
||||
IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
|
||||
ITypeFinder typeFinder, IRequestCache requestCache)
|
||||
{
|
||||
@@ -180,31 +199,65 @@ namespace Umbraco.Web.Common.Extensions
|
||||
return coreRuntime;
|
||||
}
|
||||
|
||||
private static IServiceCollection CreateCompositionRoot(IServiceCollection services, Configs configs, IHttpContextAccessor httpContextAccessor, IWebHostEnvironment webHostEnvironment,
|
||||
out ILogger logger, out IIOHelper ioHelper, out Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
out IBackOfficeInfo backOfficeInfo, out IProfiler profiler)
|
||||
private static IServiceCollection CreateCompositionRoot(
|
||||
IServiceCollection services,
|
||||
Configs configs,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
out Core.Logging.ILogger logger,
|
||||
out IIOHelper ioHelper,
|
||||
out Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
out IBackOfficeInfo backOfficeInfo,
|
||||
out IProfiler profiler)
|
||||
{
|
||||
if (configs == null)
|
||||
throw new InvalidOperationException($"Could not resolve type {typeof(Configs)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}");
|
||||
|
||||
var hostingSettings = configs.Hosting();
|
||||
var coreDebug = configs.CoreDebug();
|
||||
var globalSettings = configs.Global();
|
||||
|
||||
hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment);
|
||||
ioHelper = new IOHelper(hostingEnvironment, globalSettings);
|
||||
logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment,
|
||||
new AspNetCoreSessionManager(httpContextAccessor),
|
||||
// TODO: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured
|
||||
() => services.BuildServiceProvider().GetService<IRequestCache>(), coreDebug, ioHelper,
|
||||
new AspNetCoreMarchal());
|
||||
logger = AddLogger(services, hostingEnvironment, loggingConfiguration);
|
||||
|
||||
backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings);
|
||||
profiler = GetWebProfiler(hostingEnvironment, httpContextAccessor);
|
||||
profiler = GetWebProfiler(hostingEnvironment);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and configure the logger
|
||||
/// </summary>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
private static Core.Logging.ILogger AddLogger(IServiceCollection services, Core.Hosting.IHostingEnvironment hostingEnvironment, ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
// Create a serilog logger
|
||||
var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, loggingConfiguration);
|
||||
|
||||
// Wire up all the bits that serilog needs. We need to use our own code since the Serilog ext methods don't cater to our needs since
|
||||
// we don't want to use the global serilog `Log` object and we don't have our own ILogger implementation before the HostBuilder runs which
|
||||
// is the only other option that these ext methods allow.
|
||||
// I have created a PR to make this nicer https://github.com/serilog/serilog-extensions-hosting/pull/19 but we'll need to wait for that.
|
||||
// Also see : https://github.com/serilog/serilog-extensions-hosting/blob/dev/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs
|
||||
|
||||
services.AddSingleton<ILoggerFactory>(services => new SerilogLoggerFactory(logger.SerilogLog, false));
|
||||
|
||||
// This won't (and shouldn't) take ownership of the logger.
|
||||
services.AddSingleton(logger.SerilogLog);
|
||||
|
||||
// Registered to provide two services...
|
||||
var diagnosticContext = new DiagnosticContext(logger.SerilogLog);
|
||||
|
||||
// Consumed by e.g. middleware
|
||||
services.AddSingleton(diagnosticContext);
|
||||
|
||||
// Consumed by user code
|
||||
services.AddSingleton<IDiagnosticContext>(diagnosticContext);
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddUmbracoRuntimeMinifier(this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
@@ -214,7 +267,7 @@ namespace Umbraco.Web.Common.Extensions
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IProfiler GetWebProfiler(Umbraco.Core.Hosting.IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor)
|
||||
private static IProfiler GetWebProfiler(Umbraco.Core.Hosting.IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
// create and start asap to profile boot
|
||||
if (!hostingEnvironment.IsDebugMode)
|
||||
@@ -224,7 +277,7 @@ namespace Umbraco.Web.Common.Extensions
|
||||
return new VoidProfiler();
|
||||
}
|
||||
|
||||
var webProfiler = new WebProfiler(httpContextAccessor);
|
||||
var webProfiler = new WebProfiler();
|
||||
webProfiler.StartBoot();
|
||||
|
||||
return webProfiler;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog.Context;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Web.Common.Middleware
|
||||
{
|
||||
public class UmbracoRequestLoggingMiddleware
|
||||
{
|
||||
readonly RequestDelegate _next;
|
||||
private readonly HttpSessionIdEnricher _sessionIdEnricher;
|
||||
private readonly HttpRequestNumberEnricher _requestNumberEnricher;
|
||||
private readonly HttpRequestIdEnricher _requestIdEnricher;
|
||||
|
||||
public UmbracoRequestLoggingMiddleware(RequestDelegate next,
|
||||
HttpSessionIdEnricher sessionIdEnricher,
|
||||
HttpRequestNumberEnricher requestNumberEnricher,
|
||||
HttpRequestIdEnricher requestIdEnricher)
|
||||
{
|
||||
_next = next;
|
||||
_sessionIdEnricher = sessionIdEnricher;
|
||||
_requestNumberEnricher = requestNumberEnricher;
|
||||
_requestIdEnricher = requestIdEnricher;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
// TODO: Need to decide if we want this stuff still, there's new request logging in serilog:
|
||||
// https://github.com/serilog/serilog-aspnetcore#request-logging which i think would suffice and replace all of this?
|
||||
|
||||
using (LogContext.Push(_sessionIdEnricher))
|
||||
using (LogContext.Push(_requestNumberEnricher))
|
||||
using (LogContext.Push(_requestIdEnricher))
|
||||
{
|
||||
await _next(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,4 @@ namespace Umbraco.Web.Common.Middleware
|
||||
_umbracoRequestLifetimeManager.EndRequest(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ using Umbraco.Core.Runtime;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Common.AspNetCore;
|
||||
using Umbraco.Web.Common.Lifetime;
|
||||
using Umbraco.Core.Diagnostics;
|
||||
using Umbraco.Web.Common.Runtime.Profiler;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Common.Runtime
|
||||
{
|
||||
@@ -42,6 +45,10 @@ namespace Umbraco.Web.Common.Runtime
|
||||
composition.RegisterUnique<ICookieManager, AspNetCoreCookieManager>();
|
||||
|
||||
composition.RegisterMultipleUnique<ISessionIdResolver, ISessionManager, AspNetCoreSessionManager>();
|
||||
|
||||
composition.RegisterUnique<IMarchal, AspNetCoreMarchal>();
|
||||
|
||||
composition.RegisterUnique<IProfilerHtml, WebProfilerHtml>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using StackExchange.Profiling;
|
||||
using StackExchange.Profiling.Internal;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
// TODO: This namespace is strange, not sure why i has "Runtime" in the name?
|
||||
namespace Umbraco.Web.Common.Runtime.Profiler
|
||||
{
|
||||
|
||||
public class WebProfiler : IProfiler
|
||||
{
|
||||
private MiniProfiler _startupProfiler;
|
||||
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private int _first;
|
||||
|
||||
public WebProfiler(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
// create our own provider, which can provide a profiler even during boot
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Normally we would call MiniProfiler.Current.RenderIncludes(...), but because the requeststate is not set, this method does not work.
|
||||
/// We fake the requestIds from the RequestState here.
|
||||
/// </remarks>
|
||||
public string Render()
|
||||
{
|
||||
|
||||
var profiler = MiniProfiler.Current;
|
||||
if (profiler == null) return string.Empty;
|
||||
|
||||
var context = _httpContextAccessor.HttpContext;
|
||||
|
||||
var path = (profiler.Options as MiniProfilerOptions)?.RouteBasePath.Value.EnsureTrailingSlash();
|
||||
|
||||
var result = StackExchange.Profiling.Internal.Render.Includes(
|
||||
profiler,
|
||||
path: context.Request.PathBase + path,
|
||||
isAuthorized: true,
|
||||
requestIDs: new List<Guid>{ profiler.Id },
|
||||
position: RenderPosition.Right,
|
||||
showTrivial: profiler.Options.PopupShowTrivial,
|
||||
showTimeWithChildren: profiler.Options.PopupShowTimeWithChildren,
|
||||
maxTracesToShow: profiler.Options.PopupMaxTracesToShow,
|
||||
showControls:profiler.Options.ShowControls,
|
||||
startHidden: profiler.Options.PopupStartHidden);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IDisposable Step(string name)
|
||||
{
|
||||
return MiniProfiler.Current?.Step(name);
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Common.Runtime.Profiler
|
||||
{
|
||||
internal class WebProfilerComposer : ComponentComposer<WebProfilerComponent>, ICoreComposer
|
||||
{
|
||||
public override void Compose(Composition composition)
|
||||
{
|
||||
base.Compose(composition);
|
||||
|
||||
composition.RegisterUnique<WebProfilerHtml>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using StackExchange.Profiling;
|
||||
using StackExchange.Profiling.Internal;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
// TODO: This namespace is strange, not sure why i has "Runtime" in the name?
|
||||
namespace Umbraco.Web.Common.Runtime.Profiler
|
||||
{
|
||||
public class WebProfilerHtml : IProfilerHtml
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public WebProfilerHtml(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
// create our own provider, which can provide a profiler even during boot
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Normally we would call MiniProfiler.Current.RenderIncludes(...), but because the requeststate is not set, this method does not work.
|
||||
/// We fake the requestIds from the RequestState here.
|
||||
/// </remarks>
|
||||
public string Render()
|
||||
{
|
||||
|
||||
var profiler = MiniProfiler.Current;
|
||||
if (profiler == null) return string.Empty;
|
||||
|
||||
var context = _httpContextAccessor.HttpContext;
|
||||
|
||||
var path = (profiler.Options as MiniProfilerOptions)?.RouteBasePath.Value.EnsureTrailingSlash();
|
||||
|
||||
var result = StackExchange.Profiling.Internal.Render.Includes(
|
||||
profiler,
|
||||
path: context.Request.PathBase + path,
|
||||
isAuthorized: true,
|
||||
requestIDs: new List<Guid> { profiler.Id },
|
||||
position: RenderPosition.Right,
|
||||
showTrivial: profiler.Options.PopupShowTrivial,
|
||||
showTimeWithChildren: profiler.Options.PopupShowTimeWithChildren,
|
||||
maxTracesToShow: profiler.Options.PopupMaxTracesToShow,
|
||||
showControls: profiler.Options.ShowControls,
|
||||
startHidden: profiler.Options.PopupStartHidden);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
||||
<PackageReference Include="Smidge" Version="3.1.0" />
|
||||
<PackageReference Include="Smidge.Nuglify" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"name": "Find all logs where the Level is NOT Verbose and NOT Debug",
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration'",
|
||||
"query": "Has(Duration)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
||||
"query": "Has(Duration) and Duration > 1000"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that are from the namespace 'Umbraco.Core'",
|
||||
"query": "StartsWith(SourceContext, 'Umbraco.Core')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that use a specific log message template",
|
||||
"query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
|
||||
},
|
||||
{
|
||||
"name": "Find logs where one of the items in the SortedComponentTypes property array is equal to",
|
||||
"query": "SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'"
|
||||
},
|
||||
{
|
||||
"name": "Find logs where one of the items in the SortedComponentTypes property array contains",
|
||||
"query": "Contains(SortedComponentTypes[?], 'DatabaseServer')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that the message has localhost in it with SQL like",
|
||||
"query": "@Message like '%localhost%'"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that the message that starts with 'end' in it with SQL like",
|
||||
"query": "@Message like 'end%'"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
|
||||
<!-- Used to toggle the log levels for the main Umbraco log files -->
|
||||
<!-- Found at /app_data/logs/ -->
|
||||
<!-- NOTE: Changing this will also flow down into serilog.user.config -->
|
||||
<!-- VALID Values: Verbose, Debug, Information, Warning, Error, Fatal -->
|
||||
<add key="serilog:minimum-level" value="Information" />
|
||||
|
||||
<!-- NOTE: This is how sources can have a different level -->
|
||||
<!--
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Composing.TypeLoader" value="Warning" />
|
||||
-->
|
||||
|
||||
<!-- NOTE: Only one logger below can be enabled, you cannot log JSON & TXT files at the same time -->
|
||||
|
||||
<!-- Default JSON log file -->
|
||||
<!-- This is used by the default log viewer in the Umbraco backoffice -->
|
||||
<add key="serilog:using:File" value="Umbraco.Infrastructure" />
|
||||
<add key="serilog:write-to:File.formatter" value="Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" />
|
||||
<add key="serilog:write-to:File.path" value="%UMBLOGDIR%\UmbracoTraceLog.%MACHINENAME%..json" />
|
||||
<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" />
|
||||
<add key="serilog:write-to:File.retainedFileCountLimit" value="" /> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<add key="serilog:write-to:File.rollingInterval" value="Day" /> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
|
||||
|
||||
<!-- Optional TXT log file -->
|
||||
<!--<add key="serilog:using:File" value="Serilog.Sinks.File" /> -->
|
||||
<!--<add key="serilog:write-to:File.path" value="%UMBLOGDIR%\UmbracoTraceLog.%MACHINENAME%..txt" /> -->
|
||||
<!--<add key="serilog:write-to:File.shared" value="true" /> -->
|
||||
<!--<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" /> -->
|
||||
<!--<add key="serilog:write-to:File.retainedFileCountLimit" value="" /> --> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<!--<add key="serilog:write-to:File.rollingInterval" value="Day" /> --> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
<!--<add key="serilog:write-to:File.outputTemplate" value="{Timestamp:yyyy-MM-dd HH:mm:ss,fff} [P{ProcessId}/D{AppDomainId}/T{ThreadId}] {Log4NetLevel} {SourceContext} - {Message:lj}{NewLine}{Exception}" /> -->
|
||||
|
||||
<!-- To write to new log locations (aka Sinks) such as your own .txt files with filtering, ELMAH.io, Elastic, SEQ -->
|
||||
<!-- Please use the serilog.user.config file to configure your own logging needs -->
|
||||
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
|
||||
<!-- Used to toggle the log levels for the main Umbraco log files -->
|
||||
<!-- Found at /app_data/logs/ -->
|
||||
<!-- NOTE: Changing this will also flow down into serilog.user.config -->
|
||||
<!-- VALID Values: Verbose, Debug, Information, Warning, Error, Fatal -->
|
||||
<add key="serilog:minimum-level" value="Information" />
|
||||
|
||||
<!-- NOTE: This is how sources can have a different level -->
|
||||
<!--
|
||||
<add key="serilog:minimum-level:override:Umbraco.Core.Composing.TypeLoader" value="Warning" />
|
||||
-->
|
||||
|
||||
<!-- NOTE: Only one logger below can be enabled, you cannot log JSON & TXT files at the same time -->
|
||||
|
||||
<!-- Default JSON log file -->
|
||||
<!-- This is used by the default log viewer in the Umbraco backoffice -->
|
||||
<add key="serilog:using:File" value="Umbraco.Infrastructure" />
|
||||
<add key="serilog:write-to:File.formatter" value="Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" />
|
||||
<add key="serilog:write-to:File.path" value="%UMBLOGDIR%\UmbracoTraceLog.%MACHINENAME%..json" />
|
||||
<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" />
|
||||
<add key="serilog:write-to:File.retainedFileCountLimit" value="" /> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<add key="serilog:write-to:File.rollingInterval" value="Day" /> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
|
||||
|
||||
<!-- Optional TXT log file -->
|
||||
<!--<add key="serilog:using:File" value="Serilog.Sinks.File" /> -->
|
||||
<!--<add key="serilog:write-to:File.path" value="%UMBLOGDIR%\UmbracoTraceLog.%MACHINENAME%..txt" /> -->
|
||||
<!--<add key="serilog:write-to:File.shared" value="true" /> -->
|
||||
<!--<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" /> -->
|
||||
<!--<add key="serilog:write-to:File.retainedFileCountLimit" value="" /> --> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<!--<add key="serilog:write-to:File.rollingInterval" value="Day" /> --> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
<!--<add key="serilog:write-to:File.outputTemplate" value="{Timestamp:yyyy-MM-dd HH:mm:ss,fff} [P{ProcessId}/D{AppDomainId}/T{ThreadId}] {Log4NetLevel} {SourceContext} - {Message:lj}{NewLine}{Exception}" /> -->
|
||||
|
||||
<!-- To write to new log locations (aka Sinks) such as your own .txt files with filtering, ELMAH.io, Elastic, SEQ -->
|
||||
<!-- Please use the serilog.user.config file to configure your own logging needs -->
|
||||
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
|
||||
<!-- Controls log levels for all user-defined child sub-logger sinks configured here (Set this higher than child sinks defined here) -->
|
||||
<!-- VALID Values: Verbose, Debug, Information, Warning, Error, Fatal -->
|
||||
<add key="serilog:minimum-level" value="Information" />
|
||||
|
||||
<!-- For Different Namespaces - Set different logging levels -->
|
||||
<!--
|
||||
<add key="serilog:minimum-level:override:Microsoft" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Microsoft.AspNetCore.Mvc" value="Error" />
|
||||
<add key="serilog:minimum-level:override:YourNameSpace" value="Information" />
|
||||
-->
|
||||
|
||||
<!-- All logs defined via user.config will contain this property (won't be in main Umbraco logs) -->
|
||||
<!--
|
||||
<add key="serilog:enrich:with-property:websiteName" value="My Awesome Website - Development" />
|
||||
-->
|
||||
|
||||
<!-- Write to a user log file -->
|
||||
<!--
|
||||
<add key="serilog:using:File" value="Serilog.Sinks.File" />
|
||||
<add key="serilog:write-to:File.path" value="%BASEDIR%\logs\my-custom-logfile.txt" />
|
||||
<add key="serilog:write-to:File.shared" value="true" />
|
||||
<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" />
|
||||
<add key="serilog:write-to:File.retainedFileCountLimit" value="32" />--> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<!--<add key="serilog:write-to:File.rollingInterval" value="Day" />--> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
<!--<add key="serilog:write-to:File.outputTemplate" value="{Timestamp:yyyy-MM-dd HH:mm:ss,fff} [P{ProcessId}/D{AppDomainId}/T{ThreadId}] {Log4NetLevel} {SourceContext} - {Message:lj}{NewLine}{Exception}" /> -->
|
||||
|
||||
<!-- Filters all above sink's to use this expression -->
|
||||
<!-- Common use case is to include SourceType starting with your own namespace -->
|
||||
<!--
|
||||
<add key="serilog:using:FilterExpressions" value="Serilog.Filters.Expressions" />
|
||||
<add key="serilog:filter:ByIncludingOnly.expression" value="StartsWith(SourceContext, 'Umbraco.Core')" />
|
||||
-->
|
||||
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
|
||||
<!-- Controls log levels for all user-defined child sub-logger sinks configured here (Set this higher than child sinks defined here) -->
|
||||
<!-- VALID Values: Verbose, Debug, Information, Warning, Error, Fatal -->
|
||||
<add key="serilog:minimum-level" value="Information" />
|
||||
|
||||
<!-- For Different Namespaces - Set different logging levels -->
|
||||
<!--
|
||||
<add key="serilog:minimum-level:override:Microsoft" value="Warning" />
|
||||
<add key="serilog:minimum-level:override:Microsoft.AspNetCore.Mvc" value="Error" />
|
||||
<add key="serilog:minimum-level:override:YourNameSpace" value="Information" />
|
||||
-->
|
||||
|
||||
<!-- All logs defined via user.config will contain this property (won't be in main Umbraco logs) -->
|
||||
<!--
|
||||
<add key="serilog:enrich:with-property:websiteName" value="My Awesome Website - Development" />
|
||||
-->
|
||||
|
||||
<!-- Write to a user log file -->
|
||||
<!--
|
||||
<add key="serilog:using:File" value="Serilog.Sinks.File" />
|
||||
<add key="serilog:write-to:File.path" value="%BASEDIR%\logs\my-custom-logfile.txt" />
|
||||
<add key="serilog:write-to:File.shared" value="true" />
|
||||
<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" />
|
||||
<add key="serilog:write-to:File.retainedFileCountLimit" value="32" />--> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<!--<add key="serilog:write-to:File.rollingInterval" value="Day" />--> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
<!--<add key="serilog:write-to:File.outputTemplate" value="{Timestamp:yyyy-MM-dd HH:mm:ss,fff} [P{ProcessId}/D{AppDomainId}/T{ThreadId}] {Log4NetLevel} {SourceContext} - {Message:lj}{NewLine}{Exception}" /> -->
|
||||
|
||||
<!-- Filters all above sink's to use this expression -->
|
||||
<!-- Common use case is to include SourceType starting with your own namespace -->
|
||||
<!--
|
||||
<add key="serilog:using:FilterExpressions" value="Serilog.Filters.Expressions" />
|
||||
<add key="serilog:filter:ByIncludingOnly.expression" value="StartsWith(SourceContext, 'Umbraco.Core')" />
|
||||
-->
|
||||
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -1,7 +1,5 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.UI.BackOffice
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace Umbraco.Web.UI.BackOffice
|
||||
});
|
||||
|
||||
//Finally initialize Current
|
||||
// TODO: This should be moved to the UmbracoServiceProviderFactory when the container is cross-wired and then don't use the overload above to `out var factory`
|
||||
Current.Initialize(
|
||||
factory.GetInstance<ILogger> (),
|
||||
factory.GetInstance<Configs>(),
|
||||
@@ -77,6 +78,7 @@ namespace Umbraco.Web.UI.BackOffice
|
||||
}
|
||||
app.UseStatusCodePages();
|
||||
app.UseUmbracoCore();
|
||||
app.UseUmbracoRequestLogging();
|
||||
app.UseUmbracoWebsite();
|
||||
app.UseUmbracoBackOffice();
|
||||
app.UseRouting();
|
||||
@@ -90,7 +92,8 @@ namespace Umbraco.Web.UI.BackOffice
|
||||
});
|
||||
endpoints.MapGet("/", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync($"<html><body>Hello World!{Current.Profiler.Render()}</body></html>");
|
||||
var profilerHtml = app.ApplicationServices.GetRequiredService<IProfilerHtml>();
|
||||
await context.Response.WriteAsync($"<html><body>Hello World!{profilerHtml.Render()}</body></html>");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,4 +35,37 @@
|
||||
<Content Remove="App_Data\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Config\serilog.Release.config" />
|
||||
<Content Remove="Config\serilog.user.Release.config" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Config\logviewer.searches.config.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Config\logviewer.searches.config.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Config\serilog.Release.config">
|
||||
<SubType>Designer</SubType>
|
||||
<DependentUpon>serilog.config</DependentUpon>
|
||||
</None>
|
||||
<None Include="Config\serilog.user.Release.config">
|
||||
<SubType>Designer</SubType>
|
||||
<DependentUpon>serilog.user.config</DependentUpon>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Config\serilog.config">
|
||||
<SubType>Designer</SubType>
|
||||
</Content>
|
||||
<Content Update="Config\serilog.user.config">
|
||||
<SubType>Designer</SubType>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<!-- This is used by the default log viewer in the Umbraco backoffice -->
|
||||
<add key="serilog:using:File" value="Umbraco.Infrastructure" />
|
||||
<add key="serilog:write-to:File.formatter" value="Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" />
|
||||
<add key="serilog:write-to:File.path" value="%BASEDIR%\App_Data\Logs\UmbracoTraceLog.%MACHINENAME%..json" />
|
||||
<add key="serilog:write-to:File.path" value="%UMBLOGDIR%\UmbracoTraceLog.%MACHINENAME%..json" />
|
||||
<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" />
|
||||
<add key="serilog:write-to:File.retainedFileCountLimit" value="" /> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<add key="serilog:write-to:File.rollingInterval" value="Day" /> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<!-- Optional TXT log file -->
|
||||
<!--<add key="serilog:using:File" value="Serilog.Sinks.File" /> -->
|
||||
<!--<add key="serilog:write-to:File.path" value="%BASEDIR%\App_Data\Logs\UmbracoTraceLog.%MACHINENAME%..txt" /> -->
|
||||
<!--<add key="serilog:write-to:File.path" value="%UMBLOGDIR%\UmbracoTraceLog.%MACHINENAME%..txt" /> -->
|
||||
<!--<add key="serilog:write-to:File.shared" value="true" /> -->
|
||||
<!--<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" /> -->
|
||||
<!--<add key="serilog:write-to:File.retainedFileCountLimit" value="" /> --> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
|
||||
@@ -248,6 +248,8 @@ namespace Umbraco.Web.Composing
|
||||
|
||||
public static IProfiler Profiler => Factory.GetInstance<IProfiler>();
|
||||
|
||||
public static IProfilerHtml ProfilerHtml => Factory.GetInstance<IProfilerHtml>();
|
||||
|
||||
public static IProfilingLogger ProfilingLogger => Factory.GetInstance<IProfilingLogger>();
|
||||
|
||||
public static AppCaches AppCaches => Factory.GetInstance<AppCaches>();
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Web
|
||||
/// <returns></returns>
|
||||
public static IHtmlString RenderProfiler(this HtmlHelper helper)
|
||||
{
|
||||
return new HtmlString(Current.Profiler.Render());
|
||||
return new HtmlString(Current.ProfilerHtml.Render());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Web.Logging
|
||||
/// <remarks>
|
||||
/// Profiling only runs when the app is in debug mode, see WebRuntime for how this gets created
|
||||
/// </remarks>
|
||||
internal class WebProfiler : IProfiler
|
||||
internal class WebProfiler : IProfiler, IProfilerHtml
|
||||
{
|
||||
private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest";
|
||||
private readonly WebProfilerProvider _provider;
|
||||
|
||||
@@ -25,6 +25,9 @@ using Umbraco.Web.Trees;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.AspNet;
|
||||
using Umbraco.Core.Diagnostics;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Web.Logging;
|
||||
|
||||
namespace Umbraco.Web.Runtime
|
||||
{
|
||||
@@ -50,7 +53,8 @@ namespace Umbraco.Web.Runtime
|
||||
composition.Register<IFilePermissionHelper, FilePermissionHelper>(Lifetime.Singleton);
|
||||
|
||||
|
||||
|
||||
composition.RegisterUnique<IMarchal, FrameworkMarchal>();
|
||||
composition.RegisterUnique<IProfilerHtml, WebProfiler>();
|
||||
|
||||
composition.ComposeWebMappingProfiles();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@@ -12,6 +13,8 @@ using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Logging.Serilog;
|
||||
using Umbraco.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.AspNet;
|
||||
using Umbraco.Web.Hosting;
|
||||
using Umbraco.Web.Logging;
|
||||
@@ -34,12 +37,16 @@ namespace Umbraco.Web
|
||||
var configFactory = new ConfigsFactory();
|
||||
|
||||
var hostingSettings = configFactory.HostingSettings;
|
||||
var coreDebug = configFactory.CoreDebugSettings;
|
||||
var globalSettings = configFactory.GlobalSettings;
|
||||
|
||||
var hostingEnvironment = new AspNetHostingEnvironment(hostingSettings);
|
||||
var loggingConfiguration = new LoggingConfiguration(
|
||||
Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data\\Logs"),
|
||||
Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "config\\serilog.config"),
|
||||
Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "config\\serilog.user.config"));
|
||||
var ioHelper = new IOHelper(hostingEnvironment, globalSettings);
|
||||
var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetSessionManager(), () => _factory?.GetInstance<IRequestCache>(), coreDebug, ioHelper, new FrameworkMarchal());
|
||||
var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, loggingConfiguration);
|
||||
|
||||
var configs = configFactory.Create();
|
||||
|
||||
var backOfficeInfo = new AspNetBackOfficeInfo(globalSettings, ioHelper, logger, configFactory.WebRoutingSettings);
|
||||
@@ -168,6 +175,11 @@ namespace Umbraco.Web
|
||||
Umbraco.Composing.Current.BackOfficeInfo);
|
||||
_factory = Current.Factory = _runtime.Configure(register);
|
||||
|
||||
// now we can add our request based logging enrichers (globally, which is what we were doing in netframework before)
|
||||
LogContext.Push(new HttpSessionIdEnricher(_factory.GetInstance<ISessionIdResolver>()));
|
||||
LogContext.Push(new HttpRequestNumberEnricher(_factory.GetInstance<IRequestCache>()));
|
||||
LogContext.Push(new HttpRequestIdEnricher(_factory.GetInstance<IRequestCache>()));
|
||||
|
||||
_runtime.Start();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user