Отладка скриптов

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

  1. Production: Отключайте детальную отладку в релизе
  2. История: Ограничивайте размер истории ошибок
  3. Логирование: Используйте структурированное логирование
  4. Telemetry: Отправляйте ошибки в систему мониторинга
  5. User-friendly: Показывайте понятные сообщения пользователям
  6. Context: Сохраняйте максимум контекста для диагностики