Отладка скриптов
IgdrasilScripting предоставляет мощные инструменты для отладки скриптов через IScriptDebugger и специализированные исключения.
Отладчик скриптов
Получение отладчика
var engine = new LuaEngine();
var debugger = engine.Debugger;
if (debugger != null)
{
debugger.IsEnabled = true;
}
Включение отладки
// Включить режим отладки
debugger.IsEnabled = true;
// Выполнить скрипт с отладочной информацией
engine.Execute("print('Hello')");
// Отключить отладку
debugger.IsEnabled = false;
История ошибок
// Получить последнюю ошибку
var lastError = debugger.LastError;
if (lastError != null)
{
Console.WriteLine($"Тип: {lastError.ErrorType}");
Console.WriteLine($"Сообщение: {lastError.Message}");
Console.WriteLine($"Строка: {lastError.LineNumber}");
Console.WriteLine($"Колонка: {lastError.ColumnNumber}");
Console.WriteLine($"Время: {lastError.OccurredAt}");
if (!string.IsNullOrEmpty(lastError.StackTrace))
{
Console.WriteLine($"Stack Trace:\n{lastError.StackTrace}");
}
}
// Получить всю историю ошибок
foreach (var error in debugger.ErrorHistory)
{
Console.WriteLine($"[{error.ErrorType}] {error.Message} at line {error.LineNumber}");
}
// Очистить историю
debugger.ClearHistory();
События ошибок
// Подписаться на события ошибок
debugger.OnScriptError += (sender, args) =>
{
var error = args.Error;
Console.WriteLine($"Script Error: {error.Message}");
Console.WriteLine($"Type: {error.ErrorType}");
Console.WriteLine($"Line: {error.LineNumber}");
// Можно пометить как обработанную
args.Handled = true;
// Логирование
LogError(error);
// Отправка в телеметрию
SendToTelemetry(error);
};
// Выполнить скрипт с ошибкой
try
{
engine.Execute("invalid code");
}
catch
{
// Событие уже вызвано
}
Обработка исключений
ScriptCompilationException
using IgdrasilEngine.Engine.Scripting.Exceptions;
try
{
engine.Compile("function test("); // Незавершенная функция
}
catch (ScriptCompilationException ex)
{
Console.WriteLine($"Ошибка компиляции:");
Console.WriteLine($" Сообщение: {ex.Message}");
Console.WriteLine($" Строка: {ex.LineNumber}");
Console.WriteLine($" Колонка: {ex.ColumnNumber}");
Console.WriteLine($" Диагностика: {ex.DiagnosticInfo}");
// Показать исходный код с ошибкой
if (!string.IsNullOrEmpty(ex.ScriptSource))
{
ShowSourceWithError(ex.ScriptSource, ex.LineNumber ?? 0, ex.ColumnNumber ?? 0);
}
}
ScriptExecutionException
try
{
engine.Execute(@"
local x = 10
local y = 0
local result = x / y -- Деление на ноль
");
}
catch (ScriptExecutionException ex)
{
Console.WriteLine($"Ошибка выполнения:");
Console.WriteLine($" Сообщение: {ex.Message}");
Console.WriteLine($" Строка: {ex.LineNumber}");
// Контекст выполнения
if (ex.Context != null)
{
Console.WriteLine($" Время выполнения: {ex.Context.ElapsedMilliseconds}ms");
Console.WriteLine($" Локальные переменные:");
foreach (var (name, value) in ex.Context.LocalVariables)
{
Console.WriteLine($" {name} = {value}");
}
if (!string.IsNullOrEmpty(ex.Context.StackTrace))
{
Console.WriteLine($" Stack Trace:\n{ex.Context.StackTrace}");
}
}
}
Создание отладчика
public class CustomScriptDebugger : IScriptDebugger
{
private readonly List<ScriptError> _errorHistory = new();
private readonly int _maxHistorySize = 100;
public bool IsEnabled { get; set; }
public ScriptError? LastError { get; private set; }
public IReadOnlyList<ScriptError> ErrorHistory => _errorHistory;
public event EventHandler<ScriptErrorEventArgs>? OnScriptError;
public void LogDebugInfo(string message)
{
if (IsEnabled)
{
Console.WriteLine($"[DEBUG] {DateTime.Now:HH:mm:ss.fff} - {message}");
}
}
public void ClearHistory()
{
_errorHistory.Clear();
LastError = null;
}
public void RecordError(ScriptError error)
{
if (!IsEnabled)
return;
LastError = error;
_errorHistory.Add(error);
// Ограничение размера истории
if (_errorHistory.Count > _maxHistorySize)
{
_errorHistory.RemoveAt(0);
}
// Вызов события
var args = new ScriptErrorEventArgs { Error = error };
OnScriptError?.Invoke(this, args);
// Если не обработано, логировать
if (!args.Handled)
{
LogDebugInfo($"Unhandled error: {error.Message}");
}
}
}
Интеграция с движком
public class DebugLuaEngine : LuaEngine
{
private readonly CustomScriptDebugger _debugger = new();
public override IScriptDebugger? Debugger => _debugger;
public override IScript<object[]> Compile(string script)
{
_debugger.LogDebugInfo($"Компиляция скрипта ({script.Length} символов)");
try
{
var compiled = base.Compile(script);
_debugger.LogDebugInfo("Компиляция успешна");
return compiled;
}
catch (Exception ex)
{
_debugger.RecordError(new ScriptError
{
Message = ex.Message,
ScriptSource = script,
ErrorType = ScriptErrorType.Compilation,
OccurredAt = DateTime.UtcNow
});
throw;
}
}
public override object[] Evaluate(string script)
{
_debugger.LogDebugInfo("Начало выполнения скрипта");
var sw = Stopwatch.StartNew();
try
{
var result = base.Evaluate(script);
sw.Stop();
_debugger.LogDebugInfo($"Выполнение завершено за {sw.ElapsedMilliseconds}ms");
return result;
}
catch (Exception ex)
{
sw.Stop();
_debugger.RecordError(new ScriptError
{
Message = ex.Message,
ScriptSource = script,
ErrorType = ScriptErrorType.Runtime,
OccurredAt = DateTime.UtcNow,
StackTrace = ex.StackTrace
});
throw;
}
}
}
Визуализация ошибок
public static class ErrorVisualizer
{
public static void ShowSourceWithError(string source, int lineNumber, int columnNumber)
{
var lines = source.Split('\n');
var startLine = Math.Max(0, lineNumber - 3);
var endLine = Math.Min(lines.Length - 1, lineNumber + 2);
Console.WriteLine("Контекст ошибки:");
Console.WriteLine(new string('-', 60));
for (int i = startLine; i <= endLine; i++)
{
var marker = i == lineNumber - 1 ? ">>>" : " ";
Console.WriteLine($"{marker} {i + 1,4}: {lines[i]}");
if (i == lineNumber - 1 && columnNumber > 0)
{
var spaces = new string(' ', columnNumber + 8);
Console.WriteLine($"{spaces}^--- Здесь");
}
}
Console.WriteLine(new string('-', 60));
}
public static void PrintErrorReport(ScriptError error)
{
Console.WriteLine();
Console.WriteLine("╔════════════════════════════════════════════════════════╗");
Console.WriteLine("║ SCRIPT ERROR REPORT ║");
Console.WriteLine("╠════════════════════════════════════════════════════════╣");
Console.WriteLine($"║ Type: {error.ErrorType,-43} ║");
Console.WriteLine($"║ Time: {error.OccurredAt:yyyy-MM-dd HH:mm:ss} ║");
if (error.LineNumber.HasValue)
Console.WriteLine($"║ Line: {error.LineNumber,-43} ║");
if (error.ColumnNumber.HasValue)
Console.WriteLine($"║ Column: {error.ColumnNumber,-43} ║");
Console.WriteLine("╠════════════════════════════════════════════════════════╣");
Console.WriteLine($"║ Message: ║");
var messageLines = WrapText(error.Message, 52);
foreach (var line in messageLines)
{
Console.WriteLine($"║ {line,-52} ║");
}
Console.WriteLine("╚════════════════════════════════════════════════════════╝");
}
private static List<string> WrapText(string text, int maxWidth)
{
var lines = new List<string>();
var words = text.Split(' ');
var currentLine = "";
foreach (var word in words)
{
if (currentLine.Length + word.Length + 1 <= maxWidth)
{
currentLine += (currentLine.Length > 0 ? " " : "") + word;
}
else
{
if (currentLine.Length > 0)
lines.Add(currentLine);
currentLine = word;
}
}
if (currentLine.Length > 0)
lines.Add(currentLine);
return lines;
}
}
Логирование отладочной информации
public class DebugLogger
{
private readonly IScriptDebugger _debugger;
private readonly string _logFilePath;
public DebugLogger(IScriptDebugger debugger, string logFilePath)
{
_debugger = debugger;
_logFilePath = logFilePath;
_debugger.OnScriptError += OnError;
}
private void OnError(object? sender, ScriptErrorEventArgs e)
{
var logEntry = new StringBuilder();
logEntry.AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Script Error");
logEntry.AppendLine($"Type: {e.Error.ErrorType}");
logEntry.AppendLine($"Message: {e.Error.Message}");
if (e.Error.LineNumber.HasValue)
logEntry.AppendLine($"Line: {e.Error.LineNumber}");
if (!string.IsNullOrEmpty(e.Error.StackTrace))
logEntry.AppendLine($"Stack Trace:\n{e.Error.StackTrace}");
logEntry.AppendLine(new string('-', 80));
File.AppendAllText(_logFilePath, logEntry.ToString());
}
}
Best Practices
- Production: Отключайте детальную отладку в релизе
- История: Ограничивайте размер истории ошибок
- Логирование: Используйте структурированное логирование
- Telemetry: Отправляйте ошибки в систему мониторинга
- User-friendly: Показывайте понятные сообщения пользователям
- Context: Сохраняйте максимум контекста для диагностики