Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat logging improvements #1816

Merged
merged 12 commits into from
Feb 7, 2025
66 changes: 66 additions & 0 deletions PkSoftwareService.Custom.Backend/InMemorySink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting.Display;

namespace PkSoftwareService.Custom.Backend;

public class InMemorySink : ILogEventSink
{
private readonly int _capacity;
private readonly Queue<string> _logMessages;
private readonly object _syncRoot = new object();
private readonly MessageTemplateTextFormatter _formatter;

/// <summary>
/// Creates a new InMemorySink.
/// </summary>
/// <param name="outputTemplate">The output template (should match your Console sink).</param>
/// <param name="formatProvider">Optional format provider.</param>
/// <param name="capacity">Max number of messages to store.</param>
public InMemorySink(string outputTemplate, IFormatProvider? formatProvider = null, int capacity = 10000)
{
_capacity = capacity;
_logMessages = new Queue<string>(capacity);
_formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);
}

public void Emit(LogEvent logEvent)
{
// Format the log event into a string.
using var writer = new StringWriter();
_formatter.Format(logEvent, writer);
var message = writer.ToString();

// Ensure thread safety while enqueuing/dequeuing.
lock (_syncRoot)
{
if (_logMessages.Count >= _capacity)
{
_logMessages.Dequeue(); // remove oldest
}
_logMessages.Enqueue(message);
}
}

/// <summary>
/// Returns a snapshot of the current log messages.
/// </summary>
public List<string> GetLogs()
{
lock (_syncRoot)
{
return _logMessages.Select(x => x.Trim()).ToList();
}
}

/// <summary>
/// Optionally clear all logs.
/// </summary>
public void Clear()
{
lock (_syncRoot)
{
_logMessages.Clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Serilog" Version="4.0.0" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions TeslaSolarCharger.sln
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeslaSolarCharger.SharedMod
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeslaSolarCharger.Services", "TeslaSolarCharger.Services\TeslaSolarCharger.Services.csproj", "{21A8DB64-E449-474E-94DD-360C30D1756A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkSoftwareService.Custom.Backend", "PkSoftwareService.Custom.Backend\PkSoftwareService.Custom.Backend.csproj", "{B9331EFC-2B31-4447-B6C3-3E898B560946}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -87,6 +89,10 @@ Global
{21A8DB64-E449-474E-94DD-360C30D1756A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21A8DB64-E449-474E-94DD-360C30D1756A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21A8DB64-E449-474E-94DD-360C30D1756A}.Release|Any CPU.Build.0 = Release|Any CPU
{B9331EFC-2B31-4447-B6C3-3E898B560946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9331EFC-2B31-4447-B6C3-3E898B560946}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9331EFC-2B31-4447-B6C3-3E898B560946}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9331EFC-2B31-4447-B6C3-3E898B560946}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
9 changes: 9 additions & 0 deletions TeslaSolarCharger/Client/Pages/Support.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@page "/support"

<h1>Support</h1>

<MudLink Href="api/Debug/DownloadLogs">Download Logs</MudLink>

@code {

}
5 changes: 5 additions & 0 deletions TeslaSolarCharger/Client/Shared/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
<span class="oi oi-cog" aria-hidden="true"></span> Base Configuration
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="support">
<span class="oi oi-code" aria-hidden="true"></span> Support
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="backupAndRestore">
<span class="oi oi-data-transfer-download" aria-hidden="true"></span> Backup and Restore
Expand Down
42 changes: 42 additions & 0 deletions TeslaSolarCharger/Server/Controllers/DebugController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Mvc;
using PkSoftwareService.Custom.Backend;
using Serilog.Events;
using System.Text;
using TeslaSolarCharger.SharedBackend.Abstracts;

namespace TeslaSolarCharger.Server.Controllers;

public class DebugController(InMemorySink inMemorySink, Serilog.Core.LoggingLevelSwitch inMemoryLogLevelSwitch/*chaning log level switch is not tested*/) : ApiBaseController
{
[HttpGet]
public IActionResult DownloadLogs()
{
// Get the logs from the in-memory sink.
var logs = inMemorySink.GetLogs();

// Join the log entries into a single string, separated by new lines.
var content = string.Join(Environment.NewLine, logs);

// Convert the string content to a byte array (UTF8 encoding).
var bytes = Encoding.UTF8.GetBytes(content);

// Return the file with the appropriate content type and file name.
return File(bytes, "text/plain", "logs.log");
}

/// <summary>
/// Adjusts the minimum log level for the in-memory sink.
/// </summary>
/// <param name="level">The new log level (e.g. Verbose, Debug, Information, Warning, Error, Fatal).</param>
/// <returns>Status message.</returns>
[HttpPost]
public IActionResult SetLogLevel([FromQuery] string level)
{
if (!Enum.TryParse<LogEventLevel>(level, true, out var newLevel))
{
return BadRequest("Invalid log level. Use one of: Verbose, Debug, Information, Warning, Error, Fatal");
}
inMemoryLogLevelSwitch.MinimumLevel = newLevel;
return Ok($"In-memory sink log level changed to {newLevel}");
}
}
26 changes: 21 additions & 5 deletions TeslaSolarCharger/Server/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using PkSoftwareService.Custom.Backend;
using Serilog;
using Serilog.Context;
using Serilog.Core;
using Serilog.Events;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Interceptors;
using System.Reflection;
Expand Down Expand Up @@ -42,18 +45,31 @@
builder.Services.AddSharedDependencies();
builder.Services.AddServicesDependencies();

builder.Host.UseSerilog((context, configuration) => configuration
.ReadFrom.Configuration(context.Configuration));

builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<CarBasicConfigurationValidator>();

builder.Host.UseSerilog();
const string outputTemplate = "[{Timestamp:dd-MMM-yyyy HH:mm:ss.fff} {Level:u3} {SourceContext}] {Message:lj}{NewLine}{Exception}";
var inMemoryLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
var inMemorySink = new InMemorySink(outputTemplate);

builder.Services.AddSingleton(inMemoryLevelSwitch);
builder.Services.AddSingleton(inMemorySink);

var app = builder.Build();

Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(app.Services.GetRequiredService<IConfiguration>())
.Enrich.FromLogContext()
.MinimumLevel.Verbose()// overall minimum
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Error)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning)
.MinimumLevel.Override("TeslaSolarCharger.Shared.Wrappers.ConfigurationWrapper", LogEventLevel.Information)
.MinimumLevel.Override("TeslaSolarCharger.Model.EntityFramework.DbConnectionStringHelper", LogEventLevel.Information)
.WriteTo.Console(outputTemplate: outputTemplate, restrictedToMinimumLevel: LogEventLevel.Debug)
// Send events to the in–memory sink using a sub–logger and the dynamic level switch.
.WriteTo.Logger(lc => lc
.MinimumLevel.ControlledBy(inMemoryLevelSwitch)
.WriteTo.Sink(inMemorySink))
.CreateLogger();


Expand Down
5 changes: 2 additions & 3 deletions TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand Down Expand Up @@ -65,15 +65,14 @@
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0" />
<PackageReference Include="SharpGrip.FluentValidation.AutoValidation.Mvc" Version="1.4.0" />
<PackageReference Include="SharpGrip.FluentValidation.AutoValidation.Shared" Version="1.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\PkSoftwareService.Custom.Backend\PkSoftwareService.Custom.Backend.csproj" />
<ProjectReference Include="..\..\TeslaSolarCharger.Model\TeslaSolarCharger.Model.csproj" />
<ProjectReference Include="..\..\TeslaSolarCharger.Services\TeslaSolarCharger.Services.csproj" />
<ProjectReference Include="..\..\TeslaSolarCharger.SharedBackend\TeslaSolarCharger.SharedBackend.csproj" />
Expand Down
35 changes: 0 additions & 35 deletions TeslaSolarCharger/Server/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,4 @@
{
"Serilog": {
"Using": [
"Serilog.Sinks.Console",
"Serilog.Sinks.Seq"
],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Information",
"System": "Error",
"Microsoft.EntityFrameworkCore.Database.Command": "Information",
"Quartz": "Warning",
"TeslaSolarCharger.Server.Scheduling": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3} {SourceContext}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Seq",
"Args": {
"serverUrl": "https://www.teslasolarcharger.de/seq-logging",
"restrictedToMinimumLevel": "Warning",
"outputTemplate": "[{Timestamp:dd-MMM-yyyy HH:mm:ss.fff} {Level:u3} {SourceContext} {InstallationId} {Version}] {Message:lj}{NewLine}{Exception}"
}
}
],
"Enrich": [
"FromLogContext"
]
},
"AllowedHosts": "*",
"AllowCORS": true,
"DisplayApiRequestCounter": true,
Expand Down
36 changes: 0 additions & 36 deletions TeslaSolarCharger/Server/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,4 @@
{
"Serilog": {
"Using": [
"Serilog.Sinks.Console",
"Serilog.Sinks.Seq"
],
"MinimumLevel": {
"Default": "Debug",
"Enrich": [ "SourceContext" ],
"Override": {
"Microsoft": "Warning",
"System": "Error",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"TeslaSolarCharger.Shared.Wrappers.ConfigurationWrapper": "Information",
"TeslaSolarCharger.Model.EntityFramework.DbConnectionStringHelper": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:dd-MMM-yyyy HH:mm:ss.fff} {Level:u3} {SourceContext}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Seq",
"Args": {
"serverUrl": "https://seq-logging.teslasolarcharger.de/",
"restrictedToMinimumLevel": "Warning",
"outputTemplate": "[{Timestamp:dd-MMM-yyyy HH:mm:ss.fff} {Level:u3} {SourceContext} {InstallationId} {Version}] {Message:lj}{NewLine}{Exception}"
}
}
],
"Enrich": [
"FromLogContext"
]
},
"AllowedHosts": "*",
"ConfigFileLocation": "configs",
"BackupCopyDestinationDirectory": "backups",
Expand Down
Loading