diff --git a/src/CamBooth/.idea/.idea.CamBooth/.idea/encodings.xml b/src/CamBooth/.idea/.idea.CamBooth/.idea/encodings.xml index df87cf9..da0415a 100644 --- a/src/CamBooth/.idea/.idea.CamBooth/.idea/encodings.xml +++ b/src/CamBooth/.idea/.idea.CamBooth/.idea/encodings.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/CamBooth/CamBooth.App/App.xaml.cs b/src/CamBooth/CamBooth.App/App.xaml.cs index 2a00589..345a561 100644 --- a/src/CamBooth/CamBooth.App/App.xaml.cs +++ b/src/CamBooth/CamBooth.App/App.xaml.cs @@ -1,5 +1,5 @@ -using System.IO; - using System.Windows; +using System.IO; +using System.Windows; using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.Logging; @@ -11,6 +11,7 @@ using EDSDKLib.API.Base; using EOSDigital.API; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace CamBooth.App; @@ -26,8 +27,25 @@ public partial class App : Application { base.OnStartup(e); + // Konfiguration laden + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"; + + var configBuilder = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile("Core/AppSettings/app.settings.json", optional: false, reloadOnChange: true); + + if (environment == "Development") + { + configBuilder.AddJsonFile("Core/AppSettings/app.settings.dev.json", optional: true, reloadOnChange: true); + } + + var configuration = configBuilder.Build(); + var services = new ServiceCollection(); + // Register Configuration + services.AddSingleton(configuration); + // Register base services services.AddSingleton(); services.AddSingleton(); diff --git a/src/CamBooth/CamBooth.App/CamBooth.App.csproj b/src/CamBooth/CamBooth.App/CamBooth.App.csproj index f6204e9..208bc17 100644 --- a/src/CamBooth/CamBooth.App/CamBooth.App.csproj +++ b/src/CamBooth/CamBooth.App/CamBooth.App.csproj @@ -25,6 +25,13 @@ + + + + + + + diff --git a/src/CamBooth/CamBooth.App/Core/AppSettings/AppSettingsService.cs b/src/CamBooth/CamBooth.App/Core/AppSettings/AppSettingsService.cs index 2132c31..432d461 100644 --- a/src/CamBooth/CamBooth.App/Core/AppSettings/AppSettingsService.cs +++ b/src/CamBooth/CamBooth.App/Core/AppSettings/AppSettingsService.cs @@ -1,4 +1,4 @@ -using CamBooth.App.Core.Logging; +using CamBooth.App.Core.Logging; namespace CamBooth.App.Core.AppSettings; @@ -62,6 +62,15 @@ public class AppSettingsService public string ConfigFileName => loadedConfigFile; + // Logging Settings + public string LogLevel => configuration["LoggingSettings:LogLevel"] ?? "Information"; + + public string LogDirectory => configuration["LoggingSettings:LogDirectory"] ?? "Logs"; + + public string? RemoteServerUrl => configuration["LoggingSettings:RemoteServerUrl"]; + + public string? RemoteServerApiKey => configuration["LoggingSettings:RemoteServerApiKey"]; + // Lychee Upload Settings public string? LycheeApiUrl => configuration["LycheeSettings:ApiUrl"]; diff --git a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json index a36be88..2735108 100644 --- a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json +++ b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json @@ -11,8 +11,37 @@ "IsShutdownEnabled": false, "UseMockCamera": true }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console" + }, + { + "Name": "File", + "Args": { + "path": "Logs/cambooth-dev-.log", + "rollingInterval": "Day", + "retainedFileCountLimit": 7, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}" + } + } + ] + }, + "LoggingSettings": { + "LogLevel": "Debug", + "LogDirectory": "Logs", + "RemoteServerUrl": "https://log.grimma-fotobox.de", + "RemoteServerApiKey": "nhnVql3QNgoAxvDWmNyU" + }, "LycheeSettings": { - "ApiUrl": "https://cambooth-pics.rblnews.de", + "ApiUrl": "https://gallery.grimma-fotobox.de", "Username": "itob", "Password": "VfVyqal&Nv8U&P", "DefaultAlbumId": "gMM3W-Pk9mr8k57k-WU2Jz8t", diff --git a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json index 2027cc1..3fa6783 100644 --- a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json +++ b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json @@ -8,11 +8,40 @@ "PhotoCountdownSeconds": 5, "FocusDelaySeconds": 2, "FocusTimeoutMs": 3000, - "IsShutdownEnabled": true, - "UseMockCamera": true + "IsShutdownEnabled": false, + "UseMockCamera": false + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console" + }, + { + "Name": "File", + "Args": { + "path": "Logs/cambooth-.log", + "rollingInterval": "Day", + "retainedFileCountLimit": 120, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}" + } + } + ] + }, + "LoggingSettings": { + "LogLevel": "Information", + "LogDirectory": "Logs", + "RemoteServerUrl": "https://log.grimma-fotobox.de", + "RemoteServerApiKey": "8rjvr0zZmceuFZMYydKU" }, "LycheeSettings": { - "ApiUrl": "https://cambooth-pics.rblnews.de", + "ApiUrl": "https://gallery.grimma-fotobox.de", "Username": "itob", "Password": "VfVyqal&Nv8U&P", "DefaultAlbumId": "gMM3W-Pk9mr8k57k-WU2Jz8t", diff --git a/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs b/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs index 9315443..439998f 100644 --- a/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs +++ b/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs @@ -1,13 +1,63 @@ -using System.Globalization; +using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using System.Windows; +using Serilog; +using Serilog.Core; +using Serilog.Events; +using Serilog.Formatting.Json; +using Serilog.Sinks.Http; +using Microsoft.Extensions.Configuration; namespace CamBooth.App.Core.Logging; -public class Logger +/// +/// Custom HTTP Client für Serilog HTTP Sink mit API-Key Support +/// +public class SeqHttpClient : IHttpClient { - private readonly string _logsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); - private readonly string _errorLogPath; + private readonly HttpClient _httpClient; + private readonly string _apiKey; + + public SeqHttpClient(string apiKey = "") + { + _httpClient = new HttpClient(); + _apiKey = apiKey; + + // Setze API-Key Header, falls vorhanden + if (!string.IsNullOrWhiteSpace(_apiKey)) + { + _httpClient.DefaultRequestHeaders.Add("X-Seq-Api-Key", _apiKey); + } + } + + public void Configure(IConfiguration configuration) + { + // Konfiguration vom HTTP Sink - nicht nötig für unseren Use-Case + } + + public async Task PostAsync(string requestUri, Stream contentStream, CancellationToken cancellationToken) + { + using (var content = new StreamContent(contentStream)) + { + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + return await _httpClient.PostAsync(requestUri, content, cancellationToken); + } + } + + public void Dispose() + { + _httpClient?.Dispose(); + } +} + +public class Logger : IDisposable +{ + private readonly Serilog.Core.Logger _serilogLogger; public event LoggingEventHandler? InfoLog; public event LoggingEventHandler? ErrorLog; @@ -15,68 +65,185 @@ public class Logger public event LoggingEventHandler? DebugLog; public delegate void LoggingEventHandler(string text); - public Logger() + public Logger(IConfiguration configuration) { - // Logs-Ordner erstellen, falls nicht vorhanden - if (!Directory.Exists(_logsDirectory)) + var logLevel = configuration["LoggingSettings:LogLevel"] ?? "Information"; + var logDirectory = configuration["LoggingSettings:LogDirectory"] ?? "Logs"; + var remoteServerUrl = configuration["LoggingSettings:RemoteServerUrl"]; + var remoteServerApiKey = configuration["LoggingSettings:RemoteServerApiKey"]; + + if (!Directory.Exists(logDirectory)) { - Directory.CreateDirectory(_logsDirectory); + Directory.CreateDirectory(logDirectory); } - _errorLogPath = Path.Combine(_logsDirectory, "error.txt"); + var minimumLevel = ParseLogLevel(logLevel); + + var loggerConfig = new LoggerConfiguration() + .MinimumLevel.Is(minimumLevel) + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .Enrich.FromLogContext() + .Enrich.WithProperty("Application", "CamBooth") + .WriteTo.Console( + outputTemplate: "{Timestamp:dd.MM.yyyy HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .WriteTo.File( + Path.Combine(logDirectory, "cambooth-.log"), + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 30, + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}"); + + if (!string.IsNullOrWhiteSpace(remoteServerUrl)) + { + try + { + loggerConfig.WriteTo.Seq( + serverUrl: remoteServerUrl, + apiKey: remoteServerApiKey, + restrictedToMinimumLevel: minimumLevel + ); + + Console.WriteLine($"Seq Sink konfiguriert: {remoteServerUrl}"); + } + catch (Exception ex) + { + Console.WriteLine($"Fehler beim Konfigurieren des Seq-Sinks: {ex.Message}"); + } + } + + _serilogLogger = loggerConfig.CreateLogger(); + + Log.Logger = _serilogLogger; + + Info($"Serilog Logger initialisiert - Level: {logLevel}, Directory: {logDirectory}"); + if (!string.IsNullOrWhiteSpace(remoteServerUrl)) + { + Info($"Remote Server konfiguriert: {remoteServerUrl}"); + } } - private void WriteToErrorLog(string message) + private LogEventLevel ParseLogLevel(string level) { - try + return level.ToLowerInvariant() switch { - File.AppendAllText(_errorLogPath, message + Environment.NewLine); - } - catch (Exception ex) - { - Console.WriteLine($"Fehler beim Schreiben in Fehlerlog: {ex.Message}"); - } + "verbose" => LogEventLevel.Verbose, + "debug" => LogEventLevel.Debug, + "information" => LogEventLevel.Information, + "warning" => LogEventLevel.Warning, + "error" => LogEventLevel.Error, + "fatal" => LogEventLevel.Fatal, + _ => LogEventLevel.Information + }; } public void Info(string message) { - Application.Current.Dispatcher.Invoke(() => + try { - message = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [INFO] " + message; - InfoLog?.Invoke(message); - Console.WriteLine(message); - }); + if (Application.Current?.Dispatcher != null) + { + Application.Current.Dispatcher.Invoke(() => + { + var formattedMessage = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [INFO] " + message; + InfoLog?.Invoke(formattedMessage); + }); + } + + _serilogLogger.Information(message); + } + catch (Exception ex) + { + Console.WriteLine($"Fehler beim Info-Logging: {ex.Message}"); + } } public void Warning(string message) { - Application.Current.Dispatcher.Invoke(() => + try { - message = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [WARNING] " + message; - WarningLog?.Invoke(message); - Console.WriteLine(message); - WriteToErrorLog(message); - }); + if (Application.Current?.Dispatcher != null) + { + Application.Current.Dispatcher.Invoke(() => + { + var formattedMessage = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [WARNING] " + message; + WarningLog?.Invoke(formattedMessage); + }); + } + + _serilogLogger.Warning(message); + } + catch (Exception ex) + { + Console.WriteLine($"Fehler beim Warning-Logging: {ex.Message}"); + } } public void Error(string message) { - Application.Current.Dispatcher.Invoke(() => + try { - message = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [ERROR] " + message; - ErrorLog?.Invoke(message); - Console.WriteLine(message); - WriteToErrorLog(message); - }); + if (Application.Current?.Dispatcher != null) + { + Application.Current.Dispatcher.Invoke(() => + { + var formattedMessage = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [ERROR] " + message; + ErrorLog?.Invoke(formattedMessage); + }); + } + + _serilogLogger.Error(message); + } + catch (Exception ex) + { + Console.WriteLine($"Fehler beim Error-Logging: {ex.Message}"); + } + } + + public void Error(string message, Exception exception) + { + try + { + if (Application.Current?.Dispatcher != null) + { + Application.Current.Dispatcher.Invoke(() => + { + var formattedMessage = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [ERROR] " + message; + ErrorLog?.Invoke(formattedMessage); + }); + } + + _serilogLogger.Error(exception, message); + } + catch (Exception ex) + { + Console.WriteLine($"Fehler beim Error-Logging: {ex.Message}"); + } } public void Debug(string message) { - Application.Current.Dispatcher.Invoke(() => + try { - message = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [DEBUG] " + message; - DebugLog?.Invoke(message); - Console.WriteLine(message); - }); + if (Application.Current?.Dispatcher != null) + { + Application.Current.Dispatcher.Invoke(() => + { + var formattedMessage = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture) + ": [DEBUG] " + message; + DebugLog?.Invoke(formattedMessage); + }); + } + + _serilogLogger.Debug(message); + } + catch (Exception ex) + { + Console.WriteLine($"Fehler beim Debug-Logging: {ex.Message}"); + } + } + + public void Dispose() + { + _serilogLogger?.Dispose(); + Log.CloseAndFlush(); } } \ No newline at end of file diff --git a/src/CamBooth/CamBooth.App/ToDos.txt b/src/CamBooth/CamBooth.App/ToDos.txt index 7f3eabb..19b99ed 100644 --- a/src/CamBooth/CamBooth.App/ToDos.txt +++ b/src/CamBooth/CamBooth.App/ToDos.txt @@ -6,6 +6,8 @@ - Energiesparmodus abschalten - Starbildschirm mit freundlicher Begrüßung, kurzer Erklärung, und viel Spaß wünschen mit der FotoCam - Verschiedene Hinweise anzeigen beim Fotografieren (lächeln, Hasensohren, Zunge raus, Grimasse, usw.) -- Bild über QR Code runterladen +- Bild über QR Code runterladen (QR Code anzeigen, sowie ausdrucken und anklebene) - Windows updates deaktivieren -- logging einbinden (Elastic order ähnliches) \ No newline at end of file +- logging einbinden (Elastic order ähnliches) +- Router anschließen für Upload +- Configs kontrollieren auf Fotobox