< Summary

Information
Class: ArturRios.Logging.Loggers.FileLogger
Assembly: ArturRios.Logging
File(s): /home/ci/agent/workspace/dotnet-logging-docs_main/app/src/Loggers/FileLogger.cs
Line coverage
93%
Covered lines: 54
Uncovered lines: 4
Coverable lines: 58
Total lines: 152
Line coverage: 93.1%
Branch coverage
84%
Covered branches: 16
Total branches: 19
Branch coverage: 84.2%
Method coverage
100%
Covered methods: 15
Fully covered methods: 13
Total methods: 15
Method coverage: 100%
Full method coverage: 86.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%11100%
Trace(...)100%11100%
Debug(...)100%11100%
Info(...)100%11100%
Warn(...)100%11100%
Error(...)100%11100%
Exception(...)100%11100%
Critical(...)100%11100%
Fatal(...)100%11100%
Write(...)100%11100%
CreateDirectoryIfNotExists(...)75%44100%
BuildFullPath(...)100%11100%
BuildFolderPath(...)88.88%9987.5%
BuildFileName(...)83.33%6680%

File(s)

/home/ci/agent/workspace/dotnet-logging-docs_main/app/src/Loggers/FileLogger.cs

#LineLine coverage
 1using ArturRios.Logging.Configuration;
 2using ArturRios.Logging.Factories;
 3using ArturRios.Logging.Interfaces;
 4
 5namespace ArturRios.Logging.Loggers;
 6
 7/// <summary>
 8/// Appends log entries to files on disk, organised by the folder scheme and split level in the configuration.
 9/// </summary>
 10/// <remarks>
 11/// Writes are serialized on a process-wide lock, so entries from concurrent threads never interleave.
 12/// </remarks>
 13/// <param name="configuration">Controls the application name, base path, folder scheme and split level.</param>
 5614public class FileLogger(FileLoggerConfiguration configuration) : IInternalLogger
 15{
 16    private const string DefaultLogFolder = "log";
 17    private const string FileExtension = ".log";
 118    private static readonly Lock s_fileLock = new();
 19
 20    /// <summary>
 21    /// The folder name used by <see cref="LogFolderScheme.ByRequest"/>, fixed for the lifetime of this
 22    /// logger. Generating it per write put every single entry in a folder of its own.
 23    /// </summary>
 5624    private readonly string _instanceFolderName = Guid.NewGuid().ToString();
 25
 26    /// <inheritdoc />
 27    public void Trace(string message, string filePath, string methodName)
 28    {
 429        Write(CustomLogLevel.Trace, filePath, methodName, message);
 430    }
 31
 32    /// <inheritdoc />
 33    public void Debug(string message, string filePath, string methodName)
 34    {
 435        Write(CustomLogLevel.Debug, filePath, methodName, message);
 436    }
 37
 38    /// <inheritdoc />
 39    public void Info(string message, string filePath, string methodName)
 40    {
 5041        Write(CustomLogLevel.Information, filePath, methodName, message);
 5042    }
 43
 44    /// <inheritdoc />
 45    public void Warn(string message, string filePath, string methodName)
 46    {
 447        Write(CustomLogLevel.Warning, filePath, methodName, message);
 448    }
 49
 50    /// <inheritdoc />
 51    public void Error(string message, string filePath, string methodName)
 52    {
 453        Write(CustomLogLevel.Error, filePath, methodName, message);
 454    }
 55
 56    /// <inheritdoc />
 57    public void Exception(string message, string filePath, string methodName)
 58    {
 359        Write(CustomLogLevel.Exception, filePath, methodName, message);
 360    }
 61
 62    /// <inheritdoc />
 63    public void Critical(string message, string filePath, string methodName)
 64    {
 365        Write(CustomLogLevel.Critical, filePath, methodName, message);
 366    }
 67
 68    /// <inheritdoc />
 69    public void Fatal(string message, string filePath, string methodName)
 70    {
 371        Write(CustomLogLevel.Fatal, filePath, methodName, message);
 372    }
 73
 74    private void Write(CustomLogLevel level, string filePath, string methodName, string message)
 75    {
 76        // One instant for both the file and the entry: reading the clock twice could put an entry stamped just
 77        // after midnight (or the hour, month, year) into the file of the period before.
 7578        var timestamp = DateTime.UtcNow;
 7579        var path = BuildFullPath(timestamp);
 80
 7581        CreateDirectoryIfNotExists(path);
 82
 83        lock (s_fileLock)
 84        {
 7585            File.AppendAllText(path, LogEntryFactory.Create(level, filePath, methodName, message, timestamp));
 7586        }
 7587    }
 88
 89    private static void CreateDirectoryIfNotExists(string path)
 90    {
 7591        var directory = Path.GetDirectoryName(path);
 92
 7593        if (directory is not null && !Directory.Exists(directory))
 94        {
 5695            Directory.CreateDirectory(directory);
 96        }
 7597    }
 98
 99    private string BuildFullPath(DateTime timestamp)
 100    {
 75101        var folderPath = BuildFolderPath(timestamp);
 75102        var fileName = BuildFileName(timestamp);
 103
 75104        return Path.Combine(folderPath, $"{fileName}{FileExtension}");
 105    }
 106
 107    private string BuildFolderPath(DateTime timestamp)
 108    {
 75109        var basePath = configuration.FilePath ?? Path.Combine(AppContext.BaseDirectory, DefaultLogFolder);
 75110        var path = basePath;
 111
 75112        switch (configuration.FolderScheme)
 113        {
 114            case LogFolderScheme.AllInOne:
 115                break;
 116            case LogFolderScheme.ByYear:
 2117                path = Path.Combine(path, timestamp.Year.ToString());
 2118                break;
 119            case LogFolderScheme.ByMonth:
 3120                path = Path.Combine(path, timestamp.Year.ToString(), timestamp.Month.ToString("D2"));
 3121                break;
 122            case LogFolderScheme.ByDay:
 2123                path = Path.Combine(path, timestamp.Year.ToString(), timestamp.Month.ToString("D2"), timestamp.Day.ToStr
 2124                break;
 125            case LogFolderScheme.ByHour:
 3126                path = Path.Combine(path, timestamp.Year.ToString(), timestamp.Month.ToString("D2"), timestamp.Day.ToStr
 3127                break;
 128            case LogFolderScheme.ByRequest:
 7129                path = Path.Combine(path, _instanceFolderName);
 7130                break;
 131            default:
 0132                throw new ArgumentOutOfRangeException(
 0133                    nameof(configuration), configuration.FolderScheme, "Unknown log folder scheme.");
 134        }
 135
 75136        return path;
 137    }
 138
 139    private string BuildFileName(DateTime timestamp)
 140    {
 75141        return configuration.FileSplitLevel switch
 75142        {
 2143            LogSplitLevel.Request => configuration.ApplicationName,
 2144            LogSplitLevel.Year => $"{configuration.ApplicationName}_{timestamp:yyyy}",
 2145            LogSplitLevel.Month => $"{configuration.ApplicationName}_{timestamp:yyyy_MM}",
 66146            LogSplitLevel.Day => $"{configuration.ApplicationName}_{timestamp:yyyy_MM_dd}",
 3147            LogSplitLevel.Hour => $"{configuration.ApplicationName}_{timestamp:yyyy_MM_dd_HH}",
 0148            _ => throw new ArgumentOutOfRangeException(
 0149                nameof(configuration), configuration.FileSplitLevel, "Unknown log split level.")
 75150        };
 151    }
 152}