LogInitializer Serilog example

This article describes a Serilog LogInitializer class which can be used to init a logger from appsettings, from inline code and even create a logger for DI injection on the fly. That’s for cases when you have to deal with older project that don’t use DI yet.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Serilog;
using System;

namespace LoggingExample.Utilities;

public static class LogInitializer
{

    private const string FileLogFolderName = "Log";
    private const string FileLogName = "Serilog_SelfLog_";

    /// <summary>
    /// Creates the logger with inline settings.
    /// </summary>
    /// <returns>Logger with inline settings</returns>
    public static Serilog.ILogger CreateLogger(bool selfLog = false)
    {
        if (selfLog)
            StartSerilogSelfLog();
        
        return new LoggerConfiguration()
                .Enrich.FromLogContext()
                .WriteTo.Async(a =>
                {
                    a.File($@"C:\temp\{ThisAssembly.AssemblyName}\logs\log.txt", rollingInterval: RollingInterval.Hour);
                })
#if DEBUG
                .WriteTo.Console()
                .WriteTo.Debug()
#endif
                .CreateLogger();
    }

    /// <summary>
    /// Creates the logger with settings from appconfig and enrichments from code.
    /// </summary>
    /// <param name="appConfig">appConfig built from appsettings.json</param>
    /// <returns>Logger with inline and app.config settings</returns>
    public static Serilog.ILogger CreateLogger(IConfiguration appConfig, bool selfLog = false)
    {
        if (selfLog)
            StartSerilogSelfLog();

        return new LoggerConfiguration()
            .ReadFrom.Configuration(appConfig)
            .Enrich.FromLogContext()
            .CreateLogger();
    }

    /// <summary>
    /// Creates a logger of type T for injecting it manually into a constructor.
    /// </summary>
    /// <typeparam name="T">type of class to inject the logger to</typeparam>
    /// <returns>ILogger instance of type T for the class constructor of type T</returns>
    public static ILogger<T> CreateLoggerForInjection<T>()
    {
        // Create a MS extensions ILogger instance of the serilog logger
        var loggerFactory = (ILoggerFactory)new LoggerFactory();
        loggerFactory.AddSerilog(Log.Logger);

        var logger = loggerFactory.CreateLogger<T>();

        return logger;
    }

    /// <summary>
    /// Starts the Serilog self logging for internal log exceptions.
    /// Creates a log file in the LocalAppDataCompanyFolderPath.
    /// Deletes old selflog files to keep the folder clean.
    /// </summary>
    private static void StartSerilogSelfLog()
    {
        var appSettingsPath = AppDataCommonPathProcessor.GetCompanyFolderPath(ThisAssembly.AssemblyName);
        var fileLogFolderPath = Path.Combine(appSettingsPath, FileLogFolderName);
        var fileLogFilePath = Path.Combine(fileLogFolderPath, FileLogName.Replace(FileLogName, $"{FileLogName}{DateTime.Now:yyyyMMdd-hhmmss.fff}.txt"));

        Directory.CreateDirectory(fileLogFolderPath);
        var serilogSelfLogFile = File.CreateText(fileLogFilePath);
        Serilog.Debugging.SelfLog.Enable(TextWriter.Synchronized(serilogSelfLogFile));
        Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));

        ClearSerilogSelfLog(fileLogFolderPath);
    }

    /// <summary>
    /// Clears the old selflog files from directory.
    /// </summary>
    /// <param name="fileLogFolderPath">path to be cleared</param>
    /// <param name="ageInDays">number of days to hold old selflog files</param>
    private static void ClearSerilogSelfLog(string fileLogFolderPath, int ageInDays = 5)
    {
        string[] selfLogFiles = Directory.GetFiles(fileLogFolderPath);

        foreach (var file in selfLogFiles)
        {
            FileInfo fi = new FileInfo(file);
            if (fi.LastWriteTime < DateTime.Now.AddDays(-ageInDays) && fi.Name.Contains(FileLogName))
            {
                fi.Delete();
            }
        }
    }
}

The LogInitializer class uses another utility class to access a special folder for storing application settings outside the user folder or the application folder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System;
using System.IO;
using System.Reflection;

namespace LoggingExample.Utilities;

public static class AppDataCommonPathProcessor
{
    private const string CompanyName = "YOUR_COMPANY_NAME";

    /// <summary>
    /// Gets a common path for settings and files for the application in the LocalAppData folder, combined with company name and application name.
    /// Creates the directories of the path unless they don't exist.
    /// </summary>
    /// <param name="applicationName">A subfolder with the given application string gets created if provided.</param>
    /// <returns>Path to appsettings folder</returns>
    public static string GetCompanyFolderPath(string applicationName = "")
    {
        string company = ((AssemblyCompanyAttribute)Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyCompanyAttribute), false)).Company;
        company = string.IsNullOrEmpty(company) ? CompanyName : company.Replace(' ', '_');

        string companyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), company, applicationName);
        Directory.CreateDirectory(companyPath);
        return companyPath;
    }

    /// <summary>
    /// Gets a common path for settings and files for the application in the C:\ProgramData folder, combined with company name and application name.
    /// Creates the directories of the path unless they don't exist.
    /// </summary>
    /// <param name="applicationName">A subfolder with the given application string gets created if provided.</param>
    /// <returns>Path to appsettings folder</returns>
    public static string GetProgramDataCompanyFolderPath(string applicationName = "")
    {
        string company = ((AssemblyCompanyAttribute)Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyCompanyAttribute), false)).Company;
        company = string.IsNullOrEmpty(company) ? CompanyName : company.Replace(' ', '_');

        string companyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), company, applicationName);
        Directory.CreateDirectory(companyPath);
        return companyPath;
    }

    /// <summary>
    /// Clears files from a directory where the file name matches a given string phrase.
    /// </summary>
    /// <param name="folderPath">path to be cleared</param>
    /// <param name="match">string to recognize files to be deleted, e.g. ".pdf"</param>
    /// <param name="ageInDays">number of days to hold old files</param>
    public static void ClearFilesFromPath(string folderPath, string match, int ageInDays = 5)
    {
        string[] files = Directory.GetFiles(folderPath);

        foreach (var file in files)
        {
            FileInfo fi = new FileInfo(file);
            if (fi.LastWriteTime < DateTime.Now.AddDays(-ageInDays) && fi.Name.Contains(match))
            {
                fi.Delete();
            }
        }
    }
}