2018-08-14 09:37:29 +01:00
using System ;
2019-05-01 15:15:29 +02:00
using System.Text ;
2018-08-14 09:37:29 +01:00
using System.Web ;
using Serilog ;
2019-05-01 15:15:29 +02:00
using Serilog.Configuration ;
using Serilog.Core ;
2018-08-14 09:37:29 +01:00
using Serilog.Events ;
2019-05-01 15:15:29 +02:00
using Serilog.Formatting ;
2018-08-14 09:37:29 +01:00
using Serilog.Formatting.Compact ;
2018-09-06 20:35:02 +01:00
using Umbraco.Core.Logging.Serilog.Enrichers ;
2018-08-14 09:37:29 +01:00
2018-08-30 19:08:55 +02:00
namespace Umbraco.Core.Logging.Serilog
2018-08-14 09:37:29 +01:00
{
public static class LoggerConfigExtensions
{
2019-05-02 14:19:48 +02:00
private const string AppDomainId = "AppDomainId" ;
2018-08-15 16:58:23 +01:00
/// <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>
2018-08-14 09:37:29 +01:00
public static LoggerConfiguration MinimalConfiguration ( this LoggerConfiguration logConfig )
{
2018-08-30 19:08:55 +02:00
global :: Serilog . Debugging . SelfLog . Enable ( msg = > System . Diagnostics . Debug . WriteLine ( msg ) ) ;
2018-08-14 09:37:29 +01:00
//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 ) ;
2019-02-12 08:59:12 +00:00
Environment . SetEnvironmentVariable ( "MACHINENAME" , Environment . MachineName , EnvironmentVariableTarget . Process ) ;
2018-09-06 20:35:02 +01:00
2018-08-14 09:37:29 +01:00
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 ( )
2019-05-02 14:19:48 +02:00
. Enrich . WithProperty ( AppDomainId , AppDomain . CurrentDomain . Id )
2018-08-14 09:37:29 +01:00
. Enrich . WithProperty ( "AppDomainAppId" , HttpRuntime . AppDomainAppId . ReplaceNonAlphanumericChars ( string . Empty ) )
2018-08-21 12:48:06 +01:00
. Enrich . WithProperty ( "MachineName" , Environment . MachineName )
2018-09-06 20:35:02 +01:00
. Enrich . With < Log4NetLevelMapperEnricher > ( )
. Enrich . With < HttpSessionIdEnricher > ( )
. Enrich . With < HttpRequestNumberEnricher > ( )
. Enrich . With < HttpRequestIdEnricher > ( ) ;
2019-05-01 15:15:29 +02:00
2018-08-14 09:37:29 +01:00
return logConfig ;
}
2018-08-15 16:58:23 +01:00
/// <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 )
2018-08-14 09:37:29 +01:00
{
//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" ,
2019-05-01 15:15:29 +02:00
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}" ) ;
2018-08-14 09:37:29 +01:00
return logConfig ;
}
2019-05-01 15:15:29 +02:00
/// <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 ,
bool shared = false ,
bool buffered = false ,
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 ,
bool async = false ,
bool asyncBlockWhenFull = false ,
2019-05-02 14:19:48 +02:00
int asyncBufferSize = 10000 ,
bool asyncKeepFileOpen = true )
2019-05-01 15:15:29 +02:00
{
LoggerConfiguration GetFileSink ( LoggerSinkConfiguration c )
{
return c . File (
formatter ,
path ,
restrictedToMinimumLevel ,
fileSizeLimitBytes ,
levelSwitch ,
buffered ,
shared ,
flushToDiskInterval ,
rollingInterval ,
rollOnFileSizeLimit ,
retainedFileCountLimit ,
encoding ) ;
}
2019-05-02 14:19:48 +02:00
if ( ! async & & ! asyncKeepFileOpen )
2019-05-01 15:15:29 +02:00
{
2019-05-02 14:19:48 +02:00
throw new InvalidOperationException ( $"{nameof(asyncKeepFileOpen)} cannot be changed if async is false." ) ;
2019-05-01 15:15:29 +02:00
}
2019-05-02 14:19:48 +02:00
if ( ! async & & rollOnFileSizeLimit )
2019-05-01 15:15:29 +02:00
{
2019-05-02 14:19:48 +02:00
throw new InvalidOperationException ( $"{nameof(rollOnFileSizeLimit)} cannot be changed if async is false." ) ;
2019-05-01 15:15:29 +02:00
}
2019-05-02 14:19:48 +02:00
if ( async )
{
Action < LoggerSinkConfiguration > configure ;
if ( asyncKeepFileOpen )
{
configure = a = > GetFileSink ( a ) ;
}
else
{
// This is a way to force the file to be closed after each log event. Read more https://github.com/serilog/serilog-sinks-file/issues/99#issuecomment-488612711
// We basically map all log events with a AppDomainId property (all events) into a file sink and forces the sink to be closed, due to the sinkMapCountLimit = 0.
configure = a = > a . Map ( AppDomainId , ( name , c ) = > GetFileSink ( c ) , sinkMapCountLimit : 0 ) ;
}
return configuration . Async ( configure , blockWhenFull : asyncBlockWhenFull , bufferSize : asyncBufferSize ) ;
}
return GetFileSink ( configuration ) ;
2019-05-01 15:15:29 +02:00
}
2019-05-02 14:19:48 +02:00
2018-08-15 16:58:23 +01:00
/// <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 )
2018-08-14 09:37:29 +01:00
{
//.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" ,
2018-08-28 09:46:51 +01:00
shared : true ,
2018-08-14 09:37:29 +01:00
rollingInterval : RollingInterval . Day , //Create a new JSON file every day
2018-08-15 16:58:23 +01:00
retainedFileCountLimit : retainedFileCount , //Setting to null means we keep all files - default is 31 days
restrictedToMinimumLevel : minimumLevel ) ;
2018-08-14 09:37:29 +01:00
return logConfig ;
}
2018-08-15 16:58:23 +01:00
/// <summary>
/// Reads settings from /config/serilog.config
/// That allows the main logging pipeline to be configured
/// </summary>
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
2018-08-14 09:37:29 +01:00
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 ;
}
2018-08-15 16:58:23 +01:00
/// <summary>
/// Reads settings from /config/serilog.user.config
2019-01-22 18:03:39 -05:00
/// That allows a separate logging pipeline to be configured that will not affect the main Umbraco log
2018-08-15 16:58:23 +01:00
/// </summary>
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
2018-08-14 09:37:29 +01:00
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 ;
}
}
}