diff --git a/src/Umbraco.Core/Services/ILogViewerRepository.cs b/src/Umbraco.Core/Services/ILogViewerRepository.cs
index 770099668bc2..6d93928efc11 100644
--- a/src/Umbraco.Core/Services/ILogViewerRepository.cs
+++ b/src/Umbraco.Core/Services/ILogViewerRepository.cs
@@ -1,8 +1,11 @@
-using Umbraco.Cms.Core.Logging;
+using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Logging.Viewer;
namespace Umbraco.Cms.Core.Services;
+///
+/// Represents a repository for viewing logs in Umbraco.
+///
public interface ILogViewerRepository
{
///
diff --git a/src/Umbraco.Core/Services/LogViewerService.cs b/src/Umbraco.Core/Services/LogViewerService.cs
index 0780838416a9..5ca2a087855f 100644
--- a/src/Umbraco.Core/Services/LogViewerService.cs
+++ b/src/Umbraco.Core/Services/LogViewerService.cs
@@ -1,212 +1,58 @@
-using System.Collections.ObjectModel;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Logging.Viewer;
-using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
-using Umbraco.Extensions;
-using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
namespace Umbraco.Cms.Core.Services;
-public class LogViewerService : ILogViewerService
+///
+/// Represents a service for viewing logs in Umbraco.
+///
+public class LogViewerService : LogViewerServiceBase
{
private const int FileSizeCap = 100;
- private readonly ILogViewerQueryRepository _logViewerQueryRepository;
- private readonly ICoreScopeProvider _provider;
private readonly ILoggingConfiguration _loggingConfiguration;
- private readonly ILogViewerRepository _logViewerRepository;
+ ///
+ /// Initializes a new instance of the class.
+ ///
public LogViewerService(
ILogViewerQueryRepository logViewerQueryRepository,
ICoreScopeProvider provider,
ILoggingConfiguration loggingConfiguration,
ILogViewerRepository logViewerRepository)
+ : base(
+ logViewerQueryRepository,
+ provider,
+ logViewerRepository)
{
- _logViewerQueryRepository = logViewerQueryRepository;
- _provider = provider;
_loggingConfiguration = loggingConfiguration;
- _logViewerRepository = logViewerRepository;
- }
-
- ///
- public Task?, LogViewerOperationStatus>> GetPagedLogsAsync(
- DateTimeOffset? startDate,
- DateTimeOffset? endDate,
- int skip,
- int take,
- Direction orderDirection = Direction.Descending,
- string? filterExpression = null,
- string[]? logLevels = null)
- {
- LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
-
- // We will need to stop the request if trying to do this on a 1GB file
- if (CanViewLogs(logTimePeriod) == false)
- {
- return Task.FromResult(Attempt.FailWithStatus?, LogViewerOperationStatus>(
- LogViewerOperationStatus.CancelledByLogsSizeValidation,
- null));
- }
-
-
- PagedModel filteredLogs = GetFilteredLogs(logTimePeriod, filterExpression, logLevels, orderDirection, skip, take);
-
- return Task.FromResult(Attempt.SucceedWithStatus?, LogViewerOperationStatus>(
- LogViewerOperationStatus.Success,
- filteredLogs));
}
///
- public Task> GetSavedLogQueriesAsync(int skip, int take)
- {
- using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
- ILogViewerQuery[] savedLogQueries = _logViewerQueryRepository.GetMany().ToArray();
- var pagedModel = new PagedModel(savedLogQueries.Length, savedLogQueries.Skip(skip).Take(take));
- return Task.FromResult(pagedModel);
- }
+ protected override string LoggerName => "UmbracoFile";
///
- public Task GetSavedLogQueryByNameAsync(string name)
+ public override Task> CanViewLogsAsync(LogTimePeriod logTimePeriod)
{
- using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
- return Task.FromResult(_logViewerQueryRepository.GetByName(name));
- }
-
- ///
- public async Task> AddSavedLogQueryAsync(string name, string query)
- {
- ILogViewerQuery? logViewerQuery = await GetSavedLogQueryByNameAsync(name);
-
- if (logViewerQuery is not null)
- {
- return Attempt.FailWithStatus(LogViewerOperationStatus.DuplicateLogSearch, null);
- }
-
- logViewerQuery = new LogViewerQuery(name, query);
-
- using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
- _logViewerQueryRepository.Save(logViewerQuery);
-
- return Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, logViewerQuery);
- }
-
- ///
- public async Task> DeleteSavedLogQueryAsync(string name)
- {
- ILogViewerQuery? logViewerQuery = await GetSavedLogQueryByNameAsync(name);
-
- if (logViewerQuery is null)
- {
- return Attempt.FailWithStatus(LogViewerOperationStatus.NotFoundLogSearch, null);
- }
-
- using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
- _logViewerQueryRepository.Delete(logViewerQuery);
-
- return Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, logViewerQuery);
- }
-
- ///
- public Task> CanViewLogsAsync(DateTimeOffset? startDate, DateTimeOffset? endDate)
- {
- LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
bool isAllowed = CanViewLogs(logTimePeriod);
if (isAllowed == false)
{
- return Task.FromResult(Attempt.FailWithStatus(LogViewerOperationStatus.CancelledByLogsSizeValidation, isAllowed));
- }
-
- return Task.FromResult(Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, isAllowed));
- }
-
- ///
- public Task> GetLogLevelCountsAsync(DateTimeOffset? startDate, DateTimeOffset? endDate)
- {
- LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
-
- // We will need to stop the request if trying to do this on a 1GB file
- if (CanViewLogs(logTimePeriod) == false)
- {
- return Task.FromResult(Attempt.FailWithStatus(
+ return Task.FromResult(Attempt.FailWithStatus(
LogViewerOperationStatus.CancelledByLogsSizeValidation,
- null));
- }
-
- LogLevelCounts counter = _logViewerRepository.GetLogCount(logTimePeriod);
-
- return Task.FromResult(Attempt.SucceedWithStatus(
- LogViewerOperationStatus.Success,
- counter));
- }
-
- ///
- public Task, LogViewerOperationStatus>> GetMessageTemplatesAsync(DateTimeOffset? startDate, DateTimeOffset? endDate, int skip, int take)
- {
- LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
-
- // We will need to stop the request if trying to do this on a 1GB file
- if (CanViewLogs(logTimePeriod) == false)
- {
- return Task.FromResult(Attempt.FailWithStatus, LogViewerOperationStatus>(
- LogViewerOperationStatus.CancelledByLogsSizeValidation,
- null!));
- }
-
- LogTemplate[] messageTemplates = _logViewerRepository.GetMessageTemplates(logTimePeriod);
-
- return Task.FromResult(Attempt.SucceedWithStatus(
- LogViewerOperationStatus.Success,
- new PagedModel(messageTemplates.Length, messageTemplates.Skip(skip).Take(take))));
- }
-
- ///
- public ReadOnlyDictionary GetLogLevelsFromSinks()
- {
- var configuredLogLevels = new Dictionary
- {
- { "Global", GetGlobalMinLogLevel() },
- { "UmbracoFile", _logViewerRepository.RestrictedToMinimumLevel() },
- };
-
- return configuredLogLevels.AsReadOnly();
- }
-
- ///
- public LogLevel GetGlobalMinLogLevel() => _logViewerRepository.GetGlobalMinLogLevel();
-
- ///
- /// Returns a representation from a start and end date for filtering log files.
- ///
- /// The start date for the date range (can be null).
- /// The end date for the date range (can be null).
- /// The LogTimePeriod object used to filter logs.
- private LogTimePeriod GetTimePeriod(DateTimeOffset? startDate, DateTimeOffset? endDate)
- {
- if (startDate is null || endDate is null)
- {
- DateTime now = DateTime.Now;
- if (startDate is null)
- {
- startDate = now.AddDays(-1);
- }
-
- if (endDate is null)
- {
- endDate = now;
- }
+ isAllowed));
}
- return new LogTimePeriod(startDate.Value.LocalDateTime, endDate.Value.LocalDateTime);
+ return Task.FromResult(Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, isAllowed));
}
///
/// Returns a value indicating whether to stop a GET request that is attempting to fetch logs from a 1GB file.
///
/// The time period to filter the logs.
- /// The value whether or not you are able to view the logs.
+ /// Whether you are able to view the logs.
private bool CanViewLogs(LogTimePeriod logTimePeriod)
{
// Number of entries
@@ -230,52 +76,5 @@ private bool CanViewLogs(LogTimePeriod logTimePeriod)
return logSizeAsMegabytes <= FileSizeCap;
}
- private PagedModel GetFilteredLogs(
- LogTimePeriod logTimePeriod,
- string? filterExpression,
- string[]? logLevels,
- Direction orderDirection,
- int skip,
- int take)
- {
- IEnumerable logs = _logViewerRepository.GetLogs(logTimePeriod, filterExpression).ToArray();
-
- // 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();
- var validLogType = true;
- foreach (var level in logLevels)
- {
- // Check if level string is part of the LogEventLevel enum
- if (Enum.IsDefined(typeof(LogLevel), level))
- {
- validLogType = true;
- logsAfterLevelFilters.AddRange(logs.Where(x =>
- string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase)));
- }
- else
- {
- validLogType = false;
- }
- }
-
- if (validLogType)
- {
- logs = logsAfterLevelFilters;
- }
- }
-
- return new PagedModel
- {
- Total = logs.Count(),
- Items = logs
- .OrderBy(l => l.Timestamp, orderDirection)
- .Skip(skip)
- .Take(take),
- };
- }
-
- private string GetSearchPattern(DateTime day) => $"*{day:yyyyMMdd}*.json";
+ private static string GetSearchPattern(DateTime day) => $"*{day:yyyyMMdd}*.json";
}
diff --git a/src/Umbraco.Core/Services/LogViewerServiceBase.cs b/src/Umbraco.Core/Services/LogViewerServiceBase.cs
new file mode 100644
index 000000000000..0b69e964e17d
--- /dev/null
+++ b/src/Umbraco.Core/Services/LogViewerServiceBase.cs
@@ -0,0 +1,269 @@
+using System.Collections.ObjectModel;
+using Umbraco.Cms.Core.Logging.Viewer;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Persistence.Repositories;
+using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Core.Services.OperationStatus;
+using Umbraco.Extensions;
+using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
+
+namespace Umbraco.Cms.Core.Services;
+
+///
+/// Base class for log viewer services that provides common functionality for managing log entries and queries.
+///
+public abstract class LogViewerServiceBase : ILogViewerService
+{
+ private readonly ILogViewerQueryRepository _logViewerQueryRepository;
+ private readonly ICoreScopeProvider _provider;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected LogViewerServiceBase(
+ ILogViewerQueryRepository logViewerQueryRepository,
+ ICoreScopeProvider provider,
+ ILogViewerRepository logViewerRepository)
+ {
+ _logViewerQueryRepository = logViewerQueryRepository;
+ _provider = provider;
+ LogViewerRepository = logViewerRepository;
+ }
+
+ ///
+ /// Gets the .
+ ///
+ protected ILogViewerRepository LogViewerRepository { get; }
+
+ ///
+ /// Gets the name of the logger.
+ ///
+ protected abstract string LoggerName { get; }
+
+ ///
+ public virtual ReadOnlyDictionary GetLogLevelsFromSinks()
+ {
+ var configuredLogLevels = new Dictionary
+ {
+ { "Global", GetGlobalMinLogLevel() },
+ { LoggerName, LogViewerRepository.RestrictedToMinimumLevel() },
+ };
+
+ return configuredLogLevels.AsReadOnly();
+ }
+
+ ///
+ public virtual LogLevel GetGlobalMinLogLevel() => LogViewerRepository.GetGlobalMinLogLevel();
+
+ ///
+ public virtual Task GetSavedLogQueryByNameAsync(string name)
+ {
+ using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
+ return Task.FromResult(_logViewerQueryRepository.GetByName(name));
+ }
+
+ ///
+ public virtual async Task> AddSavedLogQueryAsync(string name, string query)
+ {
+ ILogViewerQuery? logViewerQuery = await GetSavedLogQueryByNameAsync(name);
+
+ if (logViewerQuery is not null)
+ {
+ return Attempt.FailWithStatus(
+ LogViewerOperationStatus.DuplicateLogSearch, null);
+ }
+
+ logViewerQuery = new LogViewerQuery(name, query);
+
+ using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
+ _logViewerQueryRepository.Save(logViewerQuery);
+
+ return Attempt.SucceedWithStatus(
+ LogViewerOperationStatus.Success,
+ logViewerQuery);
+ }
+
+ ///
+ public virtual async Task> DeleteSavedLogQueryAsync(string name)
+ {
+ ILogViewerQuery? logViewerQuery = await GetSavedLogQueryByNameAsync(name);
+
+ if (logViewerQuery is null)
+ {
+ return Attempt.FailWithStatus(
+ LogViewerOperationStatus.NotFoundLogSearch, null);
+ }
+
+ using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
+ _logViewerQueryRepository.Delete(logViewerQuery);
+
+ return Attempt.SucceedWithStatus(
+ LogViewerOperationStatus.Success,
+ logViewerQuery);
+ }
+
+ ///
+ public virtual Task> GetSavedLogQueriesAsync(int skip, int take)
+ {
+ using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
+ ILogViewerQuery[] savedLogQueries = _logViewerQueryRepository.GetMany().ToArray();
+ var pagedModel = new PagedModel(savedLogQueries.Length, savedLogQueries.Skip(skip).Take(take));
+ return Task.FromResult(pagedModel);
+ }
+
+ ///
+ public virtual async Task> GetLogLevelCountsAsync(
+ DateTimeOffset? startDate, DateTimeOffset? endDate)
+ {
+ LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
+
+ Attempt canViewLogs = await CanViewLogsAsync(logTimePeriod);
+
+ // We will need to stop the request if trying to do this on a 1GB file
+ if (canViewLogs.Success == false)
+ {
+ return Attempt.FailWithStatus(
+ LogViewerOperationStatus.CancelledByLogsSizeValidation,
+ null);
+ }
+
+ LogLevelCounts counter = LogViewerRepository.GetLogCount(logTimePeriod);
+
+ return Attempt.SucceedWithStatus(
+ LogViewerOperationStatus.Success,
+ counter);
+ }
+
+ ///
+ public virtual async Task, LogViewerOperationStatus>> GetMessageTemplatesAsync(
+ DateTimeOffset? startDate, DateTimeOffset? endDate, int skip, int take)
+ {
+ LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
+
+ Attempt canViewLogs = await CanViewLogsAsync(logTimePeriod);
+
+ // We will need to stop the request if trying to do this on a 1GB file
+ if (canViewLogs.Success == false)
+ {
+ return Attempt.FailWithStatus, LogViewerOperationStatus>(
+ LogViewerOperationStatus.CancelledByLogsSizeValidation,
+ null!);
+ }
+
+ LogTemplate[] messageTemplates = LogViewerRepository.GetMessageTemplates(logTimePeriod);
+
+ return Attempt.SucceedWithStatus(
+ LogViewerOperationStatus.Success,
+ new PagedModel(messageTemplates.Length, messageTemplates.Skip(skip).Take(take)));
+ }
+
+ ///
+ public virtual async Task?, LogViewerOperationStatus>> GetPagedLogsAsync(
+ DateTimeOffset? startDate,
+ DateTimeOffset? endDate,
+ int skip,
+ int take,
+ Direction orderDirection = Direction.Descending,
+ string? filterExpression = null,
+ string[]? logLevels = null)
+ {
+ LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
+
+ Attempt canViewLogs = await CanViewLogsAsync(logTimePeriod);
+
+ // We will need to stop the request if trying to do this on a 1GB file
+ if (canViewLogs.Success == false)
+ {
+ return Attempt.FailWithStatus?, LogViewerOperationStatus>(
+ LogViewerOperationStatus.CancelledByLogsSizeValidation,
+ null);
+ }
+
+ PagedModel filteredLogs =
+ GetFilteredLogs(logTimePeriod, filterExpression, logLevels, orderDirection, skip, take);
+
+ return Attempt.SucceedWithStatus?, LogViewerOperationStatus>(
+ LogViewerOperationStatus.Success,
+ filteredLogs);
+ }
+
+ ///
+ public virtual Task> CanViewLogsAsync(
+ DateTimeOffset? startDate,
+ DateTimeOffset? endDate)
+ => CanViewLogsAsync(GetTimePeriod(startDate, endDate));
+
+ ///
+ /// Checks if the logs for the specified time period can be viewed.
+ ///
+ public abstract Task> CanViewLogsAsync(LogTimePeriod logTimePeriod);
+
+
+ ///
+ /// Returns a representation from a start and end date for filtering log files.
+ ///
+ /// The start date for the date range (can be null).
+ /// The end date for the date range (can be null).
+ /// The LogTimePeriod object used to filter logs.
+ protected virtual LogTimePeriod GetTimePeriod(DateTimeOffset? startDate, DateTimeOffset? endDate)
+ {
+ if (startDate is null || endDate is null)
+ {
+ DateTime now = DateTime.Now;
+ startDate ??= now.AddDays(-1);
+ endDate ??= now;
+ }
+
+ return new LogTimePeriod(startDate.Value.LocalDateTime, endDate.Value.LocalDateTime);
+ }
+
+ ///
+ /// Gets a filtered page of logs from storage based on the provided parameters.
+ ///
+ protected PagedModel GetFilteredLogs(
+ LogTimePeriod logTimePeriod,
+ string? filterExpression,
+ string[]? logLevels,
+ Direction orderDirection,
+ int skip,
+ int take)
+ {
+ IEnumerable logs = LogViewerRepository.GetLogs(logTimePeriod, filterExpression).ToArray();
+
+ // 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();
+ var validLogType = true;
+ foreach (var level in logLevels)
+ {
+ // Check if level string is part of the LogEventLevel enum
+ if (Enum.IsDefined(typeof(LogLevel), level))
+ {
+ validLogType = true;
+ logsAfterLevelFilters.AddRange(logs.Where(x =>
+ string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase)));
+ }
+ else
+ {
+ validLogType = false;
+ }
+ }
+
+ if (validLogType)
+ {
+ logs = logsAfterLevelFilters;
+ }
+ }
+
+ return new PagedModel
+ {
+ Total = logs.Count(),
+ Items = logs
+ .OrderBy(l => l.Timestamp, orderDirection)
+ .Skip(skip)
+ .Take(take),
+ };
+ }
+}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs
index f899f311f5ce..19b6bc05d5be 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs
@@ -195,6 +195,7 @@ public static IUmbracoBuilder ConfigureFileSystems(
///
/// The type of the log viewer.
/// The builder.
+ [Obsolete("No longer used. Scheduled removal in Umbraco 18.")]
public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder)
where T : class, ILogViewer
{
@@ -207,6 +208,7 @@ public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder)
///
/// The builder.
/// A function creating a log viewer.
+ [Obsolete("No longer used. Scheduled removal in Umbraco 18.")]
public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder, Func factory)
{
builder.Services.AddUnique(factory);
@@ -218,6 +220,7 @@ public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder, Func
/// A builder.
/// A log viewer.
+ [Obsolete("No longer used. Scheduled removal in Umbraco 18.")]
public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
{
builder.Services.AddUnique(viewer);
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs
index 9d3213bd2a9b..4006c7ca1642 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs
@@ -22,7 +22,7 @@ public static class LoggerConfigExtensions
/// Such as adding ProcessID, Thread, AppDomain etc
/// It is highly recommended that you keep/use this default in your own logging config customizations
///
- [Obsolete("Please use an alternative method. This will be removed in Umbraco 13.")]
+ [Obsolete("Please use an alternative method. Scheduled for removal from Umbraco 13.")]
public static LoggerConfiguration MinimalConfiguration(
this LoggerConfiguration logConfig,
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
@@ -37,7 +37,7 @@ public static LoggerConfiguration MinimalConfiguration(
/// Such as adding ProcessID, Thread, AppDomain etc
/// It is highly recommended that you keep/use this default in your own logging config customizations
///
- [Obsolete("Please use an alternative method. This will be removed in Umbraco 13.")]
+ [Obsolete("Please use an alternative method. Scheduled for removal from Umbraco 13.")]
public static LoggerConfiguration MinimalConfiguration(
this LoggerConfiguration logConfig,
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
@@ -68,7 +68,7 @@ public static LoggerConfiguration MinimalConfiguration(
umbFileConfiguration = umbracoFileConfiguration;
logConfig.WriteTo.UmbracoFile(
- path : umbracoFileConfiguration.GetPath(loggingConfiguration.LogDirectory),
+ path: umbracoFileConfiguration.GetPath(loggingConfiguration.LogDirectory),
fileSizeLimitBytes: umbracoFileConfiguration.FileSizeLimitBytes,
restrictedToMinimumLevel: umbracoFileConfiguration.RestrictedToMinimumLevel,
rollingInterval: umbracoFileConfiguration.RollingInterval,
@@ -79,7 +79,6 @@ public static LoggerConfiguration MinimalConfiguration(
return logConfig;
}
-
///
/// This configures Serilog with some defaults
/// Such as adding ProcessID, Thread, AppDomain etc
@@ -108,14 +107,17 @@ public static LoggerConfiguration MinimalConfiguration(
.Enrich.With()
.Enrich.FromLogContext(); // allows us to dynamically enrich
- logConfig.WriteTo.UmbracoFile(
- path: umbracoFileConfiguration.GetPath(loggingConfiguration.LogDirectory, loggingConfiguration.LogFileNameFormat, loggingConfiguration.GetLogFileNameFormatArguments()),
- fileSizeLimitBytes: umbracoFileConfiguration.FileSizeLimitBytes,
- restrictedToMinimumLevel: umbracoFileConfiguration.RestrictedToMinimumLevel,
- rollingInterval: umbracoFileConfiguration.RollingInterval,
- flushToDiskInterval: umbracoFileConfiguration.FlushToDiskInterval,
- rollOnFileSizeLimit: umbracoFileConfiguration.RollOnFileSizeLimit,
- retainedFileCountLimit: umbracoFileConfiguration.RetainedFileCountLimit);
+ if (umbracoFileConfiguration.Enabled)
+ {
+ logConfig.WriteTo.UmbracoFile(
+ path: umbracoFileConfiguration.GetPath(loggingConfiguration.LogDirectory, loggingConfiguration.LogFileNameFormat, loggingConfiguration.GetLogFileNameFormatArguments()),
+ fileSizeLimitBytes: umbracoFileConfiguration.FileSizeLimitBytes,
+ restrictedToMinimumLevel: umbracoFileConfiguration.RestrictedToMinimumLevel,
+ rollingInterval: umbracoFileConfiguration.RollingInterval,
+ flushToDiskInterval: umbracoFileConfiguration.FlushToDiskInterval,
+ rollOnFileSizeLimit: umbracoFileConfiguration.RollOnFileSizeLimit,
+ retainedFileCountLimit: umbracoFileConfiguration.RetainedFileCountLimit);
+ }
return logConfig;
}
@@ -127,7 +129,7 @@ public static LoggerConfiguration MinimalConfiguration(
///
/// The log level you wish the JSON file to collect - default is Verbose (highest)
///
- [Obsolete("Will be removed in Umbraco 13.")]
+ [Obsolete("Scheduled for removal from Umbraco 13.")]
public static LoggerConfiguration OutputDefaultTextFile(
this LoggerConfiguration logConfig,
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
@@ -136,7 +138,7 @@ public static LoggerConfiguration OutputDefaultTextFile(
//Main .txt logfile - in similar format to older Log4Net output
//Ends with ..txt as Date is inserted before file extension substring
logConfig.WriteTo.File(
- Path.Combine(hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles), $"UmbracoTraceLog.{Environment.MachineName}..txt"),
+ Path.Combine(hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles), $"UmbracoTraceLog.{Environment.MachineName}..txt"),
shared: true,
rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: minimumLevel,
@@ -213,7 +215,6 @@ public static LoggerConfiguration UmbracoFile(
null));
}
-
///
/// Outputs a CLEF format JSON log at /App_Data/Logs/
///
@@ -222,7 +223,7 @@ public static LoggerConfiguration UmbracoFile(
/// The log level you wish the JSON file to collect - default is Verbose (highest)
///
/// The number of days to keep log files. Default is set to null which means all logs are kept
- [Obsolete("Will be removed in Umbraco 13.")]
+ [Obsolete("Scheduled for removal from Umbraco 13.")]
public static LoggerConfiguration OutputDefaultJsonFile(
this LoggerConfiguration logConfig,
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
@@ -234,7 +235,7 @@ public static LoggerConfiguration OutputDefaultJsonFile(
// Ends with ..txt as Date is inserted before file extension substring
logConfig.WriteTo.File(
new CompactJsonFormatter(),
- Path.Combine(hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles) ,$"UmbracoTraceLog.{Environment.MachineName}..json"),
+ Path.Combine(hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles), $"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
@@ -270,6 +271,5 @@ public static LoggerConfiguration OutputDefaultJsonFile(
return logConfig;
}
-
}
}
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
index 4eb054b2a5de..2b91932f8b80 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs
@@ -19,7 +19,7 @@ public SerilogLogger(LoggerConfiguration logConfig) =>
public ILogger SerilogLog { get; }
- [Obsolete]
+ [Obsolete("Scheduled for removal in Umbraco 17.")]
public static SerilogLogger CreateWithDefaultConfiguration(
IHostingEnvironment hostingEnvironment,
ILoggingConfiguration loggingConfiguration,
@@ -32,7 +32,7 @@ public static SerilogLogger CreateWithDefaultConfiguration(
/// Creates a logger with some pre-defined configuration and remainder from config file
///
/// Used by UmbracoApplicationBase to get its logger.
- [Obsolete]
+ [Obsolete("Scheduled for removal in Umbraco 17.")]
public static SerilogLogger CreateWithDefaultConfiguration(
IHostingEnvironment hostingEnvironment,
ILoggingConfiguration loggingConfiguration,
diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/UmbracoFileConfiguration.cs b/src/Umbraco.Infrastructure/Logging/Serilog/UmbracoFileConfiguration.cs
index 5d650dac07c2..33a6b549afdb 100644
--- a/src/Umbraco.Infrastructure/Logging/Serilog/UmbracoFileConfiguration.cs
+++ b/src/Umbraco.Infrastructure/Logging/Serilog/UmbracoFileConfiguration.cs
@@ -22,6 +22,7 @@ public UmbracoFileConfiguration(IConfiguration configuration)
{
IConfigurationSection? args = umbracoFileAppSettings.GetSection("Args");
+ Enabled = args.GetValue(nameof(Enabled), Enabled);
RestrictedToMinimumLevel = args.GetValue(nameof(RestrictedToMinimumLevel), RestrictedToMinimumLevel);
FileSizeLimitBytes = args.GetValue(nameof(FileSizeLimitBytes), FileSizeLimitBytes);
RollingInterval = args.GetValue(nameof(RollingInterval), RollingInterval);
@@ -31,6 +32,8 @@ public UmbracoFileConfiguration(IConfiguration configuration)
}
}
+ public bool Enabled { get; set; } = true;
+
public LogEventLevel RestrictedToMinimumLevel { get; set; } = LogEventLevel.Verbose;
public long FileSizeLimitBytes { get; set; } = 1073741824;
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs b/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs
index 00d0f0517d93..82d2277dc8ca 100644
--- a/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/ILogViewer.cs
@@ -2,6 +2,7 @@
namespace Umbraco.Cms.Core.Logging.Viewer;
+[Obsolete("Use ILogViewerService instead. Scheduled removal in Umbraco 18.")]
public interface ILogViewer
{
bool CanHandleLargeLogs { get; }
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/SerilogJsonLogViewer.cs b/src/Umbraco.Infrastructure/Logging/Viewer/SerilogJsonLogViewer.cs
deleted file mode 100644
index 9c8dace1cf1d..000000000000
--- a/src/Umbraco.Infrastructure/Logging/Viewer/SerilogJsonLogViewer.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Serilog.Events;
-using Serilog.Formatting.Compact.Reader;
-using ILogger = Serilog.ILogger;
-
-namespace Umbraco.Cms.Core.Logging.Viewer;
-
-internal sealed class SerilogJsonLogViewer : SerilogLogViewerSourceBase
-{
- private const int FileSizeCap = 100;
- private readonly ILogger _logger;
- private readonly string _logsPath;
-
- public SerilogJsonLogViewer(
- ILogger logger,
- ILogViewerConfig logViewerConfig,
- ILoggingConfiguration loggingConfiguration,
- ILogLevelLoader logLevelLoader,
- ILogger serilogLog)
- : base(logViewerConfig, logLevelLoader, serilogLog)
- {
- _logger = logger;
- _logsPath = loggingConfiguration.LogDirectory;
- }
-
- public override bool CanHandleLargeLogs => false;
-
- [Obsolete("Use ILogViewerService.CanViewLogsAsync instead. Scheduled for removal in Umbraco 15.")]
- 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 (DateTime 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;
- }
-
- protected override IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip,
- int take)
- {
- var logs = new List();
-
- 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 (DateTime 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(_logsPath, 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 LogEvent? 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 static string GetSearchPattern(DateTime day) => $"*{day:yyyyMMdd}*.json";
-
- private bool TryRead(LogEventReader reader, out LogEvent? evt)
- {
- try
- {
- return reader.TryRead(out evt);
- }
- catch (Exception 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.LogError(ex, "Unable to parse a line in the JSON log file");
-
- evt = null;
- return true;
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs b/src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs
index d2f21cf5d0cf..cd485e766071 100644
--- a/src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs
+++ b/src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs
@@ -6,6 +6,7 @@
namespace Umbraco.Cms.Core.Logging.Viewer;
+[Obsolete("Use ILogViewerService instead. Scheduled removal in Umbraco 18.")]
public abstract class SerilogLogViewerSourceBase : ILogViewer
{
private readonly ILogLevelLoader _logLevelLoader;
diff --git a/src/Umbraco.Infrastructure/Services/Implement/LogViewerRepository.cs b/src/Umbraco.Infrastructure/Services/Implement/LogViewerRepository.cs
index e1aac6486a41..70f4588922ab 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/LogViewerRepository.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/LogViewerRepository.cs
@@ -1,83 +1,40 @@
-using Microsoft.Extensions.Logging;
-using Serilog;
+using Microsoft.Extensions.Logging;
using Serilog.Events;
using Serilog.Formatting.Compact.Reader;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.Serialization;
-using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Logging.Serilog;
using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
namespace Umbraco.Cms.Infrastructure.Services.Implement;
-public class LogViewerRepository : ILogViewerRepository
+///
+/// Repository for accessing log entries from the Umbraco log files stored on disk.
+///
+public class LogViewerRepository : LogViewerRepositoryBase
{
private readonly ILoggingConfiguration _loggingConfiguration;
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
- private readonly UmbracoFileConfiguration _umbracoFileConfig;
- public LogViewerRepository(ILoggingConfiguration loggingConfiguration, ILogger logger, IJsonSerializer jsonSerializer, UmbracoFileConfiguration umbracoFileConfig)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LogViewerRepository(
+ ILoggingConfiguration loggingConfiguration,
+ ILogger logger,
+ IJsonSerializer jsonSerializer,
+ UmbracoFileConfiguration umbracoFileConfig)
+ : base(umbracoFileConfig)
{
_loggingConfiguration = loggingConfiguration;
_logger = logger;
_jsonSerializer = jsonSerializer;
- _umbracoFileConfig = umbracoFileConfig;
}
- ///
- public IEnumerable GetLogs(LogTimePeriod logTimePeriod, string? filterExpression = null)
- {
- var expressionFilter = new ExpressionFilter(filterExpression);
-
- return GetLogs(logTimePeriod, expressionFilter);
- }
-
- ///
- public LogLevelCounts GetLogCount(LogTimePeriod logTimePeriod)
- {
- var counter = new CountingFilter();
-
- GetLogs(logTimePeriod, counter);
-
- return counter.Counts;
- }
-
- ///
- public LogTemplate[] GetMessageTemplates(LogTimePeriod logTimePeriod)
- {
- var messageTemplates = new MessageTemplateFilter();
-
- GetLogs(logTimePeriod, messageTemplates);
-
- return messageTemplates.Counts
- .Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value })
- .OrderByDescending(x => x.Count).ToArray();
- }
-
- ///
- public LogLevel GetGlobalMinLogLevel()
- {
- LogEventLevel logLevel = GetGlobalLogLevelEventMinLevel();
-
- return Enum.Parse(logLevel.ToString());
- }
-
- public LogLevel RestrictedToMinimumLevel()
- {
- LogEventLevel minLevel = _umbracoFileConfig.RestrictedToMinimumLevel;
- return Enum.Parse(minLevel.ToString());
- }
-
- private LogEventLevel GetGlobalLogLevelEventMinLevel() =>
- Enum.GetValues(typeof(LogEventLevel))
- .Cast()
- .Where(Log.IsEnabled)
- .DefaultIfEmpty(LogEventLevel.Information)
- .Min();
-
- private IEnumerable GetLogs(LogTimePeriod logTimePeriod, ILogFilter logFilter)
+ ///
+ protected override IEnumerable GetLogs(LogTimePeriod logTimePeriod, ILogFilter logFilter)
{
var logs = new List();
@@ -163,7 +120,7 @@ private IEnumerable GetLogs(LogTimePeriod logTimePeriod, ILogFilter l
return result.AsReadOnly();
}
- private string GetSearchPattern(DateTime day) => $"*{day:yyyyMMdd}*.json";
+ private static string GetSearchPattern(DateTime day) => $"*{day:yyyyMMdd}*.json";
private bool TryRead(LogEventReader reader, out LogEvent? evt)
{
diff --git a/src/Umbraco.Infrastructure/Services/Implement/LogViewerRepositoryBase.cs b/src/Umbraco.Infrastructure/Services/Implement/LogViewerRepositoryBase.cs
new file mode 100644
index 000000000000..95d650ac6a23
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Services/Implement/LogViewerRepositoryBase.cs
@@ -0,0 +1,84 @@
+using Serilog;
+using Serilog.Events;
+using Umbraco.Cms.Core.Logging.Viewer;
+using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Infrastructure.Logging.Serilog;
+using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
+
+namespace Umbraco.Cms.Infrastructure.Services.Implement;
+
+///
+/// Provides a base class for log viewer repository implementations.
+///
+public abstract class LogViewerRepositoryBase : ILogViewerRepository
+{
+ private readonly UmbracoFileConfiguration _umbracoFileConfig;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public LogViewerRepositoryBase(UmbracoFileConfiguration umbracoFileConfig) => _umbracoFileConfig = umbracoFileConfig;
+
+ ///
+ public virtual IEnumerable GetLogs(LogTimePeriod logTimePeriod, string? filterExpression = null)
+ {
+ var expressionFilter = new ExpressionFilter(filterExpression);
+
+ return GetLogs(logTimePeriod, expressionFilter);
+ }
+
+ ///
+ public virtual LogLevelCounts GetLogCount(LogTimePeriod logTimePeriod)
+ {
+ var counter = new CountingFilter();
+
+ GetLogs(logTimePeriod, counter);
+
+ return counter.Counts;
+ }
+
+ ///
+ public virtual LogTemplate[] GetMessageTemplates(LogTimePeriod logTimePeriod)
+ {
+ var messageTemplates = new MessageTemplateFilter();
+
+ GetLogs(logTimePeriod, messageTemplates);
+
+ return messageTemplates.Counts
+ .Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value })
+ .OrderByDescending(x => x.Count).ToArray();
+ }
+
+ ///
+ public virtual LogLevel GetGlobalMinLogLevel()
+ {
+ LogEventLevel logLevel = GetGlobalLogLevelEventMinLevel();
+
+ return Enum.Parse(logLevel.ToString());
+ }
+
+ ///
+ /// Gets the minimum-level log value from the config file.
+ ///
+ public virtual LogLevel RestrictedToMinimumLevel()
+ {
+ LogEventLevel minLevel = _umbracoFileConfig.RestrictedToMinimumLevel;
+ return Enum.Parse(minLevel.ToString());
+ }
+
+ ///
+ /// Gets the minimum log level from the global Serilog configuration.
+ ///
+ protected virtual LogEventLevel GetGlobalLogLevelEventMinLevel() =>
+ Enum.GetValues(typeof(LogEventLevel))
+ .Cast()
+ .Where(Log.IsEnabled)
+ .DefaultIfEmpty(LogEventLevel.Information)
+ .Min();
+
+ ///
+ /// Retrieves the logs for a specified time period and filter.
+ ///
+ protected abstract IEnumerable GetLogs(LogTimePeriod logTimePeriod, ILogFilter logFilter);
+}
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs
deleted file mode 100644
index fe199b3cf454..000000000000
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright (c) Umbraco.
-// See LICENSE for more details.
-
-using System.Diagnostics;
-using Microsoft.Extensions.Logging;
-using Moq;
-using NUnit.Framework;
-using Serilog;
-using Umbraco.Cms.Core;
-using Umbraco.Cms.Core.Logging.Viewer;
-using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Core.Persistence.Querying;
-using Umbraco.Cms.Core.Persistence.Repositories;
-using Umbraco.Cms.Infrastructure.Migrations.Install;
-using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
-using Umbraco.Cms.Tests.UnitTests.TestHelpers;
-using File = System.IO.File;
-
-namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Logging;
-
-[TestFixture]
-public class LogviewerTests
-{
- [OneTimeSetUp]
- public void Setup()
- {
- var testRoot = TestContext.CurrentContext.TestDirectory.Split("bin")[0];
-
- // Create an example JSON log file to check results
- // As a one time setup for all tets in this class/fixture
- var ioHelper = TestHelper.IOHelper;
- var hostingEnv = TestHelper.GetHostingEnvironment();
-
- var loggingConfiguration = TestHelper.GetLoggingConfiguration(hostingEnv);
-
- var exampleLogfilePath = Path.Combine(testRoot, "TestHelpers", "Assets", LogfileName);
- _newLogfileDirPath = loggingConfiguration.LogDirectory;
- _newLogfilePath = Path.Combine(_newLogfileDirPath, LogfileName);
-
- // Create/ensure Directory exists
- ioHelper.EnsurePathExists(_newLogfileDirPath);
-
- // Copy the sample files
- File.Copy(exampleLogfilePath, _newLogfilePath, true);
-
- var logger = Mock.Of>();
- var logViewerConfig = new LogViewerConfig(LogViewerQueryRepository, TestHelper.ScopeProvider);
- var logLevelLoader = Mock.Of();
- _logViewer =
- new SerilogJsonLogViewer(logger, logViewerConfig, loggingConfiguration, logLevelLoader, Log.Logger);
- }
-
- [OneTimeTearDown]
- public void TearDown()
- {
- // Cleanup & delete the example log & search files off disk
- // Once all tests in this class/fixture have run
- if (File.Exists(_newLogfilePath))
- {
- File.Delete(_newLogfilePath);
- }
- }
-
- private ILogViewer _logViewer;
-
- private const string LogfileName = "UmbracoTraceLog.UNITTEST.20181112.json";
-
- private string _newLogfilePath;
- private string _newLogfileDirPath;
-
- private readonly LogTimePeriod _logTimePeriod = new(
- new DateTime(2018, 11, 12, 0, 0, 0),
- new DateTime(2018, 11, 13, 0, 0, 0));
-
- private ILogViewerQueryRepository LogViewerQueryRepository { get; } = new TestLogViewerQueryRepository();
-
- [Test]
- public void Logs_Contain_Correct_Error_Count()
- {
- var numberOfErrors = _logViewer.GetNumberOfErrors(_logTimePeriod);
-
- // Our dummy log should contain 2 errors
- Assert.AreEqual(1, numberOfErrors);
- }
-
- [Test]
- public void Logs_Contain_Correct_Log_Level_Counts()
- {
- var logCounts = _logViewer.GetLogLevelCounts(_logTimePeriod);
-
- Assert.AreEqual(55, logCounts.Debug);
- Assert.AreEqual(1, logCounts.Error);
- Assert.AreEqual(0, logCounts.Fatal);
- Assert.AreEqual(38, logCounts.Information);
- Assert.AreEqual(6, logCounts.Warning);
- }
-
- [Test]
- public void Logs_Contains_Correct_Message_Templates()
- {
- var templates = _logViewer.GetMessageTemplates(_logTimePeriod).ToArray();
-
- // Count no of templates
- Assert.AreEqual(25, templates.Count());
-
- // Verify all templates & counts are unique
- CollectionAssert.AllItemsAreUnique(templates);
-
- // Ensure the collection contains LogTemplate objects
- CollectionAssert.AllItemsAreInstancesOfType(templates, typeof(LogTemplate));
-
- // Get first item & verify its template & count are what we expect
- var popularTemplate = templates.FirstOrDefault();
-
- Assert.IsNotNull(popularTemplate);
- Assert.AreEqual("{EndMessage} ({Duration}ms) [Timing {TimingId}]", popularTemplate.MessageTemplate);
- Assert.AreEqual(26, popularTemplate.Count);
- }
-
- [Test]
- public void Logs_Can_Open_As_Small_File()
- {
- // We are just testing a return value (as we know the example file is less than 200MB)
- // But this test method does not test/check that
- var canOpenLogs = _logViewer.CheckCanOpenLogs(_logTimePeriod);
- Assert.IsTrue(canOpenLogs);
- }
-
- [Test]
- public void Logs_Can_Be_Queried()
- {
- var sw = new Stopwatch();
- sw.Start();
-
- // Should get me the most 100 recent log entries & using default overloads for remaining params
- var allLogs = _logViewer.GetLogs(_logTimePeriod, 1);
-
- sw.Stop();
-
- // Check we get 100 results back for a page & total items all good :)
- Assert.AreEqual(100, allLogs.Items.Count());
- Assert.AreEqual(102, allLogs.TotalItems);
- Assert.AreEqual(2, allLogs.TotalPages);
-
- // Check collection all contain same object type
- CollectionAssert.AllItemsAreInstancesOfType(allLogs.Items, typeof(LogMessage));
-
- // Check first item is newest
- var newestItem = allLogs.Items.First();
- DateTimeOffset.TryParse("2018-11-12T08:39:18.1971147Z", out var newDate);
- Assert.AreEqual(newDate, newestItem.Timestamp);
-
- // Check we call method again with a smaller set of results & in ascending
- var smallQuery = _logViewer.GetLogs(_logTimePeriod, 1, 10, Direction.Ascending);
- Assert.AreEqual(10, smallQuery.Items.Count());
- Assert.AreEqual(11, smallQuery.TotalPages);
-
- // Check first item is oldest
- var oldestItem = smallQuery.Items.First();
- DateTimeOffset.TryParse("2018-11-12T08:34:45.8371142Z", out var oldDate);
- Assert.AreEqual(oldDate, oldestItem.Timestamp);
-
- // Check invalid log levels
- // Rather than expect 0 items - get all items back & ignore the invalid levels
- string[] invalidLogLevels = { "Invalid", "NotALevel" };
- var queryWithInvalidLevels = _logViewer.GetLogs(_logTimePeriod, 1, logLevels: invalidLogLevels);
- Assert.AreEqual(102, queryWithInvalidLevels.TotalItems);
-
- // Check we can call method with an array of logLevel (error & warning)
- string[] logLevels = { "Warning", "Error" };
- var queryWithLevels = _logViewer.GetLogs(_logTimePeriod, 1, logLevels: logLevels);
- Assert.AreEqual(7, queryWithLevels.TotalItems);
-
- // Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results)
- string[] logLevelMismatch = { "Debug", "Information" };
- var filterLevelQuery = _logViewer.GetLogs(
- _logTimePeriod,
- 1,
- filterExpression: "@Level='Warning'",
- logLevels: logLevelMismatch);
- Assert.AreEqual(0, filterLevelQuery.TotalItems);
- }
-
- [TestCase("", 102)]
- [TestCase("Has(@Exception)", 1)]
- [TestCase("Has(@x)", 1)]
- [TestCase("Has(Duration) and Duration > 1000", 2)]
- [TestCase("Not(@Level = 'Verbose') and Not(@Level = 'Debug')", 45)]
- [TestCase("Not(@l = 'Verbose') and Not(@l = 'Debug')", 45)]
- [TestCase("StartsWith(SourceContext, 'Umbraco.Core')", 86)]
- [TestCase("@MessageTemplate = '{EndMessage} ({Duration}ms) [Timing {TimingId}]'", 26)]
- [TestCase("@mt = '{EndMessage} ({Duration}ms) [Timing {TimingId}]'", 26)]
- [TestCase("SortedComponentTypes[?] = 'Umbraco.Web.Search.ExamineComponent'", 1)]
- [TestCase("Contains(SortedComponentTypes[?], 'DatabaseServer')", 1)]
- [TestCase("@Message like '%definition%'", 6)]
- [TestCase("definition", 6)]
- [Test]
- public void Logs_Can_Query_With_Expressions(string queryToVerify, int expectedCount)
- {
- var testQuery = _logViewer.GetLogs(_logTimePeriod, 1, filterExpression: queryToVerify);
- Assert.AreEqual(expectedCount, testQuery.TotalItems);
- }
-
- [Test]
- public void Log_Search_Can_Persist()
- {
- // Add a new search
- _logViewer.AddSavedSearch("Unit Test Example", "Has(UnitTest)");
-
- var searches = _logViewer.GetSavedSearches();
-
- // Check if we can find the newly added item from the results we get back
- var findItem = searches.Where(x => x.Name == "Unit Test Example" && x.Query == "Has(UnitTest)");
-
- Assert.IsNotNull(findItem, "We should have found the saved search, but get no results");
- Assert.AreEqual(1, findItem.Count(), "Our list of searches should only contain one result");
-
- // TODO: Need someone to help me find out why these don't work
- // CollectionAssert.Contains(searches, savedSearch, "Can not find the new search that was saved");
- // Assert.That(searches, Contains.Item(savedSearch));
-
- // Remove the search from above & ensure it no longer exists
- _logViewer.DeleteSavedSearch("Unit Test Example");
-
- searches = _logViewer.GetSavedSearches();
- findItem = searches.Where(x => x.Name == "Unit Test Example" && x.Query == "Has(UnitTest)");
- Assert.IsEmpty(findItem, "The search item should no longer exist");
- }
-}
-
-internal class TestLogViewerQueryRepository : ILogViewerQueryRepository
-{
- public TestLogViewerQueryRepository() =>
- Store = new List(DatabaseDataCreator._defaultLogQueries.Select(LogViewerQueryModelFactory.BuildEntity));
-
- private IList Store { get; }
-
- private LogViewerQueryRepository.LogViewerQueryModelFactory LogViewerQueryModelFactory { get; } = new();
-
- public ILogViewerQuery Get(int id) => Store.FirstOrDefault(x => x.Id == id);
-
- public IEnumerable GetMany(params int[] ids) =>
- ids.Any() ? Store.Where(x => ids.Contains(x.Id)) : Store;
-
- public bool Exists(int id) => Get(id) is not null;
-
- public void Save(ILogViewerQuery entity)
- {
- var item = Get(entity.Id);
-
- if (item is null)
- {
- Store.Add(entity);
- }
- else
- {
- item.Name = entity.Name;
- item.Query = entity.Query;
- }
- }
-
- public void Delete(ILogViewerQuery entity)
- {
- var item = Get(entity.Id);
-
- if (item is not null)
- {
- Store.Remove(item);
- }
- }
-
- public IEnumerable Get(IQuery query) => throw new NotImplementedException();
-
- public int Count(IQuery query) => throw new NotImplementedException();
-
- public ILogViewerQuery GetByName(string name) => Store.FirstOrDefault(x => x.Name == name);
-}