diff --git a/.gitignore b/.gitignore
index 5f2432313f..d8c3f27d5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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/*
diff --git a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs
index 0bdfe5c425..b653f535fa 100644
--- a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs
+++ b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs
@@ -6,6 +6,10 @@ namespace Umbraco.Core.Hosting
{
string SiteName { get; }
string ApplicationId { get; }
+
+ ///
+ /// Will return the physical path to the root of the application
+ ///
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 ?
+
+ ///
+ /// Maps a virtual path to a physical path to the application's web root
+ ///
+ ///
+ ///
+ ///
+ /// 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.
+ ///
string MapPath(string path);
///
- /// 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
///
/// The virtual path. Must start with either ~/ or / else an exception is thrown.
///
diff --git a/src/Umbraco.Core/Logging/ILoggingConfiguration.cs b/src/Umbraco.Core/Logging/ILoggingConfiguration.cs
new file mode 100644
index 0000000000..47e2d8fa7c
--- /dev/null
+++ b/src/Umbraco.Core/Logging/ILoggingConfiguration.cs
@@ -0,0 +1,13 @@
+namespace Umbraco.Core.Logging
+{
+
+ public interface ILoggingConfiguration
+ {
+ ///
+ /// The physical path where logs are stored
+ ///
+ string LogDirectory { get; }
+ string LogConfigurationFile { get; }
+ string UserLogConfigurationFile { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Logging/IProfiler.cs b/src/Umbraco.Core/Logging/IProfiler.cs
index 1327651197..d855612c95 100644
--- a/src/Umbraco.Core/Logging/IProfiler.cs
+++ b/src/Umbraco.Core/Logging/IProfiler.cs
@@ -2,18 +2,12 @@
namespace Umbraco.Core.Logging
{
+
///
/// Defines the profiling service.
///
public interface IProfiler
{
- ///
- /// Renders the profiling results.
- ///
- /// The profiling results.
- /// Generally used for HTML rendering.
- string Render();
-
///
/// Gets an that will time the code between its creation and disposal.
///
diff --git a/src/Umbraco.Core/Logging/IProfilerHtml.cs b/src/Umbraco.Core/Logging/IProfilerHtml.cs
new file mode 100644
index 0000000000..4f9ee62e0b
--- /dev/null
+++ b/src/Umbraco.Core/Logging/IProfilerHtml.cs
@@ -0,0 +1,15 @@
+namespace Umbraco.Core.Logging
+{
+ ///
+ /// Used to render a profiler in a web page
+ ///
+ public interface IProfilerHtml
+ {
+ ///
+ /// Renders the profiling results.
+ ///
+ /// The profiling results.
+ /// Generally used for HTML rendering.
+ string Render();
+ }
+}
diff --git a/src/Umbraco.Core/Logging/LogProfiler.cs b/src/Umbraco.Core/Logging/LogProfiler.cs
index 294f92dad3..a1d2a2e61f 100644
--- a/src/Umbraco.Core/Logging/LogProfiler.cs
+++ b/src/Umbraco.Core/Logging/LogProfiler.cs
@@ -15,12 +15,6 @@ namespace Umbraco.Core.Logging
_logger = logger;
}
- ///
- public string Render()
- {
- return string.Empty;
- }
-
///
public IDisposable Step(string name)
{
diff --git a/src/Umbraco.Core/Logging/LoggingConfiguration.cs b/src/Umbraco.Core/Logging/LoggingConfiguration.cs
new file mode 100644
index 0000000000..c657c9d430
--- /dev/null
+++ b/src/Umbraco.Core/Logging/LoggingConfiguration.cs
@@ -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; }
+ }
+}
diff --git a/src/Umbraco.Core/Logging/VoidProfiler.cs b/src/Umbraco.Core/Logging/VoidProfiler.cs
index 51bec521a3..d771fd7630 100644
--- a/src/Umbraco.Core/Logging/VoidProfiler.cs
+++ b/src/Umbraco.Core/Logging/VoidProfiler.cs
@@ -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;
diff --git a/src/Umbraco.Core/Net/NullSessionIdResolver.cs b/src/Umbraco.Core/Net/NullSessionIdResolver.cs
new file mode 100644
index 0000000000..6bfa578268
--- /dev/null
+++ b/src/Umbraco.Core/Net/NullSessionIdResolver.cs
@@ -0,0 +1,7 @@
+namespace Umbraco.Net
+{
+ public class NullSessionIdResolver : ISessionIdResolver
+ {
+ public string SessionId => null;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
index 2099778185..ab758d42af 100644
--- a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
+++ b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
@@ -13,8 +13,17 @@ namespace Umbraco.Core.Composing
///
///
public static IHostBuilder UseUmbraco(this IHostBuilder builder)
- => builder.UseUmbraco(new UmbracoServiceProviderFactory());
+ {
+ return builder
+ .UseUmbraco(new UmbracoServiceProviderFactory());
+ }
+ ///
+ /// Assigns a custom service provider factory to use Umbraco's container
+ ///
+ ///
+ ///
+ ///
public static IHostBuilder UseUmbraco(this IHostBuilder builder, UmbracoServiceProviderFactory umbracoServiceProviderFactory)
=> builder.UseServiceProviderFactory(umbracoServiceProviderFactory);
}
diff --git a/src/Umbraco.Infrastructure/Diagnostics/MiniDump.cs b/src/Umbraco.Infrastructure/Diagnostics/MiniDump.cs
index 9bc0b1c3fb..57e9b5204b 100644
--- a/src/Umbraco.Infrastructure/Diagnostics/MiniDump.cs
+++ b/src/Umbraco.Infrastructure/Diagnostics/MiniDump.cs
@@ -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;
diff --git a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs
index 4640007e1a..712ff85e16 100644
--- a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs
+++ b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs
@@ -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 MinimalLogger = new Lazy(() => 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);
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs
index 45468ace9f..704e80d302 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestIdEnricher.cs
@@ -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
///
- internal class HttpRequestIdEnricher : ILogEventEnricher
+ public class HttpRequestIdEnricher : ILogEventEnricher
{
- private readonly Func _requestCacheGetter;
+ private readonly IRequestCache _requestCache;
- public HttpRequestIdEnricher(Func requestCacheGetter)
+ public HttpRequestIdEnricher(IRequestCache requestCache)
{
- _requestCacheGetter = requestCacheGetter;
+ _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
}
///
@@ -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));
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs
index 08eb6b93f0..20643ff539 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpRequestNumberEnricher.cs
@@ -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
///
- internal class HttpRequestNumberEnricher : ILogEventEnricher
+ public class HttpRequestNumberEnricher : ILogEventEnricher
{
- private readonly Func _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 requestCacheGetter)
+ public HttpRequestNumberEnricher(IRequestCache requestCache)
{
- _requestCacheGetter = requestCacheGetter;
+ _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
}
///
@@ -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));
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs
index 1558cdcf21..19572b5b42 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/HttpSessionIdEnricher.cs
@@ -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
///
- internal class HttpSessionIdEnricher : ILogEventEnricher
+ public class HttpSessionIdEnricher : ILogEventEnricher
{
private readonly ISessionIdResolver _sessionIdResolver;
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs
new file mode 100644
index 0000000000..1f495d3a50
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs
@@ -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
+{
+ ///
+ /// Enriches the log if there are ThreadAbort exceptions and will automatically create a minidump if it can
+ ///
+ 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");
+ }
+
+
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs
index f4e8f85281..dfcc401ea3 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs
@@ -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
///
/// A Serilog LoggerConfiguration
- ///
- public static LoggerConfiguration MinimalConfiguration(this LoggerConfiguration logConfig, IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func requestCacheGetter)
+ ///
+ ///
+ 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()
- .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/
///
/// A Serilog LoggerConfiguration
+ ///
/// The log level you wish the JSON file to collect - default is Verbose (highest)
/// The number of days to keep log files. Default is set to null which means all logs are kept
- 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/
///
/// A Serilog LoggerConfiguration
+ ///
/// The log level you wish the JSON file to collect - default is Verbose (highest)
/// The number of days to keep log files. Default is set to null which means all logs are kept
- 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
///
/// A Serilog LoggerConfiguration
- public static LoggerConfiguration ReadFromConfigFile(this LoggerConfiguration logConfig)
+ ///
+ 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
///
/// A Serilog LoggerConfiguration
- public static LoggerConfiguration ReadFromUserConfigFile(this LoggerConfiguration logConfig)
+ ///
+ 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;
}
+
}
}
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs
new file mode 100644
index 0000000000..18b417d428
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs
@@ -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();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
index bb77869e28..38af9554ab 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
@@ -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
{
+
///
/// Implements on top of Serilog.
///
public class SerilogLogger : ILogger, IDisposable
{
- private readonly ICoreDebugSettings _coreDebugSettings;
- private readonly IIOHelper _ioHelper;
- private readonly IMarchal _marchal;
+ public global::Serilog.ILogger SerilogLog { get; }
///
/// Initialize a new instance of the class with a configuration file.
///
///
- 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();
}
///
/// Creates a logger with some pre-defined configuration and remainder from config file
///
/// Used by UmbracoApplicationBase to get its logger.
- public static SerilogLogger CreateWithDefaultConfiguration(IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func 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);
}
///
/// Gets a contextualized logger.
///
private global::Serilog.ILogger LoggerFor(Type reporting)
- => Log.Logger.ForContext(reporting);
+ => SerilogLog.ForContext(reporting);
///
/// Maps Umbraco's log level to Serilog's.
@@ -99,8 +83,7 @@ namespace Umbraco.Core.Logging.Serilog
///
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
///
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);
}
///
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);
- }
-
///
public void Warn(Type reporting, string message)
{
@@ -289,7 +210,7 @@ namespace Umbraco.Core.Logging.Serilog
public void Dispose()
{
- Log.CloseAndFlush();
+ SerilogLog.DisposeIfDisposable();
}
}
}
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs b/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs
index dbdd7842ba..6763b0ebbb 100644
--- a/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs
@@ -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
{
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewerConfig.cs b/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewerConfig.cs
new file mode 100644
index 0000000000..14f35361e6
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewerConfig.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Logging.Viewer
+{
+ public interface ILogViewerConfig
+ {
+ IReadOnlyList GetSavedSearches();
+ IReadOnlyList AddSavedSearch(string name, string query);
+ IReadOnlyList DeleteSavedSearch(string name, string query);
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs
index 79680a3d53..ee115be325 100644
--- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs
@@ -9,7 +9,8 @@ namespace Umbraco.Core.Logging.Viewer
{
public void Compose(Composition composition)
{
- composition.SetLogViewer(factory => new JsonLogViewer(composition.Logger, factory.GetInstance()));
+ composition.RegisterUnique();
+ composition.SetLogViewer();
}
}
}
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs
new file mode 100644
index 0000000000..5511cd87c7
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs
@@ -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 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(rawJson);
+ }
+
+ public IReadOnlyList 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 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("[]");
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/JsonLogViewer.cs b/src/Umbraco.Infrastructure/Logging/Viewer/SerilogJsonLogViewer.cs
similarity index 87%
rename from src/Umbraco.Infrastructure/Logging/Viewer/JsonLogViewer.cs
rename to src/Umbraco.Infrastructure/Logging/Viewer/SerilogJsonLogViewer.cs
index aea1c8fae4..366a0fb9de 100644
--- a/src/Umbraco.Infrastructure/Logging/Viewer/JsonLogViewer.cs
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/SerilogJsonLogViewer.cs
@@ -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();
- //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(ex, "Unable to parse a line in the JSON log file");
+ _logger.Error(ex, "Unable to parse a line in the JSON log file");
evt = null;
return true;
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerSourceBase.cs b/src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs
similarity index 60%
rename from src/Umbraco.Infrastructure/Logging/Viewer/LogViewerSourceBase.cs
rename to src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs
index 4cc70eaf42..7c8503a37e 100644
--- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerSourceBase.cs
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs
@@ -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 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(rawJson);
- }
+ => _logViewerConfig.GetSavedSearches();
public virtual IReadOnlyList 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 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
///
public string GetLogLevel()
{
- var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast().Where(Log.Logger.IsEnabled)?.Min() ?? null;
+ var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast().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);
- }
- }
+
}
}
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
index 1ea08e3118..19d3716e1c 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
@@ -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();
-
-
-
-
}
}
}
diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs
index 42b1e6c0dd..21b1f66395 100644
--- a/src/Umbraco.Tests.Common/TestHelperBase.cs
+++ b/src/Umbraco.Tests.Common/TestHelperBase.cs
@@ -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"));
+ }
}
}
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs
index 6430291bc2..9f29b14858 100644
--- a/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs
+++ b/src/Umbraco.Tests.Integration/Implementations/TestHostingEnvironment.cs
@@ -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
diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs
index aa364210d0..ad040c8ef1 100644
--- a/src/Umbraco.Tests.Integration/RuntimeTests.cs
+++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs
@@ -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();
diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
index 41eb082dc2..0137850408 100644
--- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
+++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
@@ -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();
diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs
index 87cc19a2c6..0a193b4446 100644
--- a/src/Umbraco.Tests/Logging/LogviewerTests.cs
+++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs
@@ -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();
- _logViewer = new JsonLogViewer(logger, ioHelper, logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath);
+ var logViewerConfig = new LogViewerConfig(hostingEnv);
+ _logViewer = new SerilogJsonLogViewer(logger, logViewerConfig, loggingConfiguration, Log.Logger);
}
[OneTimeTearDown]
diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
index 0b90457b1e..f63c56b64e 100644
--- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
+++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
@@ -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(Lifetime.Singleton);
+ container.Register(Lifetime.Singleton);
var factory = base.Configure(container);
return factory;
diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs
index 39cac6e24f..ea0f9cc44f 100644
--- a/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs
+++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestProfiler.cs
@@ -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;
diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs
index 7eca49183d..6fcba1ba1a 100644
--- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs
@@ -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();
diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
index 901192c609..80f6ab9c9e 100644
--- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
+++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs
@@ -264,7 +264,7 @@ namespace Umbraco.Tests.Testing
profiler = Mock.Of();
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:
diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs
index a6c544062e..0d55fd99d7 100644
--- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs
+++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs
@@ -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));
}
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoApplicationBuilderExtensions.cs
index ddf06e6532..a27113e881 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoApplicationBuilderExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoApplicationBuilderExtensions.cs
@@ -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();
+ 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();
+
+ return app;
+ }
+
public static IApplicationBuilder UseUmbracoRuntimeMinification(this IApplicationBuilder app)
{
if (app == null) throw new ArgumentNullException(nameof(app));
diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs
index decfcfa660..b3ec11c241 100644
--- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs
+++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs
@@ -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;
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
index caba2cd825..3facf1b77f 100644
--- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
@@ -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(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
///
///
///
+ ///
+ ///
///
///
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();
- 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(), coreDebug, ioHelper,
- new AspNetCoreMarchal());
+ logger = AddLogger(services, hostingEnvironment, loggingConfiguration);
backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings);
- profiler = GetWebProfiler(hostingEnvironment, httpContextAccessor);
+ profiler = GetWebProfiler(hostingEnvironment);
return services;
}
+ ///
+ /// Create and configure the logger
+ ///
+ ///
+ 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(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(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;
diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs
new file mode 100644
index 0000000000..0e2158c939
--- /dev/null
+++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs
index 93461fc1d5..e8695f3c9c 100644
--- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs
+++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs
@@ -22,5 +22,4 @@ namespace Umbraco.Web.Common.Middleware
_umbracoRequestLifetimeManager.EndRequest(context);
}
}
-
}
diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs
index 79c7d3ec25..8af2824c03 100644
--- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs
+++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs
@@ -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();
composition.RegisterMultipleUnique();
+
+ composition.RegisterUnique();
+
+ composition.RegisterUnique();
}
}
}
diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs
index bdbc6f164d..958e134bab 100644
--- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs
+++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs
@@ -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;
- }
-
- ///
- ///
- ///
- ///
- /// 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.
- ///
- 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{ 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);
diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs
index 688a3e5c28..523faf2da5 100644
--- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs
+++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs
@@ -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, ICoreComposer
{
+ public override void Compose(Composition composition)
+ {
+ base.Compose(composition);
+
+ composition.RegisterUnique();
+ }
}
}
diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerHtml.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerHtml.cs
new file mode 100644
index 0000000000..9e989d6b5c
--- /dev/null
+++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerHtml.cs
@@ -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;
+ }
+
+ ///
+ ///
+ /// 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.
+ ///
+ 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 { 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;
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
index 7203c4ba29..711659730f 100644
--- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
+++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/Umbraco.Web.UI.NetCore/Config/logviewer.searches.config.js b/src/Umbraco.Web.UI.NetCore/Config/logviewer.searches.config.js
new file mode 100644
index 0000000000..345fe23764
--- /dev/null
+++ b/src/Umbraco.Web.UI.NetCore/Config/logviewer.searches.config.js
@@ -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%'"
+ }
+]
diff --git a/src/Umbraco.Web.UI.NetCore/Config/serilog.Release.config b/src/Umbraco.Web.UI.NetCore/Config/serilog.Release.config
new file mode 100644
index 0000000000..9aca408b36
--- /dev/null
+++ b/src/Umbraco.Web.UI.NetCore/Config/serilog.Release.config
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.NetCore/Config/serilog.config b/src/Umbraco.Web.UI.NetCore/Config/serilog.config
new file mode 100644
index 0000000000..9aca408b36
--- /dev/null
+++ b/src/Umbraco.Web.UI.NetCore/Config/serilog.config
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.NetCore/Config/serilog.user.Release.config b/src/Umbraco.Web.UI.NetCore/Config/serilog.user.Release.config
new file mode 100644
index 0000000000..8f207406e3
--- /dev/null
+++ b/src/Umbraco.Web.UI.NetCore/Config/serilog.user.Release.config
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.NetCore/Config/serilog.user.config b/src/Umbraco.Web.UI.NetCore/Config/serilog.user.config
new file mode 100644
index 0000000000..8f207406e3
--- /dev/null
+++ b/src/Umbraco.Web.UI.NetCore/Config/serilog.user.config
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.NetCore/Program.cs b/src/Umbraco.Web.UI.NetCore/Program.cs
index f0504d77e3..1151f16be8 100644
--- a/src/Umbraco.Web.UI.NetCore/Program.cs
+++ b/src/Umbraco.Web.UI.NetCore/Program.cs
@@ -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
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index b986ac2c17..37440006aa 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -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 (),
factory.GetInstance(),
@@ -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($"Hello World!{Current.Profiler.Render()}");
+ var profilerHtml = app.ApplicationServices.GetRequiredService();
+ await context.Response.WriteAsync($"Hello World!{profilerHtml.Render()}");
});
});
}
diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj
index 72f29f3c4b..59bb76bb7a 100644
--- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj
+++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj
@@ -35,4 +35,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+ serilog.config
+
+
+ Designer
+ serilog.user.config
+
+
+
+
+
+ Designer
+
+
+ Designer
+
+
+
diff --git a/src/Umbraco.Web.UI/config/serilog.Release.config b/src/Umbraco.Web.UI/config/serilog.Release.config
index e3cf52b3c5..9aca408b36 100644
--- a/src/Umbraco.Web.UI/config/serilog.Release.config
+++ b/src/Umbraco.Web.UI/config/serilog.Release.config
@@ -19,7 +19,7 @@
-
+
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs
index f9f056ff32..1ed217cc78 100644
--- a/src/Umbraco.Web/Composing/Current.cs
+++ b/src/Umbraco.Web/Composing/Current.cs
@@ -248,6 +248,8 @@ namespace Umbraco.Web.Composing
public static IProfiler Profiler => Factory.GetInstance();
+ public static IProfilerHtml ProfilerHtml => Factory.GetInstance();
+
public static IProfilingLogger ProfilingLogger => Factory.GetInstance();
public static AppCaches AppCaches => Factory.GetInstance();
diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs
index 6c207dd15a..2795fe66c6 100644
--- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs
+++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs
@@ -30,7 +30,7 @@ namespace Umbraco.Web
///
public static IHtmlString RenderProfiler(this HtmlHelper helper)
{
- return new HtmlString(Current.Profiler.Render());
+ return new HtmlString(Current.ProfilerHtml.Render());
}
///
diff --git a/src/Umbraco.Web/Logging/WebProfiler.cs b/src/Umbraco.Web/Logging/WebProfiler.cs
index e390950c0b..6f15be7ecd 100755
--- a/src/Umbraco.Web/Logging/WebProfiler.cs
+++ b/src/Umbraco.Web/Logging/WebProfiler.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Web.Logging
///
/// Profiling only runs when the app is in debug mode, see WebRuntime for how this gets created
///
- internal class WebProfiler : IProfiler
+ internal class WebProfiler : IProfiler, IProfilerHtml
{
private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest";
private readonly WebProfilerProvider _provider;
diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
index ec3b463d4c..5b59b632eb 100644
--- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs
+++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
@@ -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(Lifetime.Singleton);
-
+ composition.RegisterUnique();
+ composition.RegisterUnique();
composition.ComposeWebMappingProfiles();
diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs
index d884366bf1..1f90bc7d13 100644
--- a/src/Umbraco.Web/UmbracoApplicationBase.cs
+++ b/src/Umbraco.Web/UmbracoApplicationBase.cs
@@ -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(), 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()));
+ LogContext.Push(new HttpRequestNumberEnricher(_factory.GetInstance()));
+ LogContext.Push(new HttpRequestIdEnricher(_factory.GetInstance()));
+
_runtime.Start();
}