Skip to content

Allow for configuration of log file names #19074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Umbraco.Core/Configuration/Models/LoggingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See LICENSE for more details.

using System.ComponentModel;
using Umbraco.Cms.Core.Logging;

namespace Umbraco.Cms.Core.Configuration.Models;

Expand All @@ -13,6 +14,8 @@ public class LoggingSettings
{
internal const string StaticMaxLogAge = "1.00:00:00"; // TimeSpan.FromHours(24);
internal const string StaticDirectory = Constants.SystemDirectories.LogFiles;
internal const string StaticFileNameFormat = LoggingConfiguration.DefaultLogFileNameFormat;
internal const string StaticFileNameFormatArguments = "MachineName";

/// <summary>
/// Gets or sets a value for the maximum age of a log file.
Expand All @@ -31,4 +34,25 @@ public class LoggingSettings
/// </value>
[DefaultValue(StaticDirectory)]
public string Directory { get; set; } = StaticDirectory;

/// <summary>
/// Gets or sets the file name format to use for log files.
/// </summary>
/// <value>
/// The file name format.
/// </value>
[DefaultValue(StaticFileNameFormat)]
public string FileNameFormat { get; set; } = StaticFileNameFormat;

/// <summary>
/// Gets or sets the file name format arguments to use for log files.
/// </summary>
/// <value>
/// The file name format arguments as a comma delimited string of accepted values.
/// </value>
/// <remarks>
/// Accepted values for format arguments are: MachineName, EnvironmentName.
/// </remarks>
[DefaultValue(StaticFileNameFormatArguments)]
public string FileNameFormatArguments { get; set; } = StaticFileNameFormatArguments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Logging;

namespace Umbraco.Cms.Core.Configuration.Models.Validation;

/// <summary>
/// Validator for configuration representated as <see cref="LoggingSettings" />.
/// </summary>
public class LoggingSettingsValidator : ConfigurationValidatorBase, IValidateOptions<LoggingSettings>
{
/// <inheritdoc />
public ValidateOptionsResult Validate(string? name, LoggingSettings options)
{
if (!ValidateFileNameFormatArgument(options.FileNameFormat, options.FileNameFormatArguments, out var message))
{
return ValidateOptionsResult.Fail(message);
}


return ValidateOptionsResult.Success;
}

private bool ValidateFileNameFormatArgument(string fileNameFormat, string fileNameFormatArguments, out string message)
{
var fileNameFormatArgumentsAsArray = fileNameFormatArguments
.Split([','], StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToArray();
if (fileNameFormatArgumentsAsArray.Any(x => LoggingConfiguration.SupportedFileNameFormatArguments.Contains(x) is false))
{
message = $"The file name arguments '{string.Join(",", fileNameFormatArgumentsAsArray)}' contain one or more values that aren't in the supported list of values '{string.Join(",", LoggingConfiguration.SupportedFileNameFormatArguments)}'.";
return false;
}

try
{
_ = string.Format(fileNameFormat, fileNameFormatArgumentsAsArray);
}
catch (FormatException)
{
message = $"The provided file name format '{fileNameFormat}' could not be used with the provided arguments '{string.Join(",", fileNameFormatArgumentsAsArray)}'.";
return false;
}

message = string.Empty;
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
builder.Services.AddSingleton<IValidateOptions<ContentSettings>, ContentSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<GlobalSettings>, GlobalSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<HealthChecksSettings>, HealthChecksSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<LoggingSettings>, LoggingSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<RequestHandlerSettings>, RequestHandlerSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<UnattendedSettings>, UnattendedSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<SecuritySettings>, SecuritySettingsValidator>();
Expand Down
12 changes: 11 additions & 1 deletion src/Umbraco.Core/Logging/ILoggingConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ namespace Umbraco.Cms.Core.Logging;
public interface ILoggingConfiguration
{
/// <summary>
/// Gets the physical path where logs are stored
/// Gets the physical path where logs are stored.
/// </summary>
string LogDirectory { get; }

/// <summary>
/// Gets the file name format for the log files.
/// </summary>
string LogFileNameFormat { get; }

/// <summary>
/// Gets the file name format arguments for the log files.
/// </summary>
string[] GetLogFileNameFormatArguments();
}
68 changes: 66 additions & 2 deletions src/Umbraco.Core/Logging/LoggingConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,73 @@
namespace Umbraco.Cms.Core.Logging;

/// <summary>
/// Implements <see cref="ILoggingConfiguration"/> to provide configuration for logging to files.
/// </summary>
public class LoggingConfiguration : ILoggingConfiguration
{
public LoggingConfiguration(string logDirectory) =>
LogDirectory = logDirectory ?? throw new ArgumentNullException(nameof(logDirectory));
/// <summary>
/// The default log file name format.
/// </summary>
public const string DefaultLogFileNameFormat = "UmbracoTraceLog.{0}..json";

/// <summary>
/// The default log file name format arguments.
/// </summary>
public const string DefaultLogFileNameFormatArguments = MachineNameFileFormatArgument;

/// <summary>
/// The collection of supported file name format arguments.
/// </summary>
public static readonly string[] SupportedFileNameFormatArguments =
{
MachineNameFileFormatArgument,
EnvironmentNameFileFormatArgument,
};

private readonly string _logFileNameFormatArguments;

private const string MachineNameFileFormatArgument = "MachineName";
private const string EnvironmentNameFileFormatArgument = "EnvironmentName";

/// <summary>
/// Initializes a new instance of the <see cref="LoggingConfiguration"/> class with the default log file name format and arguments.
/// </summary>
/// <param name="logDirectory">The log file directory.</param>
public LoggingConfiguration(string logDirectory)
: this(logDirectory, DefaultLogFileNameFormat, DefaultLogFileNameFormatArguments)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="LoggingConfiguration"/> class.
/// </summary>
/// <param name="logDirectory">The log file directory.</param>
/// <param name="logFileNameFormat">The log file name format.</param>
/// <param name="logFileNameFormatArguments">The log file name format arguments as a comma delimited string.</param>
public LoggingConfiguration(string logDirectory, string logFileNameFormat, string logFileNameFormatArguments)
{
LogDirectory = logDirectory;
LogFileNameFormat = logFileNameFormat;
_logFileNameFormatArguments = logFileNameFormatArguments;
}

/// <inheritdoc/>
public string LogDirectory { get; }

/// <inheritdoc/>
public string LogFileNameFormat { get; }

/// <inheritdoc/>
public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Select(GetValue)
.ToArray();

private static string GetValue(string arg) =>
arg switch
{
MachineNameFileFormatArgument => Environment.MachineName,
EnvironmentNameFileFormatArgument => Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production",
_ => string.Empty,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public static LoggerConfiguration MinimalConfiguration(
.Enrich.FromLogContext(); // allows us to dynamically enrich

logConfig.WriteTo.UmbracoFile(
path: umbracoFileConfiguration.GetPath(loggingConfiguration.LogDirectory),
path: umbracoFileConfiguration.GetPath(loggingConfiguration.LogDirectory, loggingConfiguration.LogFileNameFormat, loggingConfiguration.GetLogFileNameFormatArguments()),
fileSizeLimitBytes: umbracoFileConfiguration.FileSizeLimitBytes,
restrictedToMinimumLevel: umbracoFileConfiguration.RestrictedToMinimumLevel,
rollingInterval: umbracoFileConfiguration.RollingInterval,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Configuration;
using Serilog;
using Serilog.Events;
using Umbraco.Cms.Core.Logging;

namespace Umbraco.Cms.Infrastructure.Logging.Serilog;

Expand Down Expand Up @@ -43,5 +44,8 @@ public UmbracoFileConfiguration(IConfiguration configuration)
public int RetainedFileCountLimit { get; set; } = 31;

public string GetPath(string logDirectory) =>
Path.Combine(logDirectory, $"UmbracoTraceLog.{Environment.MachineName}..json");
GetPath(logDirectory, LoggingConfiguration.DefaultLogFileNameFormat, Environment.MachineName);

public string GetPath(string logDirectory, string fileNameFormat, params string[] fileNameArgs) =>
Path.Combine(logDirectory, string.Format(fileNameFormat, fileNameArgs));
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static IServiceCollection AddLogger(
LoggingSettings loggerSettings = GetLoggerSettings(configuration);

var loggingDir = loggerSettings.GetAbsoluteLoggingPath(hostEnvironment);
ILoggingConfiguration loggingConfig = new LoggingConfiguration(loggingDir);
ILoggingConfiguration loggingConfig = new LoggingConfiguration(loggingDir, loggerSettings.FileNameFormat, loggerSettings.FileNameFormatArguments);

var umbracoFileConfiguration = new UmbracoFileConfiguration(configuration);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using Microsoft.Extensions.Options;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Configuration.Models.Validation;
using Umbraco.Cms.Core.Logging;

namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation
{
[TestFixture]
public class LoggingSettingsValidatorTests
{
[Test]
public void Returns_Success_ForValid_Configuration()
{
var validator = new LoggingSettingsValidator();
LoggingSettings options = BuildLoggingSettings();
ValidateOptionsResult result = validator.Validate("settings", options);
Assert.True(result.Succeeded);
}

[Test]
public void Returns_Fail_For_Configuration_With_Invalid_FileNameFormatArguments()
{
var validator = new LoggingSettingsValidator();
LoggingSettings options = BuildLoggingSettings(fileNameFormatArguments: "MachineName,Invalid");
ValidateOptionsResult result = validator.Validate("settings", options);
Assert.False(result.Succeeded);
}

[Test]
public void Returns_Fail_For_Configuration_With_Invalid_FileNameFormat()
{
var validator = new LoggingSettingsValidator();
LoggingSettings options = BuildLoggingSettings(fileNameFormat: "InvalidAsTooManyPlaceholders_{0}_{1}");
ValidateOptionsResult result = validator.Validate("settings", options);
Assert.False(result.Succeeded);
}

private static LoggingSettings BuildLoggingSettings(
string fileNameFormat = LoggingConfiguration.DefaultLogFileNameFormat,
string fileNameFormatArguments = LoggingConfiguration.DefaultLogFileNameFormatArguments) =>
new LoggingSettings
{
FileNameFormat = fileNameFormat,
FileNameFormatArguments = fileNameFormatArguments,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Logging;

namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Logging;

[TestFixture]
public class LoggingConfigurationTests
{
[Test]
public void Can_Get_Supported_Log_File_Name_Format_Arguments()
{
var config = new LoggingConfiguration("c:\\logs\\", "UmbracoLogFile_{0}_{1}..json", "MachineName,EnvironmentName");
var result = config.GetLogFileNameFormatArguments();

Assert.AreEqual(2, result.Length);

var expectedMachineName = Environment.MachineName;
var expectedEnvironmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
Assert.AreEqual(expectedMachineName, result[0]);
Assert.AreEqual(expectedEnvironmentName, result[1]);
}
}
Loading