New Backoffice: Log viewer controller (#13648)

* Fixing a few nullable reference types for log viewer (#13634)

(cherry picked from commit b4ca2a6636)

* Adding LogControllerBase

* Migrating GetLogLevels()

* Migrating GetNumberOfErrors()

* Migrating GetLogLevelCounts()

* Migrating GetCanViewLogs()

* Migrating GetMessageTemplates()

* Migrating GetLogs()

* Migrating GetSavedSearches()

* Migrating PostSavedSearch()

* Migrating DeleteSavedSearch()

* Adding LoggerViewModel

* Adding LogViewModelMapDefinition

* Update OpenApi.json

* Cleanup

* V12: Change nullability for the log searches (#13647)

* Changing nullability

* Obsolete DeleteSavedSearch since the query param is not used

* Fix a bit more referenced

* Add default implementation for the new overload of DeleteSavedSearch

(cherry picked from commit 5e06f5a8a0)

* Updates based on nullability fix

* Adding GetSavedSearchByName

* Implementing ByName endpoint

* Refactoring Delete endpoint based on GetSavedSearchByName

* Refactoring Create endpoint to return the item's location

* Suppress new GetSavedSearchByName in ILogViewer interfaces

* Update OpenApi.json

* Adding github initials to FIXME

* Renaming

* Moving files to Core proj

* Adding GetLogs with skip and take

* Introducing ILogViewerService

* Supressing xml for ILogViewer.GetLogsAsPagedModel()

* Changing to our own Enum representation of LogLevel

* Creating ILogEntry needed for GetPagedLogs()

* Refactoring controllers to use the new logViewerService

* Removing base class methods since those have been moved to the new service

* Removing ErrorCountLogViewerController since the result can be calculated from another endpoint

* Refactoring the MapDefinition because of the new return types from the service

* Update OpenApi.json

* Obsoleting old methods in favor of the ILogViewerService

* Cleanup

* Fixing enum representation as strings for Swagger

* Adding documentation

* Changing enum representation to string in OpenApi.OpenApi.json

* Fix FIXME (use CreatedAtAction)

* Removing JsonStringEnumConverter as there should be another way to fix enum representation for Swagger

* Removing MappingBuilderExtensions and making specific LogViewerBuilderExtensions

* Changes to the .sln file

* Take only the result in the response

* Register the LogViewer extensions

* Update OpenApi.json

* Fix the supressions.xml

* Add inheritdoc

* Remove GetSavedSearchByName as it isn't necessary to introduce it anymore

* Obsolete interfaces

* Rename ViewPermission controller to ValidateLogFileSize

* Make rest of the methods async

* Route name change

* Remove methods obsoletion

* Introduce the "attempt" pattern

* Refactoring of ILogViewerService

* Refactoring controllers

* Another OpenApi.json update

* Adding fixme

* Re-add new client project

Co-authored-by: nikolajlauridsen <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Elitsa Marinovska
2023-01-25 11:53:42 +01:00
committed by GitHub
parent 0fd90c1294
commit aa90efa5b7
42 changed files with 1901 additions and 69 deletions

View File

@@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.New.Cms.Core.Models;
using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer;
public class AllLogViewerController : LogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
private readonly IUmbracoMapper _umbracoMapper;
public AllLogViewerController(ILogViewerService logViewerService, IUmbracoMapper umbracoMapper)
{
_logViewerService = logViewerService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a paginated list of all logs for a specific date range.
/// </summary>
/// <param name="skip">The amount of items to skip.</param>
/// <param name="take">The amount of items to take.</param>
/// <param name="orderDirection">
/// By default this will be ordered descending (newest items first).
/// </param>
/// <param name="filterExpression">The query expression to filter on (can be null).</param>
/// <param name="logLevels">The log levels for which to retrieve the log messages (can be null).</param>
/// <param name="startDate">The start date for the date range (can be null).</param>
/// <param name="endDate">The end date for the date range (can be null).</param>
/// <returns>The paged result of the logs from the given time period.</returns>
[HttpGet("log")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<LogMessageViewModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> AllLogs(
int skip = 0,
int take = 100,
Direction orderDirection = Direction.Descending,
string? filterExpression = null,
[FromQuery(Name = "logLevel")] LogLevel[]? logLevels = null,
DateTime? startDate = null,
DateTime? endDate = null)
{
var levels = logLevels?.Select(l => l.ToString()).ToArray();
Attempt<PagedModel<ILogEntry>?, LogViewerOperationStatus> logsAttempt =
await _logViewerService.GetPagedLogsAsync(startDate, endDate, skip, take, orderDirection, filterExpression, levels);
if (logsAttempt.Success)
{
return Ok(_umbracoMapper.Map<PagedViewModel<LogMessageViewModel>>(logsAttempt.Result));
}
return LogViewerOperationStatusResult(logsAttempt.Status);
}
}

View File

@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer;
public class AllSinkLevelLogViewerController : LogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
private readonly IUmbracoMapper _umbracoMapper;
public AllSinkLevelLogViewerController(ILogViewerService logViewerService, IUmbracoMapper umbracoMapper)
{
_logViewerService = logViewerService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a paginated list of all loggers' levels.
/// </summary>
/// <param name="skip">The amount of items to skip.</param>
/// <param name="take">The amount of items to take.</param>
/// <returns>The paged result of the configured loggers and their level.</returns>
[HttpGet("level")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<LoggerViewModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<LoggerViewModel>>> AllLogLevels(int skip = 0, int take = 100)
{
IEnumerable<KeyValuePair<string, LogLevel>> logLevels = _logViewerService
.GetLogLevelsFromSinks()
.Skip(skip)
.Take(take);
return await Task.FromResult(Ok(_umbracoMapper.Map<PagedViewModel<LoggerViewModel>>(logLevels)));
}
}

View File

@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer;
public class LogLevelCountLogViewerController : LogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
private readonly IUmbracoMapper _umbracoMapper;
public LogLevelCountLogViewerController(ILogViewerService logViewerService, IUmbracoMapper umbracoMapper)
{
_logViewerService = logViewerService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets the count for each log level from the logs for a specific date range.
/// </summary>
/// <param name="startDate">The start date for the date range (can be null).</param>
/// <param name="endDate">The end date for the date range (can be null).</param>
/// <returns>The log level counts from the (filtered) logs.</returns>
[HttpGet("level-count")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> LogLevelCounts(DateTime? startDate = null, DateTime? endDate = null)
{
Attempt<LogLevelCounts?, LogViewerOperationStatus> logLevelCountsAttempt =
await _logViewerService.GetLogLevelCountsAsync(startDate, endDate);
if (logLevelCountsAttempt.Success)
{
return Ok(_umbracoMapper.Map<LogLevelCountsViewModel>(logLevelCountsAttempt.Result));
}
return LogViewerOperationStatusResult(logLevelCountsAttempt.Status);
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer;
[ApiController]
[VersionedApiBackOfficeRoute("log-viewer")]
[ApiExplorerSettings(GroupName = "Log Viewer")]
[ApiVersion("1.0")]
public abstract class LogViewerControllerBase : ManagementApiControllerBase
{
protected IActionResult LogViewerOperationStatusResult(LogViewerOperationStatus status) =>
status switch
{
LogViewerOperationStatus.NotFoundLogSearch => NotFound("The log search could not be found"),
LogViewerOperationStatus.DuplicateLogSearch => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Duplicate log search name")
.WithDetail("Another log search already exists with the attempted name.")
.Build()),
LogViewerOperationStatus.CancelledByLogsSizeValidation => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Cancelled due to log file size")
.WithDetail("The log file size for the requested date range prevented the operation.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown log viewer operation status")
};
}

View File

@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer;
public class MessageTemplateLogViewerController : LogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
private readonly IUmbracoMapper _umbracoMapper;
public MessageTemplateLogViewerController(ILogViewerService logViewerService, IUmbracoMapper umbracoMapper)
{
_logViewerService = logViewerService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a paginated list of all log message templates for a specific date range.
/// </summary>
/// <param name="skip">The amount of items to skip.</param>
/// <param name="take">The amount of items to take.</param>
/// <param name="startDate">The start date for the date range (can be null).</param>
/// <param name="endDate">The end date for the date range (can be null).</param>
/// <returns>The paged result of the log message templates from the given time period.</returns>
[HttpGet("message-template")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(PagedViewModel<LogTemplateViewModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> AllMessageTemplates(
int skip = 0,
int take = 100,
DateTime? startDate = null,
DateTime? endDate = null)
{
Attempt<IEnumerable<LogTemplate>, LogViewerOperationStatus> messageTemplatesAttempt = await _logViewerService.GetMessageTemplatesAsync(startDate, endDate);
if (messageTemplatesAttempt.Success)
{
IEnumerable<LogTemplate> messageTemplates = messageTemplatesAttempt
.Result
.Skip(skip)
.Take(take);
return Ok(_umbracoMapper.Map<PagedViewModel<LogTemplateViewModel>>(messageTemplates));
}
return LogViewerOperationStatusResult(messageTemplatesAttempt.Status);
}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer.SavedSearch;
public class AllSavedSearchLogViewerController : SavedSearchLogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
private readonly IUmbracoMapper _umbracoMapper;
public AllSavedSearchLogViewerController(ILogViewerService logViewerService, IUmbracoMapper umbracoMapper)
{
_logViewerService = logViewerService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a paginated list of all saved log searches.
/// </summary>
/// <param name="skip">The amount of items to skip.</param>
/// <param name="take">The amount of items to take.</param>
/// <returns>The paged result of the saved log searches.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<SavedLogSearchViewModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<SavedLogSearchViewModel>>> AllSavedSearches(int skip = 0, int take = 100)
{
IReadOnlyList<ILogViewerQuery> savedLogQueries = await _logViewerService.GetSavedLogQueriesAsync();
return Ok(_umbracoMapper.Map<PagedViewModel<SavedLogSearchViewModel>>(savedLogQueries.Skip(skip).Take(take)));
}
}

View File

@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer.SavedSearch;
public class ByNameSavedSearchLogViewerController : SavedSearchLogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
private readonly IUmbracoMapper _umbracoMapper;
public ByNameSavedSearchLogViewerController(ILogViewerService logViewerService, IUmbracoMapper umbracoMapper)
{
_logViewerService = logViewerService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a saved log search by name.
/// </summary>
/// <param name="name">The name of the saved log search.</param>
/// <returns>The saved log search or not found result.</returns>
[HttpGet("{name}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(NotFoundResult), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(SavedLogSearchViewModel), StatusCodes.Status200OK)]
public async Task<ActionResult<SavedLogSearchViewModel>> ByName(string name)
{
ILogViewerQuery? savedLogQuery = await _logViewerService.GetSavedLogQueryByNameAsync(name);
if (savedLogQuery is null)
{
return NotFound();
}
return Ok(_umbracoMapper.Map<SavedLogSearchViewModel>(savedLogQuery));
}
}

View File

@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer.SavedSearch;
public class CreateSavedSearchLogViewerController : SavedSearchLogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
public CreateSavedSearchLogViewerController(ILogViewerService logViewerService) => _logViewerService = logViewerService;
/// <summary>
/// Creates a saved log search.
/// </summary>
/// <param name="savedSearch">The log search to be saved.</param>
/// <returns>The location of the saved log search after the creation.</returns>
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Create(SavedLogSearchViewModel savedSearch)
{
Attempt<ILogViewerQuery?, LogViewerOperationStatus> result =
await _logViewerService.AddSavedLogQueryAsync(savedSearch.Name, savedSearch.Query);
if (result.Success)
{
return CreatedAtAction<ByNameSavedSearchLogViewerController>(
controller => nameof(controller.ByName), savedSearch.Name);
}
return LogViewerOperationStatusResult(result.Status);
}
}

View File

@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer.SavedSearch;
public class DeleteSavedSearchLogViewerController : SavedSearchLogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
public DeleteSavedSearchLogViewerController(ILogViewerService logViewerService) => _logViewerService = logViewerService;
/// <summary>
/// Deletes a saved log search with a given name.
/// </summary>
/// <param name="name">The name of the saved log search.</param>
/// <returns>The result of the deletion.</returns>
[HttpDelete("{name}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(NotFoundResult), StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Delete(string name)
{
Attempt<ILogViewerQuery?, LogViewerOperationStatus> result = await _logViewerService.DeleteSavedLogQueryAsync(name);
if (result.Success)
{
return Ok();
}
return LogViewerOperationStatusResult(result.Status);
}
}

View File

@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer.SavedSearch;
[ApiController]
[VersionedApiBackOfficeRoute("log-viewer/saved-search")]
[ApiExplorerSettings(GroupName = "Log Viewer")]
[ApiVersion("1.0")]
public class SavedSearchLogViewerControllerBase : LogViewerControllerBase
{
}

View File

@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.LogViewer;
public class ValidateLogFileSizeLogViewerController : LogViewerControllerBase
{
private readonly ILogViewerService _logViewerService;
public ValidateLogFileSizeLogViewerController(ILogViewerService logViewerService) => _logViewerService = logViewerService;
/// <summary>
/// Gets a value indicating whether or not you are able to view logs for a specified date range.
/// </summary>
/// <param name="startDate">The start date for the date range (can be null).</param>
/// <param name="endDate">The end date for the date range (can be null).</param>
/// <returns>The boolean result.</returns>
[HttpGet("validate-logs-size")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> CanViewLogs(DateTime? startDate = null, DateTime? endDate = null)
{
Attempt<bool, LogViewerOperationStatus> result = await _logViewerService.CanViewLogsAsync(startDate, endDate);
if (result.Success)
{
return Ok();
}
return LogViewerOperationStatusResult(result.Status);
}
}

View File

@@ -1,4 +1,4 @@
using System.Linq.Expressions;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Filters;
using Umbraco.Cms.Core.Security;
@@ -22,6 +22,19 @@ public class ManagementApiControllerBase : Controller
return base.CreatedAtAction(actionName, controllerName, new { key = key }, null);
}
protected CreatedAtActionResult CreatedAtAction<T>(Expression<Func<T, string>> action, string name)
{
if (action.Body is not ConstantExpression constantExpression)
{
throw new ArgumentException("Expression must be a constant expression.");
}
var controllerName = ManagementApiRegexes.ControllerTypeToNameRegex().Replace(typeof(T).Name, string.Empty);
var actionName = constantExpression.Value?.ToString() ?? throw new ArgumentException("Expression does not have a value.");
return base.CreatedAtAction(actionName, controllerName, new { name = name }, null);
}
protected static int CurrentUserId(IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
=> backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? Core.Constants.Security.SuperUserId;
}

View File

@@ -0,0 +1,15 @@
using Umbraco.Cms.Api.Management.Mapping.LogViewer;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
namespace Umbraco.Cms.Api.Management.DependencyInjection;
internal static class LogViewerBuilderExtensions
{
internal static IUmbracoBuilder AddLogViewer(this IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<LogViewerViewModelMapDefinition>();
return builder;
}
}

View File

@@ -36,6 +36,7 @@ public class ManagementApiComposer : IComposer
.AddTrackedReferences()
.AddDataTypes()
.AddTemplates()
.AddLogViewer()
.AddBackOfficeAuthentication()
.AddApiVersioning()
.AddSwaggerGen();

View File

@@ -0,0 +1,111 @@
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.LogViewer;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.New.Cms.Core.Models;
namespace Umbraco.Cms.Api.Management.Mapping.LogViewer;
public class LogViewerViewModelMapDefinition : IMapDefinition
{
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<LogLevelCounts, LogLevelCountsViewModel>((source, context) => new LogLevelCountsViewModel(), Map);
mapper.Define<KeyValuePair<string, string?>, LogMessagePropertyViewModel>(
(source, context) => new LogMessagePropertyViewModel() { Name = string.Empty }, Map);
mapper.Define<KeyValuePair<string, LogLevel>, LoggerViewModel>((source, context) => new LoggerViewModel() { Name = string.Empty }, Map);
mapper.Define<ILogViewerQuery, SavedLogSearchViewModel>(
(source, context) => new SavedLogSearchViewModel()
{
Name = string.Empty,
Query = string.Empty
},
Map);
mapper.Define<LogTemplate, LogTemplateViewModel>((source, context) => new LogTemplateViewModel(), Map);
mapper.Define<ILogEntry, LogMessageViewModel>((source, context) => new LogMessageViewModel(), Map);
mapper.Define<IEnumerable<KeyValuePair<string, LogLevel>>, PagedViewModel<LoggerViewModel>>((source, context) => new PagedViewModel<LoggerViewModel>(), Map);
mapper.Define<IEnumerable<ILogViewerQuery>, PagedViewModel<SavedLogSearchViewModel>>((source, context) => new PagedViewModel<SavedLogSearchViewModel>(), Map);
mapper.Define<IEnumerable<LogTemplate>, PagedViewModel<LogTemplateViewModel>>((source, context) => new PagedViewModel<LogTemplateViewModel>(), Map);
mapper.Define<PagedModel<ILogEntry>, PagedViewModel<LogMessageViewModel>>((source, context) => new PagedViewModel<LogMessageViewModel>(), Map);
}
// Umbraco.Code.MapAll
private static void Map(LogLevelCounts source, LogLevelCountsViewModel target, MapperContext context)
{
target.Information = source.Information;
target.Debug = source.Debug;
target.Warning = source.Warning;
target.Error = source.Error;
target.Fatal = source.Fatal;
}
// Umbraco.Code.MapAll
private static void Map(KeyValuePair<string, string?> source, LogMessagePropertyViewModel target, MapperContext context)
{
target.Name = source.Key;
target.Value = source.Value;
}
// Umbraco.Code.MapAll
private static void Map(KeyValuePair<string, LogLevel> source, LoggerViewModel target, MapperContext context)
{
target.Name = source.Key;
target.Level = source.Value;
}
// Umbraco.Code.MapAll
private static void Map(ILogViewerQuery source, SavedLogSearchViewModel target, MapperContext context)
{
target.Name = source.Name;
target.Query = source.Query;
}
// Umbraco.Code.MapAll
private static void Map(LogTemplate source, LogTemplateViewModel target, MapperContext context)
{
target.MessageTemplate = source.MessageTemplate;
target.Count = source.Count;
}
// Umbraco.Code.MapAll
private static void Map(ILogEntry source, LogMessageViewModel target, MapperContext context)
{
target.Timestamp = source.Timestamp;
target.Level = source.Level;
target.MessageTemplate = source.MessageTemplateText;
target.RenderedMessage = source.RenderedMessage;
target.Properties = context.MapEnumerable<KeyValuePair<string, string?>, LogMessagePropertyViewModel>(source.Properties);
target.Exception = source.Exception;
}
// Umbraco.Code.MapAll
private static void Map(IEnumerable<KeyValuePair<string, LogLevel>> source, PagedViewModel<LoggerViewModel> target, MapperContext context)
{
target.Items = context.MapEnumerable<KeyValuePair<string, LogLevel>, LoggerViewModel>(source);
target.Total = source.Count();
}
// Umbraco.Code.MapAll
private static void Map(IEnumerable<ILogViewerQuery> source, PagedViewModel<SavedLogSearchViewModel> target, MapperContext context)
{
target.Items = context.MapEnumerable<ILogViewerQuery, SavedLogSearchViewModel>(source);
target.Total = source.Count();
}
// Umbraco.Code.MapAll
private static void Map(IEnumerable<LogTemplate> source, PagedViewModel<LogTemplateViewModel> target, MapperContext context)
{
target.Items = context.MapEnumerable<LogTemplate, LogTemplateViewModel>(source);
target.Total = source.Count();
}
// Umbraco.Code.MapAll
private static void Map(PagedModel<ILogEntry> source, PagedViewModel<LogMessageViewModel> target, MapperContext context)
{
target.Items = context.MapEnumerable<ILogEntry, LogMessageViewModel>(source.Items);
target.Total = source.Total;
}
}

View File

@@ -638,6 +638,48 @@
}
}
},
"delete": {
"tags": [
"Dictionary"
],
"operationId": "DeleteDictionaryByKey",
"parameters": [
{
"name": "key",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
}
}
},
"put": {
"tags": [
"Dictionary"
@@ -688,38 +730,6 @@
}
}
}
},
"delete": {
"tags": [
"Dictionary"
],
"operationId": "DeleteDictionaryByKey",
"parameters": [
{
"name": "key",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Success"
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
}
}
}
},
"/umbraco/management/api/v1/dictionary/export/{key}": {
@@ -826,7 +836,7 @@
],
"operationId": "PostDictionaryUpload",
"requestBody": {
"content": { }
"content": {}
},
"responses": {
"200": {
@@ -2092,6 +2102,415 @@
}
}
},
"/umbraco/management/api/v1/log-viewer/level": {
"get": {
"tags": [
"Log Viewer"
],
"operationId": "GetLogViewerLevel",
"parameters": [
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 100
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedLogger"
}
}
}
}
}
}
},
"/umbraco/management/api/v1/log-viewer/level-count": {
"get": {
"tags": [
"Log Viewer"
],
"operationId": "GetLogViewerLevelCount",
"parameters": [
{
"name": "startDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
},
{
"name": "endDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
}
],
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": "Success"
}
}
}
},
"/umbraco/management/api/v1/log-viewer/log": {
"get": {
"tags": [
"Log Viewer"
],
"operationId": "GetLogViewerLog",
"parameters": [
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 100
}
},
{
"name": "orderDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/Direction"
}
},
{
"name": "filterExpression",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "logLevel",
"in": "query",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LogLevel"
}
}
},
{
"name": "startDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
},
{
"name": "endDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedLogMessage"
}
}
}
}
}
}
},
"/umbraco/management/api/v1/log-viewer/message-template": {
"get": {
"tags": [
"Log Viewer"
],
"operationId": "GetLogViewerMessageTemplate",
"parameters": [
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 100
}
},
{
"name": "startDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
},
{
"name": "endDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
}
],
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedLogTemplate"
}
}
}
}
}
}
},
"/umbraco/management/api/v1/log-viewer/saved-search": {
"get": {
"tags": [
"Log Viewer"
],
"operationId": "GetLogViewerSavedSearch",
"parameters": [
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 100
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedSavedLogSearch"
}
}
}
}
}
},
"post": {
"tags": [
"Log Viewer"
],
"operationId": "PostLogViewerSavedSearch",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SavedLogSearch"
}
}
}
},
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"201": {
"description": "Created"
}
}
}
},
"/umbraco/management/api/v1/log-viewer/saved-search/{name}": {
"get": {
"tags": [
"Log Viewer"
],
"operationId": "GetLogViewerSavedSearchByName",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResult"
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SavedLogSearch"
}
}
}
}
}
},
"delete": {
"tags": [
"Log Viewer"
],
"operationId": "DeleteLogViewerSavedSearchByName",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResult"
}
}
}
},
"200": {
"description": "Success"
}
}
}
},
"/umbraco/management/api/v1/log-viewer/validate-logs-size": {
"get": {
"tags": [
"Log Viewer"
],
"operationId": "GetLogViewerValidateLogsSize",
"parameters": [
{
"name": "startDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
},
{
"name": "endDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
}
],
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": "Success"
}
}
}
},
"/umbraco/management/api/v1/tree/media-type/children": {
"get": {
"tags": [
@@ -5216,6 +5635,14 @@
},
"additionalProperties": false
},
"Direction": {
"enum": [
"Ascending",
"Descending"
],
"type": "integer",
"format": "int32"
},
"DocumentBlueprintTreeItem": {
"type": "object",
"properties": {
@@ -5902,7 +6329,7 @@
},
"providerProperties": {
"type": "object",
"additionalProperties": { },
"additionalProperties": {},
"nullable": true
}
},
@@ -5987,6 +6414,88 @@
"type": "integer",
"format": "int32"
},
"LogLevel": {
"enum": [
"Verbose",
"Debug",
"Information",
"Warning",
"Error",
"Fatal"
],
"type": "integer",
"format": "int32"
},
"LogMessage": {
"type": "object",
"properties": {
"timestamp": {
"type": "string",
"format": "date-time"
},
"level": {
"$ref": "#/components/schemas/LogLevel"
},
"messageTemplate": {
"type": "string",
"nullable": true
},
"renderedMessage": {
"type": "string",
"nullable": true
},
"properties": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LogMessageProperty"
}
},
"exception": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"LogMessageProperty": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"LogTemplate": {
"type": "object",
"properties": {
"messageTemplate": {
"type": "string",
"nullable": true
},
"count": {
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
},
"Logger": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"level": {
"$ref": "#/components/schemas/LogLevel"
}
},
"additionalProperties": false
},
"MemberInfo": {
"type": "object",
"properties": {
@@ -6771,6 +7280,66 @@
},
"additionalProperties": false
},
"PagedLogMessage": {
"required": [
"items",
"total"
],
"type": "object",
"properties": {
"total": {
"type": "integer",
"format": "int64"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LogMessage"
}
}
},
"additionalProperties": false
},
"PagedLogTemplate": {
"required": [
"items",
"total"
],
"type": "object",
"properties": {
"total": {
"type": "integer",
"format": "int64"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LogTemplate"
}
}
},
"additionalProperties": false
},
"PagedLogger": {
"required": [
"items",
"total"
],
"type": "object",
"properties": {
"total": {
"type": "integer",
"format": "int64"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Logger"
}
}
},
"additionalProperties": false
},
"PagedRecycleBinItem": {
"required": [
"items",
@@ -6851,6 +7420,26 @@
},
"additionalProperties": false
},
"PagedSavedLogSearch": {
"required": [
"items",
"total"
],
"type": "object",
"properties": {
"total": {
"type": "integer",
"format": "int64"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SavedLogSearch"
}
}
},
"additionalProperties": false
},
"PagedSearchResult": {
"required": [
"items",
@@ -7022,7 +7611,7 @@
"nullable": true
}
},
"additionalProperties": { }
"additionalProperties": {}
},
"ProfilingStatus": {
"type": "object",
@@ -7296,6 +7885,18 @@
},
"additionalProperties": false
},
"SavedLogSearch": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"query": {
"type": "string"
}
},
"additionalProperties": false
},
"SearchResult": {
"type": "object",
"properties": {
@@ -8368,7 +8969,7 @@
"authorizationCode": {
"authorizationUrl": "/umbraco/management/api/v1.0/security/back-office/authorize",
"tokenUrl": "/umbraco/management/api/v1.0/security/back-office/token",
"scopes": { }
"scopes": {}
}
}
}
@@ -8376,7 +8977,7 @@
},
"security": [
{
"OAuth": [ ]
"OAuth": []
}
]
}

View File

@@ -0,0 +1,29 @@
namespace Umbraco.Cms.Api.Management.ViewModels.LogViewer;
public class LogLevelCountsViewModel
{
/// <summary>
/// Gets or sets the Information level count.
/// </summary>
public int Information { get; set; }
/// <summary>
/// Gets or sets the Debug level count.
/// </summary>
public int Debug { get; set; }
/// <summary>
/// Gets or sets the Warning level count.
/// </summary>
public int Warning { get; set; }
/// <summary>
/// Gets or sets the Error level count.
/// </summary>
public int Error { get; set; }
/// <summary>
/// Gets or sets the Fatal level count.
/// </summary>
public int Fatal { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace Umbraco.Cms.Api.Management.ViewModels.LogViewer;
public class LogMessagePropertyViewModel
{
/// <summary>
/// Gets or sets the name of the log message property.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Gets or sets the value of the log message property (can be null).
/// </summary>
public string? Value { get; set; }
}

View File

@@ -0,0 +1,36 @@
using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
namespace Umbraco.Cms.Api.Management.ViewModels.LogViewer;
public class LogMessageViewModel
{
/// <summary>
/// Gets or sets the time at which the log event occurred.
/// </summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>
/// Gets or sets the level of the event.
/// </summary>
public LogLevel Level { get; set; }
/// <summary>
/// Gets or sets the message template describing the log event (can be null).
/// </summary>
public string? MessageTemplate { get; set; }
/// <summary>
/// Gets or sets the message template filled with the log event properties (can be null).
/// </summary>
public string? RenderedMessage { get; set; }
/// <summary>
/// Gets or sets the properties associated with the log event, including those presented in MessageTemplate.
/// </summary>
public IEnumerable<LogMessagePropertyViewModel> Properties { get; set; } = null!;
/// <summary>
/// Gets or sets an exception associated with the log event (can be null).
/// </summary>
public string? Exception { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace Umbraco.Cms.Api.Management.ViewModels.LogViewer;
public class LogTemplateViewModel
{
/// <summary>
/// Gets or sets the message template.
/// </summary>
public string? MessageTemplate { get; set; }
/// <summary>
/// Gets or sets the count.
/// </summary>
public int Count { get; set; }
}

View File

@@ -0,0 +1,16 @@
using Umbraco.Cms.Core.Logging;
namespace Umbraco.Cms.Api.Management.ViewModels.LogViewer;
public class LoggerViewModel
{
/// <summary>
/// Gets or sets the name of the log event sink.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Gets or sets the log event level (can be null).
/// </summary>
public LogLevel Level { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace Umbraco.Cms.Api.Management.ViewModels.LogViewer;
public class SavedLogSearchViewModel
{
/// <summary>
/// Gets or sets the name of the saved search.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Gets or sets the query of the saved search.
/// </summary>
public required string Query { get; set; }
}

View File

@@ -0,0 +1,34 @@
namespace Umbraco.Cms.Core.Logging.Viewer;
public interface ILogEntry
{
/// <summary>
/// Gets or sets the time at which the log event occurred.
/// </summary>
DateTimeOffset Timestamp { get; set; }
/// <summary>
/// Gets or sets the level of the log event.
/// </summary>
LogLevel Level { get; set; }
/// <summary>
/// Gets or sets the message template describing the log event.
/// </summary>
string? MessageTemplateText { get; set; }
/// <summary>
/// Gets or sets the message template filled with the log event properties.
/// </summary>
string? RenderedMessage { get; set; }
/// <summary>
/// Gets or sets the properties associated with the log event.
/// </summary>
IReadOnlyDictionary<string, string?> Properties { get; set; }
/// <summary>
/// Gets or sets an exception associated with the log event, or null.
/// </summary>
string? Exception { get; set; }
}

View File

@@ -0,0 +1,34 @@
namespace Umbraco.Cms.Core.Logging.Viewer;
public class LogEntry : ILogEntry
{
/// <summary>
/// Gets or sets the time at which the log event occurred.
/// </summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>
/// Gets or sets the level of the log event.
/// </summary>
public LogLevel Level { get; set; }
/// <summary>
/// Gets or sets the message template describing the log event.
/// </summary>
public string? MessageTemplateText { get; set; }
/// <summary>
/// Gets or sets the message template filled with the log event properties.
/// </summary>
public string? RenderedMessage { get; set; }
/// <summary>
/// Gets or sets the properties associated with the log event.
/// </summary>
public IReadOnlyDictionary<string, string?> Properties { get; set; } = new Dictionary<string, string?>();
/// <summary>
/// Gets or sets an exception associated with the log event, or null.
/// </summary>
public string? Exception { get; set; }
}

View File

@@ -0,0 +1,94 @@
using System.Collections.ObjectModel;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.New.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services;
public interface ILogViewerService : IService
{
/// <summary>
/// Gets all logs as a paged model. The attempt will fail if the log files
/// for the given time period are too large (more than 1GB).
/// </summary>
/// <param name="startDate">The start date for the date range.</param>
/// <param name="endDate">The end date for the date range.</param>
/// <param name="skip">The amount of items to skip.</param>
/// <param name="take">The amount of items to take.</param>
/// <param name="orderDirection">
/// The direction in which the log entries are to be ordered.
/// </param>
/// <param name="filterExpression">The query expression to filter on.</param>
/// <param name="logLevels">The log levels for which to retrieve the log messages.</param>
Task<Attempt<PagedModel<ILogEntry>?, LogViewerOperationStatus>> GetPagedLogsAsync(
DateTime? startDate,
DateTime? endDate,
int skip,
int take,
Direction orderDirection = Direction.Descending,
string? filterExpression = null,
string[]? logLevels = null);
/// <summary>
/// Get all saved log queries from your chosen data source.
/// </summary>
Task<IReadOnlyList<ILogViewerQuery>> GetSavedLogQueriesAsync();
/// <summary>
/// Gets a saved log query by name from your chosen data source.
/// </summary>
/// <param name="name">The name of the saved log query.</param>
Task<ILogViewerQuery?> GetSavedLogQueryByNameAsync(string name);
/// <summary>
/// Adds a new saved log query to your chosen data source.
/// </summary>
/// <param name="name">The name of the new saved log query.</param>
/// <param name="query">The query of the new saved log query.</param>
Task<Attempt<ILogViewerQuery?, LogViewerOperationStatus>> AddSavedLogQueryAsync(string name, string query);
/// <summary>
/// Deletes a saved log query to your chosen data source.
/// </summary>
/// <param name="name">The name of the saved log search.</param>
Task<Attempt<ILogViewerQuery?, LogViewerOperationStatus>> DeleteSavedLogQueryAsync(string name);
/// <summary>
/// Returns a value indicating whether the log files for the given time
/// period are not too large to view (more than 1GB).
/// </summary>
/// <param name="startDate">The start date for the date range.</param>
/// <param name="endDate">The end date for the date range.</param>
/// <returns>The value whether or not you are able to view the logs.</returns>
Task<Attempt<bool, LogViewerOperationStatus>> CanViewLogsAsync(DateTime? startDate, DateTime? endDate);
/// <summary>
/// Returns a number of the different log level entries.
/// The attempt will fail if the log files for the given
/// time period are too large (more than 1GB).
/// </summary>
/// <param name="startDate">The start date for the date range.</param>
/// <param name="endDate">The end date for the date range.</param>
Task<Attempt<LogLevelCounts?, LogViewerOperationStatus>> GetLogLevelCountsAsync(DateTime? startDate, DateTime? endDate);
/// <summary>
/// Returns a list of all unique message templates and their counts.
/// The attempt will fail if the log files for the given
/// time period are too large (more than 1GB).
/// </summary>
/// <param name="startDate">The start date for the date range.</param>
/// <param name="endDate">The end date for the date range.</param>
Task<Attempt<IEnumerable<LogTemplate>, LogViewerOperationStatus>> GetMessageTemplatesAsync(DateTime? startDate, DateTime? endDate);
/// <summary>
/// Get the log level values of the global minimum and the UmbracoFile one from the config file.
/// </summary>
ReadOnlyDictionary<string, LogLevel> GetLogLevelsFromSinks();
/// <summary>
/// Get the minimum log level value from the config file.
/// </summary>
LogLevel GetGlobalMinLogLevel();
}

View File

@@ -0,0 +1,9 @@
namespace Umbraco.Cms.Core.Services.OperationStatus;
public enum LogViewerOperationStatus
{
Success,
NotFoundLogSearch,
DuplicateLogSearch,
CancelledByLogsSizeValidation
}

View File

@@ -1,5 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Core.Logging.Viewer.LogLevelCounts</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Core.Logging.Viewer.LogTemplate</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Core.Logging.Viewer.LogTimePeriod</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Infrastructure.Migrations.PostMigrations.ClearCsrfCookies</Target>
@@ -126,6 +147,13 @@
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Logging.Viewer.ILogViewer.GetLogsAsPagedModel(Umbraco.Cms.Core.Logging.Viewer.LogTimePeriod,System.Int32,System.Int32,Umbraco.Cms.Core.Direction,System.String,System.String[])</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Migrations.IMigrationPlanExecutor.ExecutePlan(Umbraco.Cms.Infrastructure.Migrations.MigrationPlan,System.String)</Target>

View File

@@ -32,6 +32,7 @@ using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Implement;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Core.Templates;
using Umbraco.Cms.Core.Trees;
@@ -232,6 +233,7 @@ public static partial class UmbracoBuilderExtensions
factory.GetRequiredService<ILoggingConfiguration>(),
factory.GetRequiredService<ILogLevelLoader>(),
Log.Logger));
builder.Services.AddSingleton<ILogViewerService, LogViewerService>();
return builder;
}

View File

@@ -3,6 +3,7 @@ using Serilog.Events;
namespace Umbraco.Cms.Core.Logging.Viewer;
[Obsolete("Use ILogViewerService instead. Scheduled for removal in Umbraco 15.")]
public interface ILogLevelLoader
{
/// <summary>

View File

@@ -1,4 +1,5 @@
using Umbraco.Cms.Core.Models;
using Umbraco.New.Cms.Core.Models;
namespace Umbraco.Cms.Core.Logging.Viewer;
@@ -9,16 +10,19 @@ public interface ILogViewer
/// <summary>
/// Get all saved searches from your chosen data source
/// </summary>
[Obsolete("Use ILogViewerService.GetSavedLogQueriesAsync instead. Scheduled for removal in Umbraco 15.")]
IReadOnlyList<SavedLogSearch> GetSavedSearches();
/// <summary>
/// Adds a new saved search to chosen data source and returns the updated searches
/// </summary>
[Obsolete("Use ILogViewerService.AddSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")]
IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query);
/// <summary>
/// Deletes a saved search to chosen data source and returns the remaining searches
/// </summary>
[Obsolete("Use ILogViewerService.DeleteSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")]
IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name) => DeleteSavedSearch(name, string.Empty);
[Obsolete("Use the overload that only takes a 'name' parameter instead. This will be removed in Umbraco 14.")]
@@ -33,18 +37,22 @@ public interface ILogViewer
/// <summary>
/// Returns a number of the different log level entries
/// </summary>
[Obsolete("Use ILogViewerService.GetLogLevelCounts instead. Scheduled for removal in Umbraco 15.")]
LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod);
/// <summary>
/// Returns a list of all unique message templates and their counts
/// </summary>
[Obsolete("Use ILogViewerService.GetMessageTemplates instead. Scheduled for removal in Umbraco 15.")]
IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod);
[Obsolete("Use ILogViewerService.CanViewLogsAsync instead. Scheduled for removal in Umbraco 15.")]
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
/// <summary>
/// Returns the collection of logs
/// </summary>
[Obsolete("Use ILogViewerService.GetPagedLogs instead. Scheduled for removal in Umbraco 15.")]
PagedResult<LogMessage> GetLogs(
LogTimePeriod logTimePeriod,
int pageNumber = 1,
@@ -52,4 +60,13 @@ public interface ILogViewer
Direction orderDirection = Direction.Descending,
string? filterExpression = null,
string[]? logLevels = null);
[Obsolete("Use ILogViewerService.GetPagedLogs instead. Scheduled for removal in Umbraco 15.")]
PagedModel<LogMessage> GetLogsAsPagedModel(
LogTimePeriod logTimePeriod,
int skip,
int take,
Direction orderDirection = Direction.Descending,
string? filterExpression = null,
string[]? logLevels = null);
}

View File

@@ -1,5 +1,6 @@
namespace Umbraco.Cms.Core.Logging.Viewer;
[Obsolete("Use ILogViewerService instead. Scheduled for removal in Umbraco 15.")]
public interface ILogViewerConfig
{
IReadOnlyList<SavedLogSearch> GetSavedSearches();

View File

@@ -14,6 +14,7 @@ public class LogLevelLoader : ILogLevelLoader
/// <summary>
/// Get the Serilog level values of the global minimum and the UmbracoFile one from the config file.
/// </summary>
[Obsolete("Use ILogViewerService.GetLogLevelsFromSinks instead. Scheduled for removal in Umbraco 15.")]
public ReadOnlyDictionary<string, LogEventLevel?> GetLogLevelsFromSinks()
{
var configuredLogLevels = new Dictionary<string, LogEventLevel?>
@@ -27,6 +28,7 @@ public class LogLevelLoader : ILogLevelLoader
/// <summary>
/// Get the Serilog minimum-level value from the config file.
/// </summary>
[Obsolete("Use ILogViewerService.GetGlobalMinLogLevel instead. Scheduled for removal in Umbraco 15.")]
public LogEventLevel? GetGlobalMinLogLevel()
{
LogEventLevel? logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.IsEnabled)

View File

@@ -5,6 +5,7 @@ using Serilog.Events;
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Umbraco.Cms.Core.Logging.Viewer;
[Obsolete("Use ILogEntry instead. Scheduled for removal in Umbraco 15.")]
public class LogMessage
{
/// <summary>

View File

@@ -16,6 +16,7 @@ public class LogViewerConfig : ILogViewerConfig
_scopeProvider = scopeProvider;
}
[Obsolete("Use ILogViewerService.GetSavedLogQueriesAsync instead. Scheduled for removal in Umbraco 15.")]
public IReadOnlyList<SavedLogSearch> GetSavedSearches()
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
@@ -24,6 +25,7 @@ public class LogViewerConfig : ILogViewerConfig
return result;
}
[Obsolete("Use ILogViewerService.AddSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")]
public IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
@@ -35,6 +37,7 @@ public class LogViewerConfig : ILogViewerConfig
[Obsolete("Use the overload that only takes a 'name' parameter instead. This will be removed in Umbraco 14.")]
public IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query) => DeleteSavedSearch(name);
[Obsolete("Use ILogViewerService.DeleteSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")]
public IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name)
{
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);

View File

@@ -26,6 +26,7 @@ internal class SerilogJsonLogViewer : SerilogLogViewerSourceBase
public override bool CanHandleLargeLogs => false;
[Obsolete("Use ILogViewerService.CanViewLogsAsync instead. Scheduled for removal in Umbraco 15.")]
public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod)
{
// Log Directory

View File

@@ -3,6 +3,7 @@ using Serilog;
using Serilog.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Extensions;
using Umbraco.New.Cms.Core.Models;
namespace Umbraco.Cms.Core.Logging.Viewer;
@@ -19,11 +20,14 @@ public abstract class SerilogLogViewerSourceBase : ILogViewer
public abstract bool CanHandleLargeLogs { get; }
[Obsolete("Use ILogViewerService.CanViewLogsAsync instead. Scheduled for removal in Umbraco 15.")]
public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
[Obsolete("Use ILogViewerService.GetSavedLogQueriesAsync instead. Scheduled for removal in Umbraco 15.")]
public virtual IReadOnlyList<SavedLogSearch> GetSavedSearches()
=> _logViewerConfig.GetSavedSearches();
[Obsolete("Use ILogViewerService.AddSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")]
public virtual IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query)
=> _logViewerConfig.AddSavedSearch(name, query);
@@ -31,6 +35,7 @@ public abstract class SerilogLogViewerSourceBase : ILogViewer
public virtual IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query)
=> DeleteSavedSearch(name);
[Obsolete("Use ILogViewerService.DeleteSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")]
public virtual IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name)
=> _logViewerConfig.DeleteSavedSearch(name);
@@ -41,6 +46,7 @@ public abstract class SerilogLogViewerSourceBase : ILogViewer
return errorCounter.Count;
}
[Obsolete("Use ILogViewerService.GetLogLevelCounts instead. Scheduled for removal in Umbraco 15.")]
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
{
var counter = new CountingFilter();
@@ -48,6 +54,7 @@ public abstract class SerilogLogViewerSourceBase : ILogViewer
return counter.Counts;
}
[Obsolete("Use ILogViewerService.GetMessageTemplates instead. Scheduled for removal in Umbraco 15.")]
public IEnumerable<LogTemplate> GetMessageTemplates(LogTimePeriod logTimePeriod)
{
var messageTemplates = new MessageTemplateFilter();
@@ -60,6 +67,7 @@ public abstract class SerilogLogViewerSourceBase : ILogViewer
return templates;
}
[Obsolete("Use ILogViewerService.GetPagedLogs instead. Scheduled for removal in Umbraco 15.")]
public PagedResult<LogMessage> GetLogs(
LogTimePeriod logTimePeriod,
int pageNumber = 1,
@@ -67,6 +75,72 @@ public abstract class SerilogLogViewerSourceBase : ILogViewer
Direction orderDirection = Direction.Descending,
string? filterExpression = null,
string[]? logLevels = null)
{
IReadOnlyList<LogEvent> filteredLogs = GetFilteredLogs(logTimePeriod, filterExpression, logLevels);
long totalRecords = filteredLogs.Count;
// Order By, Skip, Take & Select
IEnumerable<LogMessage> logMessages = filteredLogs
.OrderBy(l => l.Timestamp, orderDirection)
.Skip(pageSize * (pageNumber - 1))
.Take(pageSize)
.Select(x => new LogMessage
{
Timestamp = x.Timestamp,
Level = x.Level,
MessageTemplateText = x.MessageTemplate.Text,
Exception = x.Exception?.ToString(),
Properties = x.Properties,
RenderedMessage = x.RenderMessage(),
});
return new PagedResult<LogMessage>(totalRecords, pageNumber, pageSize) { Items = logMessages };
}
[Obsolete("Use ILogViewerService.GetPagedLogs instead. Scheduled for removal in Umbraco 15.")]
public PagedModel<LogMessage> GetLogsAsPagedModel(
LogTimePeriod logTimePeriod,
int skip,
int take,
Direction orderDirection = Direction.Descending,
string? filterExpression = null,
string[]? logLevels = null)
{
IReadOnlyList<LogEvent> filteredLogs = GetFilteredLogs(logTimePeriod, filterExpression, logLevels);
// Order By, Skip, Take & Select
IEnumerable<LogMessage> logMessages = filteredLogs
.OrderBy(l => l.Timestamp, orderDirection)
.Skip(skip)
.Take(take)
.Select(x => new LogMessage
{
Timestamp = x.Timestamp,
Level = x.Level,
MessageTemplateText = x.MessageTemplate.Text,
Exception = x.Exception?.ToString(),
Properties = x.Properties,
RenderedMessage = x.RenderMessage(),
});
return new PagedModel<LogMessage>(logMessages.Count(), logMessages);
}
/// <summary>
/// Get the Serilog minimum-level and UmbracoFile-level values from the config file.
/// </summary>
[Obsolete("Use ILogViewerService.GetLogLevelsFromSinks instead. Scheduled for removal in Umbraco 15.")]
public ReadOnlyDictionary<string, LogEventLevel?> GetLogLevels() => _logLevelLoader.GetLogLevelsFromSinks();
/// <summary>
/// Get all logs from your chosen data source back as Serilog LogEvents
/// </summary>
protected abstract IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take);
private IReadOnlyList<LogEvent> GetFilteredLogs(
LogTimePeriod logTimePeriod,
string? filterExpression,
string[]? logLevels)
{
var expression = new ExpressionFilter(filterExpression);
IReadOnlyList<LogEvent> filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue);
@@ -98,33 +172,6 @@ public abstract class SerilogLogViewerSourceBase : ILogViewer
}
}
long totalRecords = filteredLogs.Count;
// Order By, Skip, Take & Select
IEnumerable<LogMessage> logMessages = filteredLogs
.OrderBy(l => l.Timestamp, orderDirection)
.Skip(pageSize * (pageNumber - 1))
.Take(pageSize)
.Select(x => new LogMessage
{
Timestamp = x.Timestamp,
Level = x.Level,
MessageTemplateText = x.MessageTemplate.Text,
Exception = x.Exception?.ToString(),
Properties = x.Properties,
RenderedMessage = x.RenderMessage(),
});
return new PagedResult<LogMessage>(totalRecords, pageNumber, pageSize) { Items = logMessages };
return filteredLogs;
}
/// <summary>
/// Get the Serilog minimum-level and UmbracoFile-level values from the config file.
/// </summary>
public ReadOnlyDictionary<string, LogEventLevel?> GetLogLevels() => _logLevelLoader.GetLogLevelsFromSinks();
/// <summary>
/// Get all logs from your chosen data source back as Serilog LogEvents
/// </summary>
protected abstract IReadOnlyList<LogEvent> GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take);
}

View File

@@ -0,0 +1,260 @@
using Serilog.Events;
using System.Collections.ObjectModel;
using System.Text.Json;
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 Umbraco.New.Cms.Core.Models;
using LogLevel = Umbraco.Cms.Core.Logging.LogLevel;
namespace Umbraco.Cms.Core.Services.Implement;
// FIXME: Get rid of ILogViewer and ILogLevelLoader dependencies (as they are obsolete)
// and fix the implementation of the methods using it
public class LogViewerService : ILogViewerService
{
private readonly ILogViewerQueryRepository _logViewerQueryRepository;
private readonly ILogViewer _logViewer;
private readonly ILogLevelLoader _logLevelLoader;
private readonly ICoreScopeProvider _provider;
public LogViewerService(
ILogViewerQueryRepository logViewerQueryRepository,
ILogViewer logViewer,
ILogLevelLoader logLevelLoader,
ICoreScopeProvider provider)
{
_logViewerQueryRepository = logViewerQueryRepository;
_logViewer = logViewer;
_logLevelLoader = logLevelLoader;
_provider = provider;
}
/// <inheritdoc/>
public async Task<Attempt<PagedModel<ILogEntry>?, LogViewerOperationStatus>> GetPagedLogsAsync(
DateTime? startDate,
DateTime? 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 Attempt.FailWithStatus<PagedModel<ILogEntry>?, LogViewerOperationStatus>(
LogViewerOperationStatus.CancelledByLogsSizeValidation,
null);
}
PagedModel<LogMessage> logMessages =
_logViewer.GetLogsAsPagedModel(logTimePeriod, skip, take, orderDirection, filterExpression, logLevels);
var logEntries = new PagedModel<ILogEntry>(logMessages.Total, logMessages.Items.Select(x => ToLogEntry(x)));
return Attempt.SucceedWithStatus<PagedModel<ILogEntry>?, LogViewerOperationStatus>(
LogViewerOperationStatus.Success,
logEntries);
}
/// <inheritdoc/>
public async Task<IReadOnlyList<ILogViewerQuery>> GetSavedLogQueriesAsync()
{
using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
return await Task.FromResult(_logViewerQueryRepository.GetMany().ToList());
}
/// <inheritdoc/>
public async Task<ILogViewerQuery?> GetSavedLogQueryByNameAsync(string name)
{
using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
return await Task.FromResult(_logViewerQueryRepository.GetByName(name));
}
/// <inheritdoc/>
public async Task<Attempt<ILogViewerQuery?, LogViewerOperationStatus>> AddSavedLogQueryAsync(string name, string query)
{
ILogViewerQuery? logViewerQuery = await GetSavedLogQueryByNameAsync(name);
if (logViewerQuery is not null)
{
return Attempt.FailWithStatus<ILogViewerQuery?, LogViewerOperationStatus>(LogViewerOperationStatus.DuplicateLogSearch, null);
}
logViewerQuery = new LogViewerQuery(name, query);
using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
_logViewerQueryRepository.Save(logViewerQuery);
return Attempt.SucceedWithStatus<ILogViewerQuery?, LogViewerOperationStatus>(LogViewerOperationStatus.Success, logViewerQuery);
}
/// <inheritdoc/>
public async Task<Attempt<ILogViewerQuery?, LogViewerOperationStatus>> DeleteSavedLogQueryAsync(string name)
{
ILogViewerQuery? logViewerQuery = await GetSavedLogQueryByNameAsync(name);
if (logViewerQuery is null)
{
return Attempt.FailWithStatus<ILogViewerQuery?, LogViewerOperationStatus>(LogViewerOperationStatus.NotFoundLogSearch, null);
}
using ICoreScope scope = _provider.CreateCoreScope(autoComplete: true);
_logViewerQueryRepository.Delete(logViewerQuery);
return Attempt.SucceedWithStatus<ILogViewerQuery?, LogViewerOperationStatus>(LogViewerOperationStatus.Success, logViewerQuery);
}
/// <inheritdoc/>
public async Task<Attempt<bool, LogViewerOperationStatus>> CanViewLogsAsync(DateTime? startDate, DateTime? endDate)
{
LogTimePeriod logTimePeriod = GetTimePeriod(startDate, endDate);
bool isAllowed = CanViewLogs(logTimePeriod);
if (isAllowed == false)
{
return Attempt.FailWithStatus(LogViewerOperationStatus.CancelledByLogsSizeValidation, isAllowed);
}
return Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, isAllowed);
}
/// <inheritdoc/>
public async Task<Attempt<LogLevelCounts?, LogViewerOperationStatus>> GetLogLevelCountsAsync(DateTime? startDate, DateTime? 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 Attempt.FailWithStatus<LogLevelCounts?, LogViewerOperationStatus>(
LogViewerOperationStatus.CancelledByLogsSizeValidation,
null);
}
return Attempt.SucceedWithStatus<LogLevelCounts?, LogViewerOperationStatus>(
LogViewerOperationStatus.Success,
_logViewer.GetLogLevelCounts(logTimePeriod));
}
/// <inheritdoc/>
public async Task<Attempt<IEnumerable<LogTemplate>, LogViewerOperationStatus>> GetMessageTemplatesAsync(DateTime? startDate, DateTime? 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 Attempt.FailWithStatus(
LogViewerOperationStatus.CancelledByLogsSizeValidation,
Enumerable.Empty<LogTemplate>());
}
return Attempt.SucceedWithStatus(
LogViewerOperationStatus.Success,
_logViewer.GetMessageTemplates(logTimePeriod));
}
/// <inheritdoc/>
public ReadOnlyDictionary<string, LogLevel> GetLogLevelsFromSinks()
{
ReadOnlyDictionary<string, LogEventLevel?> configuredLogLevels = _logLevelLoader.GetLogLevelsFromSinks();
return configuredLogLevels.ToDictionary(logLevel => logLevel.Key, logLevel => Enum.Parse<LogLevel>(logLevel.Value!.ToString()!)).AsReadOnly();
}
/// <inheritdoc/>
public LogLevel GetGlobalMinLogLevel()
{
LogEventLevel? serilogLogLevel = _logLevelLoader.GetGlobalMinLogLevel();
return Enum.Parse<LogLevel>(serilogLogLevel!.ToString()!);
}
/// <summary>
/// Returns a <see cref="LogTimePeriod" /> representation from a start and end date for filtering log files.
/// </summary>
/// <param name="startDate">The start date for the date range (can be null).</param>
/// <param name="endDate">The end date for the date range (can be null).</param>
/// <returns>The LogTimePeriod object used to filter logs.</returns>
private LogTimePeriod GetTimePeriod(DateTime? startDate, DateTime? 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;
}
}
return new LogTimePeriod(startDate.Value, endDate.Value);
}
/// <summary>
/// Returns a value indicating whether to stop a GET request that is attempting to fetch logs from a 1GB file.
/// </summary>
/// <param name="logTimePeriod">The time period to filter the logs.</param>
/// <returns>The value whether or not you are able to view the logs.</returns>
private bool CanViewLogs(LogTimePeriod logTimePeriod)
{
// Check if the interface can deal with large files
if (_logViewer.CanHandleLargeLogs)
{
return true;
}
return _logViewer.CheckCanOpenLogs(logTimePeriod);
}
private ILogEntry ToLogEntry(LogMessage logMessage)
{
return new LogEntry()
{
Timestamp = logMessage.Timestamp,
Level = Enum.Parse<LogLevel>(logMessage.Level.ToString()),
MessageTemplateText = logMessage.MessageTemplateText,
RenderedMessage = logMessage.RenderedMessage,
Properties = MapLogMessageProperties(logMessage.Properties),
Exception = logMessage.Exception
};
}
private IReadOnlyDictionary<string, string?> MapLogMessageProperties(IReadOnlyDictionary<string, LogEventPropertyValue>? properties)
{
var result = new Dictionary<string, string?>();
if (properties is not null)
{
foreach (KeyValuePair<string, LogEventPropertyValue> property in properties)
{
string? value;
if (property.Value is ScalarValue scalarValue)
{
value = scalarValue.Value?.ToString();
}
else
{
// When polymorphism is implemented, this should be changed
value = JsonSerializer.Serialize(property.Value as object);
}
result.Add(property.Key, value);
}
}
return result.AsReadOnly();
}
}

View File

@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Serilog.Events;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Web.Common.Attributes;

View File

@@ -181,8 +181,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Imaging.ImageSh
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{05878304-40EB-4F84-B40B-91BDB70DE094}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI.New", "src\Umbraco.Web.UI.New\Umbraco.Web.UI.New.csproj", "{C55CA725-9F4E-4618-9435-6B8AE05DA14D}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Api.Common", "src\Umbraco.Cms.Api.Common\Umbraco.Cms.Api.Common.csproj", "{D48B5D6B-82FF-4235-986C-CDE646F41DEC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.UI.New", "src\Umbraco.Web.UI.New\Umbraco.Web.UI.New.csproj", "{C55CA725-9F4E-4618-9435-6B8AE05DA14D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Api.Common", "src\Umbraco.Cms.Api.Common\Umbraco.Cms.Api.Common.csproj", "{D48B5D6B-82FF-4235-986C-CDE646F41DEC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution