diff --git a/Services/Logging/InteractiveUserPath.cs b/Services/Logging/InteractiveUserPath.cs
new file mode 100644
index 0000000..35c69ce
--- /dev/null
+++ b/Services/Logging/InteractiveUserPath.cs
@@ -0,0 +1,58 @@
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace UtopiaCanteenSystem.Services.Logging;
+
+///
+/// Resolves the logged-on Windows user's profile paths when the app runs as a service (SYSTEM).
+///
+internal static class InteractiveUserPath
+{
+ public static string? TryGetLocalAppDataPath()
+ {
+ var profile = TryGetProfileDirectory();
+ if (string.IsNullOrWhiteSpace(profile))
+ return null;
+
+ var localAppData = Path.Combine(profile, "AppData", "Local");
+ return Directory.Exists(localAppData) ? localAppData : null;
+ }
+
+ private static string? TryGetProfileDirectory()
+ {
+ var sessionId = WTSGetActiveConsoleSessionId();
+ if (sessionId == 0xFFFFFFFF)
+ return null;
+
+ if (!WTSQueryUserToken(sessionId, out var userToken) || userToken == IntPtr.Zero)
+ return null;
+
+ try
+ {
+ var sb = new StringBuilder(512);
+ var size = sb.Capacity;
+ if (!GetUserProfileDirectory(userToken, sb, ref size, out _))
+ return null;
+
+ var path = sb.ToString();
+ return string.IsNullOrWhiteSpace(path) ? null : path;
+ }
+ finally
+ {
+ CloseHandle(userToken);
+ }
+ }
+
+ [DllImport("kernel32.dll")]
+ private static extern uint WTSGetActiveConsoleSessionId();
+
+ [DllImport("wtsapi32.dll", SetLastError = true)]
+ private static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken);
+
+ [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern bool GetUserProfileDirectory(IntPtr hToken, StringBuilder lpProfileDir, ref int lpcchSize, out int lpDwFlags);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool CloseHandle(IntPtr hObject);
+}
diff --git a/Services/Logging/LogPaths.cs b/Services/Logging/LogPaths.cs
new file mode 100644
index 0000000..ad23b7e
--- /dev/null
+++ b/Services/Logging/LogPaths.cs
@@ -0,0 +1,65 @@
+using System.IO;
+
+namespace UtopiaCanteenSystem.Services.Logging;
+
+///
+/// File log directories. Client uses the current user's LocalAppData.
+/// Backend (Windows Service) uses the interactive user's LocalAppData when possible
+/// so logs appear under the logged-on user's profile instead of systemprofile.
+///
+public static class LogPaths
+{
+ private static string? _backendLogsDirectoryOverride;
+ private static string? _cachedLocalAppDataRoot;
+
+ ///
+ /// Optional absolute path (e.g. from appsettings LogsDirectory). Used by the backend service only.
+ ///
+ public static void SetBackendLogsDirectory(string? directory)
+ {
+ _backendLogsDirectoryOverride = string.IsNullOrWhiteSpace(directory)
+ ? null
+ : directory.Trim();
+ }
+
+ ///
+ /// Client logs folder: %LocalAppData%\UtopiaCanteenClient\Logs.
+ ///
+ public static string ClientLogsDirectory =>
+ Path.Combine(ResolveLocalAppDataRoot(), "UtopiaCanteenClient", "Logs");
+
+ ///
+ /// Backend logs folder: %LocalAppData%\UtopiaCanteenBackend\Logs
+ /// (interactive user when running as a service).
+ ///
+ public static string BackendLogsDirectory =>
+ _backendLogsDirectoryOverride ?? Path.Combine(ResolveLocalAppDataRoot(), "UtopiaCanteenBackend", "Logs");
+
+ public static string GetBackendLogFilePath(DateTime? date = null)
+ {
+ var d = date ?? DateTime.Now;
+ return Path.Combine(BackendLogsDirectory, $"backend-{d:yyyy-MM-dd}.log");
+ }
+
+ public static string GetClientLogFilePath(DateTime? date = null)
+ {
+ var d = date ?? DateTime.Now;
+ return Path.Combine(ClientLogsDirectory, $"client-{d:yyyy-MM-dd}.log");
+ }
+
+ ///
+ /// User's LocalAppData, or SYSTEM profile when no interactive session (service at login screen).
+ ///
+ public static string ResolveLocalAppDataRoot()
+ {
+ if (!string.IsNullOrEmpty(_cachedLocalAppDataRoot))
+ return _cachedLocalAppDataRoot;
+
+ var interactive = InteractiveUserPath.TryGetLocalAppDataPath();
+ if (!string.IsNullOrWhiteSpace(interactive))
+ return _cachedLocalAppDataRoot = interactive;
+
+ return _cachedLocalAppDataRoot =
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ }
+}