AB4227 - Moved Logging

This commit is contained in:
Bjarke Berg
2019-12-18 12:27:14 +01:00
parent 897cb63ad6
commit 38519b2bab
38 changed files with 63 additions and 54 deletions

View File

@@ -1,319 +0,0 @@
using System;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Dictionary;
using Umbraco.Core.IO;
using Umbraco.Core.Logging.Viewer;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PackageActions;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
#region Collection Builders
/// <summary>
/// Gets the cache refreshers collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static CacheRefresherCollectionBuilder CacheRefreshers(this Composition composition)
=> composition.WithCollectionBuilder<CacheRefresherCollectionBuilder>();
/// <summary>
/// Gets the mappers collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static MapperCollectionBuilder Mappers(this Composition composition)
=> composition.WithCollectionBuilder<MapperCollectionBuilder>();
/// <summary>
/// Gets the package actions collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
internal static PackageActionCollectionBuilder PackageActions(this Composition composition)
=> composition.WithCollectionBuilder<PackageActionCollectionBuilder>();
/// <summary>
/// Gets the data editor collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static DataEditorCollectionBuilder DataEditors(this Composition composition)
=> composition.WithCollectionBuilder<DataEditorCollectionBuilder>();
/// <summary>
/// Gets the data value reference factory collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this Composition composition)
=> composition.WithCollectionBuilder<DataValueReferenceFactoryCollectionBuilder>();
/// <summary>
/// Gets the property value converters collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this Composition composition)
=> composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
/// <summary>
/// Gets the url segment providers collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition)
=> composition.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>();
/// <summary>
/// Gets the validators collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this Composition composition)
=> composition.WithCollectionBuilder<ManifestValueValidatorCollectionBuilder>();
/// <summary>
/// Gets the manifest filter collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static ManifestFilterCollectionBuilder ManifestFilters(this Composition composition)
=> composition.WithCollectionBuilder<ManifestFilterCollectionBuilder>();
#endregion
#region Uniques
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="composition">The composition.</param>
public static void SetCultureDictionaryFactory<T>(this Composition composition)
where T : ICultureDictionaryFactory
{
composition.RegisterUnique<ICultureDictionaryFactory, T>();
}
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating a culture dictionary factory.</param>
public static void SetCultureDictionaryFactory(this Composition composition, Func<IFactory, ICultureDictionaryFactory> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A factory.</param>
public static void SetCultureDictionaryFactory(this Composition composition, ICultureDictionaryFactory factory)
{
composition.RegisterUnique(_ => factory);
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="composition">The composition.</param>
public static void SetPublishedContentModelFactory<T>(this Composition composition)
where T : IPublishedModelFactory
{
composition.RegisterUnique<IPublishedModelFactory, T>();
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating a published content model factory.</param>
public static void SetPublishedContentModelFactory(this Composition composition, Func<IFactory, IPublishedModelFactory> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A published content model factory.</param>
public static void SetPublishedContentModelFactory(this Composition composition, IPublishedModelFactory factory)
{
composition.RegisterUnique(_ => factory);
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="composition">The composition.</param>
public static void SetServerRegistrar<T>(this Composition composition)
where T : IServerRegistrar
{
composition.RegisterUnique<IServerRegistrar, T>();
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating a server registrar.</param>
public static void SetServerRegistrar(this Composition composition, Func<IFactory, IServerRegistrar> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="registrar">A server registrar.</param>
public static void SetServerRegistrar(this Composition composition, IServerRegistrar registrar)
{
composition.RegisterUnique(_ => registrar);
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="composition">The composition.</param>
public static void SetServerMessenger<T>(this Composition composition)
where T : IServerMessenger
{
composition.RegisterUnique<IServerMessenger, T>();
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating a server messenger.</param>
public static void SetServerMessenger(this Composition composition, Func<IFactory, IServerMessenger> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="registrar">A server messenger.</param>
public static void SetServerMessenger(this Composition composition, IServerMessenger registrar)
{
composition.RegisterUnique(_ => registrar);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating the options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this Composition composition, Func<IFactory, DatabaseServerMessengerOptions> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="options">Options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this Composition composition, DatabaseServerMessengerOptions options)
{
composition.RegisterUnique(_ => options);
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <typeparam name="T">The type of the short string helper.</typeparam>
/// <param name="composition">The composition.</param>
public static void SetShortStringHelper<T>(this Composition composition)
where T : IShortStringHelper
{
composition.RegisterUnique<IShortStringHelper, T>();
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating a short string helper.</param>
public static void SetShortStringHelper(this Composition composition, Func<IFactory, IShortStringHelper> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="helper">A short string helper.</param>
public static void SetShortStringHelper(this Composition composition, IShortStringHelper helper)
{
composition.RegisterUnique(_ => helper);
}
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
public static void SetMediaFileSystem(this Composition composition, Func<IFactory, IFileSystem> filesystemFactory)
=> composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(filesystemFactory);
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
public static void SetMediaFileSystem(this Composition composition, Func<IFileSystem> filesystemFactory)
=> composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(_ => filesystemFactory());
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <typeparam name="T">The type of the log viewer.</typeparam>
/// <param name="composition">The composition.</param>
public static void SetLogViewer<T>(this Composition composition)
where T : ILogViewer
{
composition.RegisterUnique<ILogViewer, T>();
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating a log viewer.</param>
public static void SetLogViewer(this Composition composition, Func<IFactory, ILogViewer> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="helper">A log viewer.</param>
public static void SetLogViewer(this Composition composition, ILogViewer viewer)
{
composition.RegisterUnique(_ => viewer);
}
#endregion
}
}

View File

@@ -1,48 +0,0 @@
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
/// <summary>
/// Registers essential services.
/// </summary>
public static void RegisterEssentials(this Composition composition,
ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger,
IMainDom mainDom,
AppCaches appCaches,
IUmbracoDatabaseFactory databaseFactory,
TypeLoader typeLoader,
IRuntimeState state,
ITypeFinder typeFinder,
IIOHelper ioHelper,
IUmbracoVersion umbracoVersion,
IDbProviderFactoryCreator dbProviderFactoryCreator,
IBulkSqlInsertProvider bulkSqlInsertProvider)
{
composition.RegisterUnique(logger);
composition.RegisterUnique(profiler);
composition.RegisterUnique(profilingLogger);
composition.RegisterUnique(mainDom);
composition.RegisterUnique(appCaches);
composition.RegisterUnique(appCaches.RequestCache);
composition.RegisterUnique(databaseFactory);
composition.RegisterUnique(factory => factory.GetInstance<IUmbracoDatabaseFactory>().SqlContext);
composition.RegisterUnique(typeLoader);
composition.RegisterUnique(state);
composition.RegisterUnique(typeFinder);
composition.RegisterUnique(ioHelper);
composition.RegisterUnique(umbracoVersion);
composition.RegisterUnique(dbProviderFactoryCreator);
composition.RegisterUnique(bulkSqlInsertProvider);
}
}
}

View File

@@ -1,47 +0,0 @@
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem, TImplementing>(this Composition composition)
where TImplementing : FileSystemWrapper, TFileSystem
where TFileSystem : class
{
composition.RegisterUnique<TFileSystem>(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supporting.For<TFileSystem>());
});
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem>(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supporting.For<TFileSystem>());
});
}
}
}

View File

@@ -1,139 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
namespace Umbraco.Core.Diagnostics
{
// taken from https://blogs.msdn.microsoft.com/dondu/2010/10/24/writing-minidumps-in-c/
// and https://blogs.msdn.microsoft.com/dondu/2010/10/31/writing-minidumps-from-exceptions-in-c/
// which itself got it from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/
internal static class MiniDump
{
private static readonly object LockO = new object();
[Flags]
public enum Option : uint
{
// From dbghelp.h:
Normal = 0x00000000,
WithDataSegs = 0x00000001,
WithFullMemory = 0x00000002,
WithHandleData = 0x00000004,
FilterMemory = 0x00000008,
ScanMemory = 0x00000010,
WithUnloadedModules = 0x00000020,
WithIndirectlyReferencedMemory = 0x00000040,
FilterModulePaths = 0x00000080,
WithProcessThreadData = 0x00000100,
WithPrivateReadWriteMemory = 0x00000200,
WithoutOptionalData = 0x00000400,
WithFullMemoryInfo = 0x00000800,
WithThreadInfo = 0x00001000,
WithCodeSegs = 0x00002000,
WithoutAuxiliaryState = 0x00004000,
WithFullAuxiliaryState = 0x00008000,
WithPrivateWriteCopyMemory = 0x00010000,
IgnoreInaccessibleMemory = 0x00020000,
ValidTypeFlags = 0x0003ffff,
}
//typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
// DWORD ThreadId;
// PEXCEPTION_POINTERS ExceptionPointers;
// BOOL ClientPointers;
//} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
[StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64!
public struct MiniDumpExceptionInformation
{
public uint ThreadId;
public IntPtr ExceptionPointers;
[MarshalAs(UnmanagedType.Bool)]
public bool ClientPointers;
}
//BOOL
//WINAPI
//MiniDumpWriteDump(
// __in HANDLE hProcess,
// __in DWORD ProcessId,
// __in HANDLE hFile,
// __in MINIDUMP_TYPE DumpType,
// __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
// __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
// __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam
// );
// Overload requiring MiniDumpExceptionInformation
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);
// Overload supporting MiniDumpExceptionInformation == NULL
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
[DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
private static extern uint GetCurrentThreadId();
private static bool Write(SafeHandle fileHandle, Option options, bool withException = false)
{
var currentProcess = Process.GetCurrentProcess();
var currentProcessHandle = currentProcess.Handle;
var currentProcessId = (uint)currentProcess.Id;
MiniDumpExceptionInformation exp;
exp.ThreadId = GetCurrentThreadId();
exp.ClientPointers = false;
exp.ExceptionPointers = IntPtr.Zero;
if (withException)
exp.ExceptionPointers = Marshal.GetExceptionPointers();
var bRet = exp.ExceptionPointers == IntPtr.Zero
? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
: MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, ref exp, IntPtr.Zero, IntPtr.Zero);
return bRet;
}
public static bool Dump(Option options = Option.WithFullMemory, bool withException = false)
{
lock (LockO)
{
// work around "stack trace is not available while minidump debugging",
// by making sure a local var (that we can inspect) contains the stack trace.
// getting the call stack before it is unwound would require a special exception
// filter everywhere in our code = not!
var stacktrace = withException ? Environment.StackTrace : string.Empty;
var ioHelper = Current.Factory.GetInstance<IIOHelper>();
var filepath = ioHelper.MapPath("~/App_Data/MiniDump");
if (Directory.Exists(filepath) == false)
Directory.CreateDirectory(filepath);
var filename = Path.Combine(filepath, $"{DateTime.UtcNow:yyyyMMddTHHmmss}.{Guid.NewGuid().ToString("N").Substring(0, 4)}.dmp");
using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
{
return Write(stream.SafeFileHandle, options, withException);
}
}
}
public static bool OkToDump()
{
lock (LockO)
{
var ioHelper = Current.Factory.GetInstance<IIOHelper>();
var filepath = ioHelper.MapPath("~/App_Data/MiniDump");
if (Directory.Exists(filepath) == false) return true;
var count = Directory.GetFiles(filepath, "*.dmp").Length;
return count < 8;
}
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Logging
{
public static class LogHttpRequest
{
static readonly string RequestIdItemName = typeof(LogHttpRequest).Name + "+RequestId";
/// <summary>
/// Retrieve the id assigned to the currently-executing HTTP request, if any.
/// </summary>
/// <param name="requestId">The request id.</param>
/// <param name="requestCache"></param>
/// <returns><c>true</c> if there is a request in progress; <c>false</c> otherwise.</returns>
public static bool TryGetCurrentHttpRequestId(out Guid requestId, IRequestCache requestCache)
{
var requestIdItem = requestCache.Get(RequestIdItemName, () => Guid.NewGuid());
requestId = (Guid)requestIdItem;
return true;
}
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using Serilog;
using Serilog.Events;
using Serilog.Parsing;
namespace Umbraco.Core.Logging
{
public class MessageTemplates : IMessageTemplates
{
// Umbraco now uses Message Templates (https://messagetemplates.org/) for logging, which means
// we cannot plainly use string.Format() to format them. There is a work-in-progress C# lib,
// derived from Serilog, which should help (https://github.com/messagetemplates/messagetemplates-csharp)
// but it only has a pre-release NuGet package. So, we've got to use Serilog's code, which
// means we cannot get rid of Serilog entirely. We may want to revisit this at some point.
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;
var bound = logger.BindMessageTemplate(messageTemplate, args, out var parsedTemplate, out var boundProperties);
if (!bound)
throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args.");
var values = boundProperties.ToDictionary(x => x.Name, x => x.Value);
// this ends up putting every string parameter between quotes
//return parsedTemplate.Render(values);
// this does not
var tw = new StringWriter();
foreach (var t in parsedTemplate.Tokens)
{
if (t is PropertyToken pt &&
values.TryGetValue(pt.PropertyName, out var propVal) &&
(propVal as ScalarValue)?.Value is string s)
tw.Write(s);
else
t.Render(values, tw);
}
return tw.ToString();
}
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Diagnostics;
namespace Umbraco.Core.Logging
{
internal class OwinLogger : Microsoft.Owin.Logging.ILogger
{
private readonly ILogger _logger;
private readonly Lazy<Type> _type;
public OwinLogger(ILogger logger, Lazy<Type> type)
{
_logger = logger;
_type = type;
}
/// <summary>
/// Aggregates most logging patterns to a single method. This must be compatible with the Func representation in the OWIN environment.
/// To check IsEnabled call WriteCore with only TraceEventType and check the return value, no event will be written.
/// </summary>
/// <param name="eventType"/><param name="eventId"/><param name="state"/><param name="exception"/><param name="formatter"/>
/// <returns/>
public bool WriteCore(TraceEventType eventType, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
{
if (state == null) state = "";
switch (eventType)
{
case TraceEventType.Critical:
_logger.Fatal(_type.Value, exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Error:
_logger.Error(_type.Value, exception, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Warning:
_logger.Warn(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Information:
_logger.Info(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Verbose:
_logger.Debug(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Start:
_logger.Debug(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Stop:
_logger.Debug(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Suspend:
_logger.Debug(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Resume:
_logger.Debug(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
case TraceEventType.Transfer:
_logger.Debug(_type.Value, "[{EventType}] Event Id: {EventId}, State: {State}", eventType, eventId, state);
return true;
default:
throw new ArgumentOutOfRangeException("eventType");
}
}
}
}

View File

@@ -1,19 +0,0 @@
using System;
using Microsoft.Owin.Logging;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Logging
{
internal class OwinLoggerFactory : ILoggerFactory
{
/// <summary>
/// Creates a new ILogger instance of the given name.
/// </summary>
/// <param name="name"/>
/// <returns/>
public Microsoft.Owin.Logging.ILogger Create(string name)
{
return new OwinLogger(Current.Logger, new Lazy<Type>(() => Type.GetType(name) ?? typeof (OwinLogger)));
}
}
}

View File

@@ -1,48 +0,0 @@
using System;
using Serilog.Core;
using Serilog.Events;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Logging.Serilog.Enrichers
{
/// <summary>
/// Enrich log events with a HttpRequestId GUID.
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestIdEnricher.cs
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
/// </summary>
internal class HttpRequestIdEnricher : ILogEventEnricher
{
private readonly Func<IRequestCache> _requestCacheGetter;
public HttpRequestIdEnricher(Func<IRequestCache> requestCacheGetter)
{
_requestCacheGetter = requestCacheGetter;
}
/// <summary>
/// The property name added to enriched log events.
/// </summary>
public const string HttpRequestIdPropertyName = "HttpRequestId";
/// <summary>
/// Enrich the log event with an id assigned to the currently-executing HTTP request, if any.
/// </summary>
/// <param name="logEvent">The log event to enrich.</param>
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
var requestCache = _requestCacheGetter();
if(requestCache is null) return;
Guid requestId;
if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId, requestCache))
return;
var requestIdProperty = new LogEventProperty(HttpRequestIdPropertyName, new ScalarValue(requestId));
logEvent.AddPropertyIfAbsent(requestIdProperty);
}
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.Threading;
using Serilog.Core;
using Serilog.Events;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Logging.Serilog.Enrichers
{
/// <summary>
/// Enrich log events with a HttpRequestNumber unique within the current
/// logging session.
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestNumberEnricher.cs
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
/// </summary>
internal class HttpRequestNumberEnricher : ILogEventEnricher
{
private readonly Func<IRequestCache> _requestCacheGetter;
private static int _lastRequestNumber;
private static readonly string _requestNumberItemName = typeof(HttpRequestNumberEnricher).Name + "+RequestNumber";
/// <summary>
/// The property name added to enriched log events.
/// </summary>
private const string _httpRequestNumberPropertyName = "HttpRequestNumber";
public HttpRequestNumberEnricher(Func<IRequestCache> requestCacheGetter)
{
_requestCacheGetter = requestCacheGetter;
}
/// <summary>
/// Enrich the log event with the number assigned to the currently-executing HTTP request, if any.
/// </summary>
/// <param name="logEvent">The log event to enrich.</param>
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
var requestCache = _requestCacheGetter();
if (requestCache is null) return;
var requestNumber = requestCache.Get(_requestNumberItemName,
() => Interlocked.Increment(ref _lastRequestNumber));
var requestNumberProperty = new LogEventProperty(_httpRequestNumberPropertyName, new ScalarValue(requestNumber));
logEvent.AddPropertyIfAbsent(requestNumberProperty);
}
}
}

View File

@@ -1,43 +0,0 @@
using Serilog.Core;
using Serilog.Events;
using System;
using Umbraco.Net;
namespace Umbraco.Core.Logging.Serilog.Enrichers
{
/// <summary>
/// Enrich log events with the HttpSessionId property.
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpSessionIdEnricher.cs
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
/// </summary>
internal class HttpSessionIdEnricher : ILogEventEnricher
{
private readonly ISessionIdResolver _sessionIdResolver;
public HttpSessionIdEnricher(ISessionIdResolver sessionIdResolver)
{
_sessionIdResolver = sessionIdResolver;
}
/// <summary>
/// The property name added to enriched log events.
/// </summary>
public const string HttpSessionIdPropertyName = "HttpSessionId";
/// <summary>
/// Enrich the log event with the current ASP.NET session id, if sessions are enabled.</summary>
/// <param name="logEvent">The log event to enrich.</param>
/// <param name="propertyFactory">Factory for creating new properties to add to the event.</param>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
var sessionId = _sessionIdResolver.SessionId;
if (sessionId is null)
return;
var sessionIdProperty = new LogEventProperty(HttpSessionIdPropertyName, new ScalarValue(sessionId));
logEvent.AddPropertyIfAbsent(sessionIdProperty);
}
}
}

View File

@@ -1,49 +0,0 @@
using Serilog.Core;
using Serilog.Events;
namespace Umbraco.Core.Logging.Serilog.Enrichers
{
/// <summary>
/// This is used to create a new property in Logs called 'Log4NetLevel'
/// So that we can map Serilog levels to Log4Net levels - so log files stay consistent
/// </summary>
internal class Log4NetLevelMapperEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var log4NetLevel = string.Empty;
switch (logEvent.Level)
{
case LogEventLevel.Debug:
log4NetLevel = "DEBUG";
break;
case LogEventLevel.Error:
log4NetLevel = "ERROR";
break;
case LogEventLevel.Fatal:
log4NetLevel = "FATAL";
break;
case LogEventLevel.Information:
log4NetLevel = "INFO";
break;
case LogEventLevel.Verbose:
log4NetLevel = "ALL";
break;
case LogEventLevel.Warning:
log4NetLevel = "WARN";
break;
}
//Pad string so that all log levels are 5 chars long (needed to keep the txt log file lined up nicely)
log4NetLevel = log4NetLevel.PadRight(5);
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Log4NetLevel", log4NetLevel));
}
}
}

View File

@@ -1,154 +0,0 @@
using System;
using System.Text;
using Serilog;
using Serilog.Configuration;
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
{
public static class LoggerConfigExtensions
{
private const string AppDomainId = "AppDomainId";
/// <summary>
/// This configures Serilog with some defaults
/// Such as adding ProcessID, Thread, AppDomain etc
/// 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)
{
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);
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("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));
return logConfig;
}
/// <summary>
/// Outputs a .txt format log at /App_Data/Logs/
/// </summary>
/// <param name="logConfig">A Serilog LoggerConfiguration</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)
{
//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",
shared: true,
rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: minimumLevel,
retainedFileCountLimit: null, //Setting to null means we keep all files - default is 31 days
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss,fff} [P{ProcessId}/D{AppDomainId}/T{ThreadId}] {Log4NetLevel} {SourceContext} - {Message:lj}{NewLine}{Exception}");
return logConfig;
}
/// <remarks>
/// Used in config - If renamed or moved to other assembly the config file also has be updated.
/// </remarks>
public static LoggerConfiguration File(this LoggerSinkConfiguration configuration, ITextFormatter formatter,
string path,
LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose,
LoggingLevelSwitch levelSwitch = null,
long? fileSizeLimitBytes = 1073741824,
TimeSpan? flushToDiskInterval = null,
RollingInterval rollingInterval = RollingInterval.Infinite,
bool rollOnFileSizeLimit = false,
int? retainedFileCountLimit = 31,
Encoding encoding = null
)
{
return configuration.Async(
asyncConfiguration => asyncConfiguration.Map(AppDomainId, (_,mapConfiguration) =>
mapConfiguration.File(
formatter,
path,
restrictedToMinimumLevel,
fileSizeLimitBytes,
levelSwitch,
buffered:true,
shared:false,
flushToDiskInterval,
rollingInterval,
rollOnFileSizeLimit,
retainedFileCountLimit,
encoding),
sinkMapCountLimit:0)
);
}
/// <summary>
/// Outputs a CLEF format JSON log at /App_Data/Logs/
/// </summary>
/// <param name="logConfig">A Serilog LoggerConfiguration</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)
{
//.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",
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
restrictedToMinimumLevel: minimumLevel);
return logConfig;
}
/// <summary>
/// Reads settings from /config/serilog.config
/// That allows the main logging pipeline to be configured
/// </summary>
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
public static LoggerConfiguration ReadFromConfigFile(this LoggerConfiguration logConfig)
{
//Read from main serilog.config file
logConfig.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + @"\config\serilog.config");
return logConfig;
}
/// <summary>
/// Reads settings from /config/serilog.user.config
/// 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)
{
//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"));
return logConfig;
}
}
}

View File

@@ -1,282 +0,0 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using Serilog;
using Serilog.Events;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Diagnostics;
using Umbraco.Core.Hosting;
using Umbraco.Net;
namespace Umbraco.Core.Logging.Serilog
{
///<summary>
/// Implements <see cref="ILogger"/> on top of Serilog.
///</summary>
public class SerilogLogger : ILogger, IDisposable
{
/// <summary>
/// Initialize a new instance of the <see cref="SerilogLogger"/> class with a configuration file.
/// </summary>
/// <param name="logConfigFile"></param>
public SerilogLogger(FileInfo logConfigFile)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + logConfigFile)
.CreateLogger();
}
public SerilogLogger(LoggerConfiguration logConfig)
{
//Configure Serilog static global logger with config passed in
Log.Logger = 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)
{
var loggerConfig = new LoggerConfiguration();
loggerConfig
.MinimalConfiguration(hostingEnvironment, sessionIdResolver, requestCacheGetter)
.ReadFromConfigFile()
.ReadFromUserConfigFile();
return new SerilogLogger(loggerConfig);
}
/// <summary>
/// Gets a contextualized logger.
/// </summary>
private global::Serilog.ILogger LoggerFor(Type reporting)
=> Log.Logger.ForContext(reporting);
/// <summary>
/// Maps Umbraco's log level to Serilog's.
/// </summary>
private LogEventLevel MapLevel(LogLevel level)
{
switch (level)
{
case LogLevel.Debug:
return LogEventLevel.Debug;
case LogLevel.Error:
return LogEventLevel.Error;
case LogLevel.Fatal:
return LogEventLevel.Fatal;
case LogLevel.Information:
return LogEventLevel.Information;
case LogLevel.Verbose:
return LogEventLevel.Verbose;
case LogLevel.Warning:
return LogEventLevel.Warning;
}
throw new NotSupportedException($"LogLevel \"{level}\" is not supported.");
}
/// <inheritdoc/>
public bool IsEnabled(Type reporting, LogLevel level)
=> LoggerFor(reporting).IsEnabled(MapLevel(level));
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception, string message)
{
var logger = LoggerFor(reporting);
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
logger.Fatal(exception, message);
}
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception)
{
var logger = LoggerFor(reporting);
var message = "Exception.";
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
logger.Fatal(exception, message);
}
/// <inheritdoc/>
public void Fatal(Type reporting, string message)
{
LoggerFor(reporting).Fatal(message);
}
/// <inheritdoc/>
public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues)
{
LoggerFor(reporting).Fatal(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
var logger = LoggerFor(reporting);
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref messageTemplate);
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);
logger.Error(exception, message);
}
/// <inheritdoc/>
public void Error(Type reporting, Exception exception)
{
var logger = LoggerFor(reporting);
var message = "Exception";
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref message);
logger.Error(exception, message);
}
/// <inheritdoc/>
public void Error(Type reporting, string message)
{
LoggerFor(reporting).Error(message);
}
/// <inheritdoc/>
public void Error(Type reporting, string messageTemplate, params object[] propertyValues)
{
LoggerFor(reporting).Error(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
var logger = LoggerFor(reporting);
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref messageTemplate);
logger.Error(exception, messageTemplate, propertyValues);
}
private static 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 = Current.Configs.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception);
// dump if it is ok to dump (might have a cap on number of dump...)
dump &= MiniDump.OkToDump();
}
if (dump)
{
try
{
var dumped = MiniDump.Dump(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)
{
LoggerFor(reporting).Warning(message);
}
/// <inheritdoc/>
public void Warn(Type reporting, string message, params object[] propertyValues)
{
LoggerFor(reporting).Warning(message, propertyValues);
}
/// <inheritdoc/>
public void Warn(Type reporting, Exception exception, string message)
{
LoggerFor(reporting).Warning(exception, message);
}
/// <inheritdoc/>
public void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
LoggerFor(reporting).Warning(exception, messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Info(Type reporting, string message)
{
LoggerFor(reporting).Information(message);
}
/// <inheritdoc/>
public void Info(Type reporting, string messageTemplate, params object[] propertyValues)
{
LoggerFor(reporting).Information(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Debug(Type reporting, string message)
{
LoggerFor(reporting).Debug(message);
}
/// <inheritdoc/>
public void Debug(Type reporting, string messageTemplate, params object[] propertyValues)
{
LoggerFor(reporting).Debug(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Verbose(Type reporting, string message)
{
LoggerFor(reporting).Verbose(message);
}
/// <inheritdoc/>
public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues)
{
LoggerFor(reporting).Verbose(messageTemplate, propertyValues);
}
public void Dispose()
{
Log.CloseAndFlush();
}
}
}

View File

@@ -1,49 +0,0 @@
using System;
using Serilog.Events;
namespace Umbraco.Core.Logging.Viewer
{
internal class CountingFilter : ILogFilter
{
public CountingFilter()
{
Counts = new LogLevelCounts();
}
public LogLevelCounts Counts { get; }
public bool TakeLogEvent(LogEvent e)
{
switch (e.Level)
{
case LogEventLevel.Debug:
Counts.Debug++;
break;
case LogEventLevel.Information:
Counts.Information++;
break;
case LogEventLevel.Warning:
Counts.Warning++;
break;
case LogEventLevel.Error:
Counts.Error++;
break;
case LogEventLevel.Fatal:
Counts.Fatal++;
break;
case LogEventLevel.Verbose:
break;
default:
throw new ArgumentOutOfRangeException();
}
//Don't add it to the list
return false;
}
}
}

View File

@@ -1,18 +0,0 @@
using Serilog.Events;
namespace Umbraco.Core.Logging.Viewer
{
internal class ErrorCounterFilter : ILogFilter
{
public int Count { get; private set; }
public bool TakeLogEvent(LogEvent e)
{
if (e.Level == LogEventLevel.Fatal || e.Level == LogEventLevel.Error || e.Exception != null)
Count++;
//Don't add it to the list
return false;
}
}
}

View File

@@ -1,61 +0,0 @@
using System;
using System.Linq;
using Serilog.Events;
using Serilog.Filters.Expressions;
namespace Umbraco.Core.Logging.Viewer
{
//Log Expression Filters (pass in filter exp string)
internal class ExpressionFilter : ILogFilter
{
private readonly Func<LogEvent, bool> _filter;
private const string ExpressionOperators = "()+=*<>%-";
public ExpressionFilter(string filterExpression)
{
Func<LogEvent, bool> filter;
if (string.IsNullOrEmpty(filterExpression))
{
return;
}
// If the expression is one word and doesn't contain a serilog operator then we can perform a like search
if (!filterExpression.Contains(" ") && !filterExpression.ContainsAny(ExpressionOperators.Select(c => c)))
{
filter = PerformMessageLikeFilter(filterExpression);
}
else // check if it's a valid expression
{
// If the expression evaluates then make it into a filter
if (FilterLanguage.TryCreateFilter(filterExpression, out var eval, out _))
{
filter = evt => true.Equals(eval(evt));
}
else
{
//Assume the expression was a search string and make a Like filter from that
filter = PerformMessageLikeFilter(filterExpression);
}
}
_filter = filter;
}
public bool TakeLogEvent(LogEvent e)
{
return _filter == null || _filter(e);
}
private Func<LogEvent, bool> PerformMessageLikeFilter(string filterExpression)
{
var filterSearch = $"@Message like '%{FilterLanguage.EscapeLikeExpressionContent(filterExpression)}%'";
if (FilterLanguage.TryCreateFilter(filterSearch, out var eval, out _))
{
return evt => true.Equals(eval(evt));
}
return null;
}
}
}

View File

@@ -1,9 +0,0 @@
using Serilog.Events;
namespace Umbraco.Core.Logging.Viewer
{
public interface ILogFilter
{
bool TakeLogEvent(LogEvent e);
}
}

View File

@@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Logging.Viewer
{
public interface ILogViewer
{
/// <summary>
/// Get all saved searches from your chosen data source
/// </summary>
IReadOnlyList<SavedLogSearch> GetSavedSearches();
/// <summary>
/// Adds a new saved search to chosen data source and returns the updated searches
/// </summary>
IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query);
/// <summary>
/// Deletes a saved search to chosen data source and returns the remaining searches
/// </summary>
IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query);
/// <summary>
/// A count of number of errors
/// By counting Warnings with Exceptions, Errors & Fatal messages
/// </summary>
int GetNumberOfErrors(LogTimePeriod logTimePeriod);
/// <summary>
/// Returns a number of the different log level entries
/// </summary>
LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod);
/// <summary>
/// Returns a list of all unique message templates and their counts
/// </summary>
IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod);
bool CanHandleLargeLogs { get; }
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
/// <summary>
/// Gets the current Serilog minimum log level
/// </summary>
/// <returns></returns>
string GetLogLevel();
/// <summary>
/// Returns the collection of logs
/// </summary>
PagedResult<LogMessage> GetLogs(LogTimePeriod logTimePeriod,
int pageNumber = 1,
int pageSize = 100,
Direction orderDirection = Direction.Descending,
string filterExpression = null,
string[] logLevels = null);
}
}

View File

@@ -1,140 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Serilog.Events;
using Serilog.Formatting.Compact.Reader;
using Umbraco.Core.IO;
namespace Umbraco.Core.Logging.Viewer
{
internal class JsonLogViewer : LogViewerSourceBase
{
private readonly string _logsPath;
private readonly ILogger _logger;
public JsonLogViewer(ILogger logger, IIOHelper ioHelper, string logsPath = "", string searchPath = "") : base(ioHelper, searchPath)
{
if (string.IsNullOrEmpty(logsPath))
logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
_logsPath = logsPath;
_logger = logger;
}
private const int FileSizeCap = 100;
public override bool CanHandleLargeLogs => false;
public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod)
{
//Log Directory
var logDirectory = _logsPath;
//Number of entries
long fileSizeCount = 0;
//foreach full day in the range - see if we can find one or more filenames that end with
//yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1))
{
//Filename ending to search for (As could be multiple)
var filesToFind = GetSearchPattern(day);
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
fileSizeCount += filesForCurrentDay.Sum(x => new FileInfo(x).Length);
}
//The GetLogSize call on JsonLogViewer returns the total file size in bytes
//Check if the log size is not greater than 100Mb (FileSizeCap)
var logSizeAsMegabytes = fileSizeCount / 1024 / 1024;
return logSizeAsMegabytes <= FileSizeCap;
}
private string GetSearchPattern(DateTime day)
{
return $"*{day:yyyyMMdd}*.json";
}
protected override IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take)
{
var logs = new List<LogEvent>();
//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
//yyyyMMdd.json - Ends with due to MachineName in filenames - could be 1 or more due to load balancing
for (var day = logTimePeriod.StartTime.Date; day.Date <= logTimePeriod.EndTime.Date; day = day.AddDays(1))
{
//Filename ending to search for (As could be multiple)
var filesToFind = GetSearchPattern(day);
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
//Foreach file we find - open it
foreach (var filePath in filesForCurrentDay)
{
//Open log file & add contents to the log collection
//Which we then use LINQ to page over
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var stream = new StreamReader(fs))
{
var reader = new LogEventReader(stream);
while (TryRead(reader, out var evt))
{
//We may get a null if log line is malformed
if (evt == null)
{
continue;
}
if (count > skip + take)
{
break;
}
if (count < skip)
{
count++;
continue;
}
if (filter.TakeLogEvent(evt))
{
logs.Add(evt);
}
count++;
}
}
}
}
}
return logs;
}
private bool TryRead(LogEventReader reader, out LogEvent evt)
{
try
{
return reader.TryRead(out evt);
}
catch (JsonReaderException ex)
{
// As we are reading/streaming one line at a time in the JSON file
// Thus we can not report the line number, as it will always be 1
_logger.Error<JsonLogViewer>(ex, "Unable to parse a line in the JSON log file");
evt = null;
return true;
}
}
}
}

View File

@@ -1,15 +0,0 @@
namespace Umbraco.Core.Logging.Viewer
{
public class LogLevelCounts
{
public int Information { get; set; }
public int Debug { get; set; }
public int Warning { get; set; }
public int Error { get; set; }
public int Fatal { get; set; }
}
}

View File

@@ -1,43 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Serilog.Events;
using System;
using System.Collections.Generic;
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Umbraco.Core.Logging.Viewer
{
public class LogMessage
{
/// <summary>
/// The time at which the log event occurred.
/// </summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>
/// The level of the event.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public LogEventLevel Level { get; set; }
/// <summary>
/// The message template describing the log event.
/// </summary>
public string MessageTemplateText { get; set; }
/// <summary>
/// The message template filled with the log event properties.
/// </summary>
public string RenderedMessage { get; set; }
/// <summary>
/// Properties associated with the log event, including those presented in Serilog.Events.LogEvent.MessageTemplate.
/// </summary>
public IReadOnlyDictionary<string, LogEventPropertyValue> Properties { get; set; }
/// <summary>
/// An exception associated with the log event, or null.
/// </summary>
public string Exception { get; set; }
}
}

View File

@@ -1,9 +0,0 @@
namespace Umbraco.Core.Logging.Viewer
{
public class LogTemplate
{
public string MessageTemplate { get; set; }
public int Count { get; set; }
}
}

View File

@@ -1,16 +0,0 @@
using System;
namespace Umbraco.Core.Logging.Viewer
{
public class LogTimePeriod
{
public LogTimePeriod(DateTime startTime, DateTime endTime)
{
StartTime = startTime;
EndTime = endTime;
}
public DateTime StartTime { get; }
public DateTime EndTime { get; }
}
}

View File

@@ -1,15 +0,0 @@
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
namespace Umbraco.Core.Logging.Viewer
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
// ReSharper disable once UnusedMember.Global
public class LogViewerComposer : ICoreComposer
{
public void Compose(Composition composition)
{
composition.SetLogViewer(factory => new JsonLogViewer(composition.Logger, factory.GetInstance<IIOHelper>()));
}
}
}

View File

@@ -1,196 +0,0 @@
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
{
private readonly string _searchesConfigPath;
private readonly IIOHelper _ioHelper;
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;
}
public abstract bool CanHandleLargeLogs { get; }
/// <summary>
/// Get all logs from your chosen data source back as Serilog LogEvents
/// </summary>
protected abstract IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take);
public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
public virtual IReadOnlyList<SavedLogSearch> GetSavedSearches()
{
//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);
}
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;
}
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;
}
public int GetNumberOfErrors(LogTimePeriod logTimePeriod)
{
var errorCounter = new ErrorCounterFilter();
GetLogs(logTimePeriod, errorCounter, 0, int.MaxValue);
return errorCounter.Count;
}
/// <summary>
/// Get the Serilog minimum-level value from the config file.
/// </summary>
/// <returns></returns>
public string GetLogLevel()
{
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.Logger.IsEnabled)?.Min() ?? null;
return logLevel?.ToString() ?? "";
}
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
{
var counter = new CountingFilter();
GetLogs(logTimePeriod, counter, 0, int.MaxValue);
return counter.Counts;
}
public IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod)
{
var messageTemplates = new MessageTemplateFilter();
GetLogs(logTimePeriod, messageTemplates, 0, int.MaxValue);
var templates = messageTemplates.Counts.
Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value })
.OrderByDescending(x=> x.Count);
return templates;
}
public PagedResult<LogMessage> GetLogs(LogTimePeriod logTimePeriod,
int pageNumber = 1, int pageSize = 100,
Direction orderDirection = Direction.Descending,
string filterExpression = null,
string[] logLevels = null)
{
var expression = new ExpressionFilter(filterExpression);
var filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue);
//This is user used the checkbox UI to toggle which log levels they wish to see
//If an empty array or null - its implied all levels to be viewed
if (logLevels?.Length > 0)
{
var logsAfterLevelFilters = new List<LogEvent>();
var validLogType = true;
foreach (var level in logLevels)
{
//Check if level string is part of the LogEventLevel enum
if(Enum.IsDefined(typeof(LogEventLevel), level))
{
validLogType = true;
logsAfterLevelFilters.AddRange(filteredLogs.Where(x => string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase)));
}
else
{
validLogType = false;
}
}
if (validLogType)
{
filteredLogs = logsAfterLevelFilters;
}
}
long totalRecords = filteredLogs.Count;
//Order By, Skip, Take & Select
var logMessages = filteredLogs
.OrderBy(l => l.Timestamp, orderDirection)
.Skip(pageSize * (pageNumber - 1))
.Take(pageSize)
.Select(x => new LogMessage
{
Timestamp = x.Timestamp,
Level = x.Level,
MessageTemplateText = x.MessageTemplate.Text,
Exception = x.Exception?.ToString(),
Properties = x.Properties,
RenderedMessage = x.RenderMessage()
});
return new PagedResult<LogMessage>(totalRecords, pageNumber, pageSize)
{
Items = logMessages
};
}
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);
}
}
}
}

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
using Serilog.Events;
namespace Umbraco.Core.Logging.Viewer
{
internal class MessageTemplateFilter : ILogFilter
{
public readonly Dictionary<string, int> Counts = new Dictionary<string, int>();
public bool TakeLogEvent(LogEvent e)
{
var templateText = e.MessageTemplate.Text;
if (Counts.TryGetValue(templateText, out var count))
{
count++;
}
else
{
count = 1;
}
Counts[templateText] = count;
//Don't add it to the list
return false;
}
}
}

View File

@@ -1,13 +0,0 @@
using Newtonsoft.Json;
namespace Umbraco.Core.Logging.Viewer
{
public class SavedLogSearch
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("query")]
public string Query { get; set; }
}
}

View File

@@ -129,19 +129,15 @@
<Compile Include="Compose\AuditEventsComponent.cs" />
<Compile Include="Compose\AuditEventsComposer.cs" />
<Compile Include="Composing\RegisterFactory.cs" />
<Compile Include="CompositionExtensions.cs" />
<Compile Include="Composing\CompositionExtensions\Configuration.cs" />
<Compile Include="Composing\CompositionExtensions\CoreMappingProfiles.cs" />
<Compile Include="Composing\CompositionExtensions\FileSystems.cs" />
<Compile Include="Composing\CompositionExtensions\Repositories.cs" />
<Compile Include="Composing\CompositionExtensions\Services.cs" />
<Compile Include="CompositionExtensions_Essentials.cs" />
<Compile Include="CompositionExtensions_FileSystems.cs" />
<Compile Include="Deploy\IGridCellValueConnector.cs" />
<Compile Include="Composing\Current.cs" />
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
<Compile Include="Composing\LightInject\MixedLightInjectScopeManagerProvider.cs" />
<Compile Include="Logging\Viewer\LogTimePeriod.cs" />
<Compile Include="Models\Identity\BackOfficeIdentityUser.cs" />
<Compile Include="Models\Identity\IdentityMapDefinition.cs" />
<Compile Include="Models\Identity\IdentityUser.cs" />
@@ -155,35 +151,11 @@
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
<Compile Include="StringExtensions.cs" />
<Compile Include="TypeLoaderExtensions.cs" />
<Compile Include="Logging\Viewer\CountingFilter.cs" />
<Compile Include="Logging\Viewer\ErrorCounterFilter.cs" />
<Compile Include="Logging\Viewer\ExpressionFilter.cs" />
<Compile Include="Logging\Viewer\ILogFilter.cs" />
<Compile Include="Logging\Viewer\LogTemplate.cs" />
<Compile Include="Logging\Viewer\ILogViewer.cs" />
<Compile Include="Logging\Viewer\LogLevelCounts.cs" />
<Compile Include="Logging\Viewer\JsonLogViewer.cs" />
<Compile Include="Logging\Viewer\LogMessage.cs" />
<Compile Include="Logging\Viewer\LogViewerComposer.cs" />
<Compile Include="Logging\Viewer\LogViewerSourceBase.cs" />
<Compile Include="Logging\LogHttpRequest.cs" />
<Compile Include="Logging\MessageTemplates.cs" />
<Compile Include="Logging\Serilog\Enrichers\HttpRequestIdEnricher.cs" />
<Compile Include="Logging\Serilog\Enrichers\HttpRequestNumberEnricher.cs" />
<Compile Include="Logging\Serilog\Enrichers\HttpSessionIdEnricher.cs" />
<Compile Include="Logging\Serilog\LoggerConfigExtensions.cs" />
<Compile Include="Logging\Serilog\Enrichers\Log4NetLevelMapperEnricher.cs" />
<Compile Include="Logging\Viewer\MessageTemplateFilter.cs" />
<Compile Include="Logging\Viewer\SavedLogSearch.cs" />
<Compile Include="RuntimeOptions.cs" />
<Compile Include="Runtime\CoreRuntime.cs" />
<Compile Include="Runtime\CoreInitialComponent.cs" />
<Compile Include="Diagnostics\MiniDump.cs" />
<Compile Include="Composing\LightInject\LightInjectException.cs" />
<Compile Include="FileResources\Files.Designer.cs" />
<Compile Include="Logging\Serilog\SerilogLogger.cs" />
<Compile Include="Logging\OwinLogger.cs" />
<Compile Include="Logging\OwinLoggerFactory.cs" />
<Compile Include="MainDom.cs" />
<Compile Include="ContentExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -222,8 +194,5 @@
<Name>Umbraco.Infrastructure</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Sync" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>