From 3ac862a0a84fc625d2f34a2d2202d9dd2b7a524e Mon Sep 17 00:00:00 2001 From: Migaroez Date: Tue, 29 Jul 2025 18:22:07 +0200 Subject: [PATCH 01/10] Update logging with new abstraction/configuration --- .../fundamentals/backoffice/logviewer.md | 138 +++++++++++------- .../fundamentals/code/debugging/logging.md | 4 + .../reference/configuration/serilog.md | 3 + 3 files changed, 90 insertions(+), 55 deletions(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index cd228c6e9a6..32daf1bc0c6 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -30,13 +30,13 @@ Here are some example queries to help you get started. For more details on the s If you frequently use a custom query, you can save it for quick access. Type your query in the search box and click the heart icon to save it with a friendly name. Saved queries are stored in the `umbracoLogViewerQuery` table in the database. -## Implementing Your Own Log Viewer +## Implementing Your Own Log Viewer Source -Umbraco allows you to implement a customn `ILogViewer` to fetch logs from alternative sources, such as **Azure Table Storage**. +Umbraco allows you to implement a customn `ILogViewerRepository` to fetch logs from alternative sources, such as **Azure Table Storage**. -### Creating a Custom Log Viewer +### Creating a Custom Log Viewer Repository -To fetch logs from Azure Table Storage, implement the `SerilogLogViewerSourceBase` class from `Umbraco.Cms.Core.Logging.Viewer`. +To fetch logs from Azure Table Storage, extend the `LogViewerRepositoryBase` class from `Umbraco.Cms.Infrastructure.Services.Implement`. {% hint style="info" %} This implementation requires the `Azure.Data.Tables` NuGet package. @@ -47,80 +47,107 @@ using Azure; using Azure.Data.Tables; using Serilog.Events; using Serilog.Formatting.Compact.Reader; -using Serilog.Sinks.AzureTableStorage; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Logging.Viewer; -using ITableEntity = Azure.Data.Tables.ITableEntity; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Logging.Serilog; +using Umbraco.Cms.Infrastructure.Services.Implement; namespace My.Website; -public class AzureTableLogViewer : SerilogLogViewerSourceBase +public class AzureTableLogsRepository : LogViewerRepositoryBase { - public AzureTableLogViewer(ILogViewerConfig logViewerConfig, Serilog.ILogger serilogLog, ILogLevelLoader logLevelLoader) - : base(logViewerConfig, logLevelLoader, serilogLog) + private readonly IJsonSerializer _jsonSerializer; + + public AzureTableLogsRepository(UmbracoFileConfiguration umbracoFileConfig, IJsonSerializer jsonSerializer) : base( + umbracoFileConfig) { + _jsonSerializer = jsonSerializer; } - public override bool CanHandleLargeLogs => true; - - // This method will not be called - as we have indicated that this 'CanHandleLargeLogs' - public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod) => throw new NotImplementedException(); - - protected override IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take) + protected override IEnumerable GetLogs(LogTimePeriod logTimePeriod, ILogFilter logFilter) { - //Replace ACCOUNT_NAME and KEY with your actual Azure Storage Account details. The "Logs" parameter refers to the table name where logs will be stored and retrieved from. + // This example uses a connetionstring compatible with the Azurite emulator + // https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite var client = new TableClient( - "DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=KEY;EndpointSuffix=core.windows.net", - "Logs"); - - // Table storage does not support skip, only take, so the best we can do is to not fetch more entities than we need in total. - // See: https://learn.microsoft.com/en-us/rest/api/storageservices/writing-linq-queries-against-the-table-service#returning-the-top-n-entities for more info. - var requiredEntities = skip + take; - IEnumerable results = client.Query().Take(requiredEntities); - - return results - .Skip(skip) - .Take(take) - .Select(x => LogEventReader.ReadFromString(x.Data)) - // Filter by timestamp to avoid retrieving all logs from the table, preventing memory and performance issues - .Where(evt => evt.Timestamp >= logTimePeriod.StartTime.Date && - evt.Timestamp <= logTimePeriod.EndTime.Date.AddDays(1).AddSeconds(-1)) - .Where(filter.TakeLogEvent) - .ToList(); + "UseDevelopmentStorage=true", + "LogEventEntity"); + + // Filter by timestamp to avoid retrieving all logs from the table, preventing memory and performance issues + IEnumerable results = client.Query( + entity => entity.Timestamp >= logTimePeriod.StartTime.Date && + entity.Timestamp <= logTimePeriod.EndTime.Date.AddDays(1).AddSeconds(-1)); + + // Read the data and apply logfilters + IEnumerable filteredData = results.Select(x => LogEventReader.ReadFromString(x.Data)) + .Where(logFilter.TakeLogEvent); + + return filteredData.Select(x => new LogEntry + { + Timestamp = x.Timestamp, + Level = Enum.Parse(x.Level.ToString()), + MessageTemplateText = x.MessageTemplate.Text, + Exception = x.Exception?.ToString(), + Properties = MapLogMessageProperties(x.Properties), + RenderedMessage = x.RenderMessage(), + }); } - public override IReadOnlyList? GetSavedSearches() + private IReadOnlyDictionary MapLogMessageProperties( + IReadOnlyDictionary? properties) { - //This method is optional. If you store saved searches in Azure Table Storage, implement fetching logic here. - return base.GetSavedSearches(); + var result = new Dictionary(); + + if (properties is not null) + { + foreach (KeyValuePair property in properties) + { + string? value; + + if (property.Value is ScalarValue scalarValue) + { + value = scalarValue.Value?.ToString(); + } + else if (property.Value is StructureValue structureValue) + { + var textWriter = new StringWriter(); + structureValue.Render(textWriter); + value = textWriter.ToString(); + } + else + { + value = _jsonSerializer.Serialize(property.Value); + } + + result.Add(property.Key, value); + } + } + + return result; } - public override IReadOnlyList? AddSavedSearch(string? name, string? query) + public class AzureTableLogEntity : ITableEntity { - //This method is optional. If you store saved searches in Azure Table Storage, implement adding logic here. - return base.AddSavedSearch(name, query); - } + public required string Data { get; set; } - public override IReadOnlyList? DeleteSavedSearch(string? name, string? query) - { - //This method is optional. If you store saved searches in Azure Table Storage, implement deleting logic here. - return base.DeleteSavedSearch(name, query); - } -} + public required string PartitionKey { get; set; } -public class AzureTableLogEntity : LogEventEntity, ITableEntity -{ - public DateTimeOffset? Timestamp { get; set; } + public required string RowKey { get; set; } - public ETag ETag { get; set; } + public DateTimeOffset? Timestamp { get; set; } + + public ETag ETag { get; set; } + } } ``` -Azure Table Storage requires entities to implement the `ITableEntity` interface. Since Umbraco’s default log entity does not implement this, a custom entity (`AzureTableLogEntity`) must be created to ensure logs are correctly fetched and stored. +Azure Table Storage requires entities to implement the `ITableEntity` interface. Since Umbraco’s default log entity does not implement this, a custom entity (`AzureTableLogEntity`) must be created to ensure logs are correctly fetched. ### Register implementation -Umbraco needs to be made aware that there is a new implementation of an `ILogViewer` to register. We also need to replace the default JSON LogViewer that we ship in the core of Umbraco. +Umbraco needs to be made aware that there is a new implementation of an `ILogViewerRepository` to register. We also need to replace the default JSON LogViewer that is shipped in the core of Umbraco. ```csharp using Umbraco.Cms.Core.Composing; @@ -128,9 +155,10 @@ using Umbraco.Cms.Infrastructure.DependencyInjection; namespace My.Website; -public class LogViewerSavedSearches : IComposer -{ - public void Compose(IUmbracoBuilder builder) => builder.SetLogViewer(); +public class AzureTableLogsComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) => builder.Services.AddUnique(); + } } ``` diff --git a/16/umbraco-cms/fundamentals/code/debugging/logging.md b/16/umbraco-cms/fundamentals/code/debugging/logging.md index 467cbec5f41..737ed137bf6 100644 --- a/16/umbraco-cms/fundamentals/code/debugging/logging.md +++ b/16/umbraco-cms/fundamentals/code/debugging/logging.md @@ -117,6 +117,10 @@ Serilog uses levels as the primary means for assigning importance to log events. Serilog can be configured and extended by using the .NET Core configuration such as the AppSetting.json files or environment variables. For more information, see the [Serilog config](../../../reference/configuration/serilog.md) article. +## The UmbracoFile Sink + +Serilog uses the concept of Sinks to output the log messages to different places. Umbraco ships with a custom sink configuration called UmbracoFile that uses the [Serilog.Sinks.File](https://github.com/serilog/serilog-sinks-file) sink to save the logs to a rolling file on disk in the Umbraco. You can disable this sink by setting its Enabled configuration flag to false, see [Serilog config](../../../reference/configuration/serilog.md) for more information. + ## The logviewer dashboard Learn more about the [logviewer dashboard](../../backoffice/logviewer.md) in the backoffice and how it can be extended. diff --git a/16/umbraco-cms/reference/configuration/serilog.md b/16/umbraco-cms/reference/configuration/serilog.md index 6bdb8396aa7..ce8c9a0cfcb 100644 --- a/16/umbraco-cms/reference/configuration/serilog.md +++ b/16/umbraco-cms/reference/configuration/serilog.md @@ -105,6 +105,7 @@ By default, Umbraco uses a special Serilog 'sink' that is optimized for performa { "Name": "UmbracoFile", "Args": { + "Enabled": "True", "RestrictedToMinimumLevel": "Warning", "FileSizeLimitBytes": 1073741824, "RollingInterval" : "Day", @@ -117,6 +118,8 @@ By default, Umbraco uses a special Serilog 'sink' that is optimized for performa } ``` +You can also disable this sink if you do not wish to write files to disk. + ## Adding a custom log property to all log items You may wish to add a log property to all log messages. A good example could be a log property for the `environment` to determine if the log message came from `development` or `production`. From 42db115a2344986e1b2550239386b6f9865b7a18 Mon Sep 17 00:00:00 2001 From: Migaroez Date: Wed, 30 Jul 2025 13:17:18 +0200 Subject: [PATCH 02/10] Added AzureTableLogsService --- .../fundamentals/backoffice/logviewer.md | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index 32daf1bc0c6..a1a1fa35155 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -145,6 +145,46 @@ public class AzureTableLogsRepository : LogViewerRepositoryBase Azure Table Storage requires entities to implement the `ITableEntity` interface. Since Umbraco’s default log entity does not implement this, a custom entity (`AzureTableLogEntity`) must be created to ensure logs are correctly fetched. +### Creating a custom log viewer service + +The next thing we need to do is create a new service that amongs other things is responsible to figure out whether a certain log query is allowed. + +```csharp +public class AzureTableLogsService : LogViewerServiceBase +{ + private readonly ILogViewerRepository _logViewerRepository; + + public AzureTableLogsService( + ILogViewerQueryRepository logViewerQueryRepository, + ICoreScopeProvider provider, + ILogViewerRepository logViewerRepository) + : base(logViewerQueryRepository, provider, logViewerRepository) + { + _logViewerRepository = logViewerRepository; + } + + // Change this to what you think is sensible + // as an example we check whether more than 5 days logs are requested + public override Task> CanViewLogsAsync(LogTimePeriod logTimePeriod) + { + return logTimePeriod.EndTime - logTimePeriod.StartTime < TimeSpan.FromDays(5) + ? Task.FromResult(Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, true)) + : Task.FromResult(Attempt.SucceedWithStatus(LogViewerOperationStatus.CancelledByLogsSizeValidation, false)); + } + + public override ReadOnlyDictionary GetLogLevelsFromSinks() + { + var configuredLogLevels = new Dictionary + { + { "Global", GetGlobalMinLogLevel() }, + { "AzureTableStorage", _logViewerRepository.RestrictedToMinimumLevel() }, + }; + + return configuredLogLevels.AsReadOnly(); + } +} +``` + ### Register implementation Umbraco needs to be made aware that there is a new implementation of an `ILogViewerRepository` to register. We also need to replace the default JSON LogViewer that is shipped in the core of Umbraco. @@ -152,12 +192,17 @@ Umbraco needs to be made aware that there is a new implementation of an `ILogVie ```csharp using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Infrastructure.DependencyInjection; +using Umbraco.Cms.Core.Services; namespace My.Website; public class AzureTableLogsComposer : IComposer { - public void Compose(IUmbracoBuilder builder) => builder.Services.AddUnique(); + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.Services.AddUnique(); + } } } ``` From 50bfb228f695501c131d409cdd5a4e99e0d7abb4 Mon Sep 17 00:00:00 2001 From: Migaroez Date: Wed, 30 Jul 2025 17:11:26 +0200 Subject: [PATCH 03/10] Fix typo and wrong function --- .../fundamentals/backoffice/logviewer.md | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index a1a1fa35155..62fa49d29f0 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -164,12 +164,12 @@ public class AzureTableLogsService : LogViewerServiceBase } // Change this to what you think is sensible - // as an example we check whether more than 5 days logs are requested + // as an example we check whether more than 5 days off logs are requested public override Task> CanViewLogsAsync(LogTimePeriod logTimePeriod) { return logTimePeriod.EndTime - logTimePeriod.StartTime < TimeSpan.FromDays(5) ? Task.FromResult(Attempt.SucceedWithStatus(LogViewerOperationStatus.Success, true)) - : Task.FromResult(Attempt.SucceedWithStatus(LogViewerOperationStatus.CancelledByLogsSizeValidation, false)); + : Task.FromResult(Attempt.FailWithStatus(LogViewerOperationStatus.CancelledByLogsSizeValidation, false)); } public override ReadOnlyDictionary GetLogLevelsFromSinks() @@ -211,18 +211,19 @@ public class AzureTableLogsComposer : IComposer With the above two classes, the setup is in place to view logs from an Azure Table. However, logs are not yet persisted into the Azure Table Storage account. To enable persistence, configure the Serilog logging pipeline to store logs in Azure Table Storage. -* Install `Serilog.Sinks.AzureTableStorage` from NuGet. -* Add a new sink to `appsettings.json` with credentials to persist logs to Azure. +- Install `Serilog.Sinks.AzureTableStorage` from NuGet. +- Add a new sink to `appsettings.json` with credentials to persist logs to Azure. The following sink needs to be added to the [`Serilog:WriteTo`](https://github.com/serilog/serilog-sinks-azuretablestorage#json-configuration) array. ```json { -"Name": "AzureTableStorage", -"Args": { - "storageTableName": "LogEventEntity", - "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact", - "connectionString": "DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=KEY;EndpointSuffix=core.windows.net"} + "Name": "AzureTableStorage", + "Args": { + "storageTableName": "LogEventEntity", + "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact", + "connectionString": "DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=KEY;EndpointSuffix=core.windows.net" + } } ``` @@ -230,4 +231,4 @@ For more in-depth information about logging and how to configure it, see the [Lo ### Compact Log Viewer - Desktop App -[Compact Log Viewer](https://www.microsoft.com/store/apps/9N8RV8LKTXRJ?cid=storebadge\&ocid=badge). A desktop tool is available for viewing and querying JSON log files in the same way as the built-in Log Viewer in Umbraco. +[Compact Log Viewer](https://www.microsoft.com/store/apps/9N8RV8LKTXRJ?cid=storebadge&ocid=badge). A desktop tool is available for viewing and querying JSON log files in the same way as the built-in Log Viewer in Umbraco. From 98ea78f49e1215683da0565f5f59cdb96166c976 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Wed, 30 Jul 2025 17:14:09 +0200 Subject: [PATCH 04/10] Apply suggestions from code review Co-authored-by: Andy Butland --- 16/umbraco-cms/fundamentals/backoffice/logviewer.md | 2 +- 16/umbraco-cms/fundamentals/code/debugging/logging.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index 62fa49d29f0..d0efb481710 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -68,7 +68,7 @@ public class AzureTableLogsRepository : LogViewerRepositoryBase protected override IEnumerable GetLogs(LogTimePeriod logTimePeriod, ILogFilter logFilter) { - // This example uses a connetionstring compatible with the Azurite emulator + // This example uses a connection string compatible with the Azurite emulator // https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite var client = new TableClient( diff --git a/16/umbraco-cms/fundamentals/code/debugging/logging.md b/16/umbraco-cms/fundamentals/code/debugging/logging.md index 737ed137bf6..7d7a1b278ae 100644 --- a/16/umbraco-cms/fundamentals/code/debugging/logging.md +++ b/16/umbraco-cms/fundamentals/code/debugging/logging.md @@ -119,7 +119,7 @@ Serilog can be configured and extended by using the .NET Core configuration such ## The UmbracoFile Sink -Serilog uses the concept of Sinks to output the log messages to different places. Umbraco ships with a custom sink configuration called UmbracoFile that uses the [Serilog.Sinks.File](https://github.com/serilog/serilog-sinks-file) sink to save the logs to a rolling file on disk in the Umbraco. You can disable this sink by setting its Enabled configuration flag to false, see [Serilog config](../../../reference/configuration/serilog.md) for more information. +Serilog uses the concept of Sinks to output the log messages to different places. Umbraco ships with a custom sink configuration called UmbracoFile that uses the [Serilog.Sinks.File](https://github.com/serilog/serilog-sinks-file) sink. This will save the logs to a rolling file on disk. You can disable this sink by setting its Enabled configuration flag to false, see [Serilog config](../../../reference/configuration/serilog.md) for more information. ## The logviewer dashboard From 3702faf51b7f5fe34fc8e9e201f5d21e35a148a2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 1 Aug 2025 08:12:47 +0200 Subject: [PATCH 05/10] Apply suggestions from code review --- 16/umbraco-cms/fundamentals/backoffice/logviewer.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index d0efb481710..70634d7ccfe 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -143,11 +143,11 @@ public class AzureTableLogsRepository : LogViewerRepositoryBase } ``` -Azure Table Storage requires entities to implement the `ITableEntity` interface. Since Umbraco’s default log entity does not implement this, a custom entity (`AzureTableLogEntity`) must be created to ensure logs are correctly fetched. +Azure Table Storage requires entities to implement the `ITableEntity` interface. Since Umbraco's default log entity does not implement this, a custom entity (`AzureTableLogEntity`) must be created to ensure logs are correctly fetched. ### Creating a custom log viewer service -The next thing we need to do is create a new service that amongs other things is responsible to figure out whether a certain log query is allowed. +The next thing we need to do is create a new service that amongst other things is responsible for figuring out whether a provided log query is allowed. ```csharp public class AzureTableLogsService : LogViewerServiceBase @@ -187,7 +187,7 @@ public class AzureTableLogsService : LogViewerServiceBase ### Register implementation -Umbraco needs to be made aware that there is a new implementation of an `ILogViewerRepository` to register. We also need to replace the default JSON LogViewer that is shipped in the core of Umbraco. +Umbraco needs to be made aware that there is a new implementation of an `ILogViewerRepository` and an `ILogViewerService` to register. These need to replace the default ones that are shipped with Umbraco. ```csharp using Umbraco.Cms.Core.Composing; From f4ef57dcfc011d8278a6c18f254bb65b36a27b7b Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 1 Aug 2025 08:20:14 +0200 Subject: [PATCH 06/10] Further updates --- .../fundamentals/backoffice/logviewer.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index 70634d7ccfe..fd9ae7a7fb0 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -32,7 +32,7 @@ If you frequently use a custom query, you can save it for quick access. Type you ## Implementing Your Own Log Viewer Source -Umbraco allows you to implement a customn `ILogViewerRepository` to fetch logs from alternative sources, such as **Azure Table Storage**. +Umbraco allows you to implement a custom `ILogViewerRepository` and `ILogViewerService` to fetch logs from alternative sources, such as **Azure Table Storage**. ### Creating a Custom Log Viewer Repository @@ -152,19 +152,16 @@ The next thing we need to do is create a new service that amongst other things i ```csharp public class AzureTableLogsService : LogViewerServiceBase { - private readonly ILogViewerRepository _logViewerRepository; - public AzureTableLogsService( ILogViewerQueryRepository logViewerQueryRepository, ICoreScopeProvider provider, ILogViewerRepository logViewerRepository) : base(logViewerQueryRepository, provider, logViewerRepository) { - _logViewerRepository = logViewerRepository; } - // Change this to what you think is sensible - // as an example we check whether more than 5 days off logs are requested + // Change this to what you think is sensible. + // As an example we check whether more than 5 days off logs are requested. public override Task> CanViewLogsAsync(LogTimePeriod logTimePeriod) { return logTimePeriod.EndTime - logTimePeriod.StartTime < TimeSpan.FromDays(5) @@ -177,7 +174,7 @@ public class AzureTableLogsService : LogViewerServiceBase var configuredLogLevels = new Dictionary { { "Global", GetGlobalMinLogLevel() }, - { "AzureTableStorage", _logViewerRepository.RestrictedToMinimumLevel() }, + { "AzureTableStorage", LogViewerRepository.RestrictedToMinimumLevel() }, }; return configuredLogLevels.AsReadOnly(); @@ -185,9 +182,9 @@ public class AzureTableLogsService : LogViewerServiceBase } ``` -### Register implementation +### Register implementations -Umbraco needs to be made aware that there is a new implementation of an `ILogViewerRepository` and an `ILogViewerService` to register. These need to replace the default ones that are shipped with Umbraco. +Umbraco needs to be made aware that there is a new implementation of an `ILogViewerRepository` and an `ILogViewerService`. These need to replace the default ones that are shipped with Umbraco. ```csharp using Umbraco.Cms.Core.Composing; From 1c78ce2514979944f160dc09772a7d59be06f298 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 1 Aug 2025 08:25:59 +0200 Subject: [PATCH 07/10] Resolved issue raised in review --- 16/umbraco-cms/fundamentals/backoffice/logviewer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index fd9ae7a7fb0..daf7793d4af 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -206,7 +206,7 @@ public class AzureTableLogsComposer : IComposer ### Configuring Logging to Azure Table Storage -With the above two classes, the setup is in place to view logs from an Azure Table. However, logs are not yet persisted into the Azure Table Storage account. To enable persistence, configure the Serilog logging pipeline to store logs in Azure Table Storage. +With the above three classes, the setup is in place to view logs from an Azure Table. However, logs are not yet persisted into the Azure Table Storage account. To enable persistence, configure the Serilog logging pipeline to store logs in Azure Table Storage. - Install `Serilog.Sinks.AzureTableStorage` from NuGet. - Add a new sink to `appsettings.json` with credentials to persist logs to Azure. From 2431119b9a10e61040dfc2513e8b10661e7656b0 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 1 Aug 2025 08:33:32 +0200 Subject: [PATCH 08/10] Cross-referenced details on the running Umbraco in Docker page --- .../server-setup/running-umbraco-in-docker.md | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/running-umbraco-in-docker.md b/16/umbraco-cms/fundamentals/setup/server-setup/running-umbraco-in-docker.md index 7fda09e7aa2..928204cfc13 100644 --- a/16/umbraco-cms/fundamentals/setup/server-setup/running-umbraco-in-docker.md +++ b/16/umbraco-cms/fundamentals/setup/server-setup/running-umbraco-in-docker.md @@ -8,30 +8,34 @@ Docker is a platform for developing, shipping, and running applications in conta ## The Docker file system -By default, files created inside a container are written to an ephemeral, writable container layer. +By default, files created inside a container are written to an ephemeral, writable container layer. This means that the files don't persist when the container is removed, and it's challenging to get files out of the container. Additionally, this writable layer is not suitable for performance-critical data processing. This has implications when running Umbraco in Docker. For more information, refer to the [Docker documentation on storage](https://docs.docker.com/engine/storage/). -### General file system consideration +### General file system consideration In general, when working with files and Docker you work in a "push" fashion with read-only layers. When you build, you take all your files and "push" them into this read-only layer. This means that you should avoid making files on the fly, and instead rely on building your image. -In an Umbraco context, this means you should not create or edit template, script or stylesheet files via the backoffice. These should be deployed as part of your web application and not managed via Umbraco. +In an Umbraco context, this means you should not create or edit template, script or stylesheet files via the backoffice. These should be deployed as part of your web application and not managed via Umbraco. Similarly, you shouldn't use InMemory modelsbuilder, since that also relies on creating files on the disk. While this is not a hard requirement, it doesn't provide any value unless you are live editing your site. Instead, configure models builder to use "source code" mode in development, and "none" in production, as [described when using runtime modes](https://docs.umbraco.com/umbraco-cms/fundamentals/setup/server-setup/runtime-modes). - ### Logs -Umbraco writes logs to the `/umbraco/Logs/` directory. Due to the performance implications of writing to a writable layer, -and the limited size, it is recommended to mount a volume to this directory. +Umbraco writes logs to the `/umbraco/Logs/` directory. Due to the performance implications of writing to a writable layer, and the limited size, it is recommended to mount a volume to this directory. + +You may prefer to avoid writing to disk for logs when hosting in containers. If so, you can disable this default behavior and register a custom Serilog sink to alternative storage, such as Azure Table storage. + +You can also provide an alternative implementation of a common abstraction for the log viewer. In this way you can read logs from the location where you have configured them to be written. + +For more on this please read the article on Umbraco's [log viewer](../../backoffice/logviewer.md). ### Data @@ -39,14 +43,14 @@ The `/umbraco/Data/` directory is used to store temporary files, such as file up ### Media -It's recommended to not store media in the writable layer. This is for similar performance reasons as logs, -but also for practical hosting reasons. You likely want to persist media files between containers. +It's recommended to not store media in the writable layer. This is for similar performance reasons as logs, +but also for practical hosting reasons. You likely want to persist media files between containers. One solution is to use bind mounts. The ideal setup, though, is to store the media and ImageSharp cache externally. For more information, refer to the [Azure Blob Storage documentation](https://docs.umbraco.com/umbraco-cms/extending/filesystemproviders/azure-blob-storage). ### Required files -Your solution may require some specific files to run, such as license files. You will need to pass these files into the container at build time, or mount them externally. +Your solution may require some specific files to run, such as license files. You will need to pass these files into the container at build time, or mount them externally. ## HTTPS From 6d580558902cb77fb0d04b0307b5b3bde04527a5 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 1 Aug 2025 08:35:24 +0200 Subject: [PATCH 09/10] Shorter sentences. --- 16/umbraco-cms/fundamentals/backoffice/logviewer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index daf7793d4af..444abffe342 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -147,7 +147,7 @@ Azure Table Storage requires entities to implement the `ITableEntity` interface. ### Creating a custom log viewer service -The next thing we need to do is create a new service that amongst other things is responsible for figuring out whether a provided log query is allowed. +The next thing we need to do is create a new implementation of `ILogViewerService`. Amongst other things, this is responsible for figuring out whether a provided log query is allowed. Again a base class is available. ```csharp public class AzureTableLogsService : LogViewerServiceBase From 49b9f836c2c8b3a5946a344f5c17d2b3c1188824 Mon Sep 17 00:00:00 2001 From: sofietoft Date: Mon, 4 Aug 2025 09:51:44 +0200 Subject: [PATCH 10/10] Let's not use "we" --- 16/umbraco-cms/fundamentals/backoffice/logviewer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md index 444abffe342..d959abf7b0c 100644 --- a/16/umbraco-cms/fundamentals/backoffice/logviewer.md +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -147,7 +147,7 @@ Azure Table Storage requires entities to implement the `ITableEntity` interface. ### Creating a custom log viewer service -The next thing we need to do is create a new implementation of `ILogViewerService`. Amongst other things, this is responsible for figuring out whether a provided log query is allowed. Again a base class is available. +The next thing to do is create a new implementation of `ILogViewerService`. Amongst other things, this is responsible for figuring out whether a provided log query is allowed. Again a base class is available. ```csharp public class AzureTableLogsService : LogViewerServiceBase @@ -161,7 +161,7 @@ public class AzureTableLogsService : LogViewerServiceBase } // Change this to what you think is sensible. - // As an example we check whether more than 5 days off logs are requested. + // As an example, check whether more than 5 days off logs are requested. public override Task> CanViewLogsAsync(LogTimePeriod logTimePeriod) { return logTimePeriod.EndTime - logTimePeriod.StartTime < TimeSpan.FromDays(5)