Danro-Jump/Assets/Scripts/Runtime/Debugging/FileDebugLogHandler.cs
Danil Iarosh 77186697f8 Prepare v0.1 gameplay and hardware foundation
Подробные изменения:

- Расширен основной gameplay loop: джетпак, новые платформы, оборудование, враги, прогрессия сложности и ограничения взаимодействий игрока.

- Добавлены Bootstrap-сервисы запуска приложения, защита, фокус окна, управление курсором и настройки сервисов через DI.

- Добавлена COM-интеграция с технической схемой Input System, обработкой кнопок/ADC, подсветкой и консольной диагностикой.

- Добавлен отдельный debug/logging модуль с каналами, обработчиками консоли и файловым выводом.

- Добавлены asmdef, link.xml, EditMode/PlayMode тесты и обновлена дорожная карта проекта.

Проверки:

- dotnet build .\\DanroJump.Runtime.csproj --no-restore

- dotnet build .\\DanroJump.Tests.EditMode.csproj --no-restore

- dotnet build .\\DanroJump.Tests.PlayMode.csproj --no-restore
2026-06-08 14:22:48 +03:00

132 lines
4.1 KiB
C#

using System;
using System.Globalization;
using System.IO;
using System.Text;
using UnityEngine;
using Zenject;
namespace DanroJump.Debugging
{
/// <summary>
/// Дополнительный обработчик, который сохраняет отладочные сообщения в файл.
/// </summary>
public sealed class FileDebugLogHandler : IDebugLogHandler, IInitializable, IDisposable
{
private readonly IDebugLogService debugLogService;
private readonly DebugFileLogOptions options;
private readonly object writerLock = new();
private StreamWriter writer;
private int entriesSinceFlush;
private bool isFaulted;
public FileDebugLogHandler(IDebugLogService debugLogService, DebugFileLogOptions options)
{
this.debugLogService = debugLogService;
this.options = options;
}
public string FilePath { get; private set; } = "";
public void Initialize()
{
if (!options.IsEnabled)
{
return;
}
try
{
FilePath = options.ResolveFilePath();
string directoryPath = Path.GetDirectoryName(FilePath);
if (!string.IsNullOrEmpty(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
var stream = new FileStream(
FilePath,
options.AppendToFile ? FileMode.Append : FileMode.Create,
FileAccess.Write,
FileShare.ReadWrite);
writer = new StreamWriter(stream, new UTF8Encoding(false));
if (options.WriteSessionHeader)
{
writer.WriteLine($"# Danro Jump log session started {DateTime.UtcNow:O}");
writer.Flush();
}
debugLogService.AddHandler(this);
Debug.Log($"[Debug] File log handler writes to: {FilePath}");
}
catch (Exception exception)
{
isFaulted = true;
Debug.LogWarning($"[Debug] Failed to initialize file log handler: {exception.Message}");
}
}
public void Dispose()
{
debugLogService.RemoveHandler(this);
lock (writerLock)
{
writer?.Flush();
writer?.Dispose();
writer = null;
}
}
public void Handle(DebugLogEntry entry)
{
if (isFaulted)
{
return;
}
try
{
lock (writerLock)
{
if (writer == null)
{
return;
}
writer.WriteLine(FormatEntry(entry));
entriesSinceFlush++;
if (entriesSinceFlush >= options.FlushEveryEntries)
{
writer.Flush();
entriesSinceFlush = 0;
}
}
}
catch (Exception exception)
{
isFaulted = true;
Debug.LogWarning($"[Debug] File log handler disabled after write failure: {exception.Message}");
}
}
private static string FormatEntry(DebugLogEntry entry)
{
string timestamp = entry.TimestampUtc.ToString("O", CultureInfo.InvariantCulture);
string contextName = entry.Context == null ? "" : entry.Context.name;
return string.IsNullOrEmpty(contextName)
? $"{timestamp} [{entry.Level}] [{entry.Channel}] {Normalize(entry.Message)}"
: $"{timestamp} [{entry.Level}] [{entry.Channel}] {Normalize(entry.Message)} | context={Normalize(contextName)}";
}
private static string Normalize(string value)
{
return string.IsNullOrEmpty(value)
? ""
: value.Replace("\r", "\\r").Replace("\n", "\\n");
}
}
}