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