AB4227 - Moved Logging
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public interface ILogFilter
|
||||
{
|
||||
bool TakeLogEvent(LogEvent e);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public class LogTemplate
|
||||
{
|
||||
public string MessageTemplate { get; set; }
|
||||
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user