跳到主要内容

last_error

简介

参考 windows 的 last error 设置, 将错误信息保存到 协程的 local_user_value 中, 方便在协程中获取错误信息, 从而将底层的错误信息暴露给上层

Last Error 模块使用说明文档

概述

last_error 模块是一个专为 GameScript 语言设计的错误处理模块,参考了 Windows 的 GetLastError() 机制。该模块将错误信息保存到协程的本地用户值(local_user_value)中,使得底层的错误信息能够方便地暴露给上层调用者。

主要功能

1. 错误处理

  • 设置错误信息:支持格式化的错误信息设置
  • 获取错误信息:支持获取纯文本错误信息或带堆栈的错误对象
  • 一次性获取:错误信息获取后自动清理,避免重复处理

2. 警告处理

  • 设置警告信息:支持格式化的警告信息设置
  • 获取警告信息:支持获取纯文本警告信息或带堆栈的警告对象
  • 独立于错误:警告和错误信息独立存储和处理

3. 堆栈跟踪

  • 自动堆栈收集:自动收集调用堆栈信息
  • 精确定位:准确定位错误发生的位置
  • 调用链追踪:提供完整的函数调用链信息

4. 配置管理

  • 输出控制:控制错误和警告是否输出到控制台
  • 严格模式:防止错误信息被覆盖而未处理
  • 灵活配置:支持运行时动态调整配置

优缺点分析

优点

  1. 简化错误处理

    • 统一的错误处理机制,避免复杂的异常传递
    • 类似于 Windows GetLastError() 的熟悉模式
    • 支持格式化错误信息,便于调试
  2. 自动堆栈跟踪

    • 自动收集调用堆栈信息
    • 精确定位错误发生位置
    • 便于调试和问题诊断
  3. 灵活配置

    • 可控制错误和警告的输出
    • 支持严格模式防止错误丢失
    • 运行时动态调整配置
  4. 协程支持

    • 基于协程的本地存储, 不需要关心错误的消息的回收问题
    • 支持多协程并发执行
    • 错误信息隔离,避免相互影响
  5. 轻量级设计

    • 简单易用的 API
    • 最小化性能开销
    • 不依赖复杂的异常机制

使用场景

1. 文件操作错误处理

bool read_config_file(string filename)
{
// 模拟文件读取
if (!file_exists(filename)) {
last_error.set_error("配置文件不存在: %s", filename);
return false;
}

if (!has_read_permission(filename)) {
last_error.set_error("没有读取权限: %s", filename);
return false;
}

return true;
}

2. 网络连接错误处理

bool connect_to_server(string host, int port)
{
// 模拟网络连接
if (!network_available()) {
last_error.set_error("网络不可用");
return false;
}

if (!can_reach_host(host, port)) {
last_error.set_error("无法连接到服务器 %s:%d", host, port);
return false;
}

return true;
}

3. 数据库操作错误处理

array query_database(string sql)
{
// 模拟数据库查询
if (!database_connected()) {
last_error.set_error("数据库未连接");
return nil;
}

if (!validate_sql(sql)) {
last_error.set_error("SQL 语句格式错误: %s", sql);
return nil;
}

return execute_query(sql);
}

4. 系统服务错误处理

bool start_service(string service_name)
{
// 模拟服务启动
if (!service_exists(service_name)) {
last_error.set_error("服务不存在: %s", service_name);
return false;
}

if (service_running(service_name)) {
last_error.set_warning("服务 %s 已经在运行", service_name);
return true;
}

if (!start_service_internal(service_name)) {
last_error.set_error("启动服务失败: %s", service_name);
return false;
}

return true;
}

5. 游戏逻辑错误处理

bool load_game_level(string level_name)
{
// 模拟游戏关卡加载
if (!level_file_exists(level_name)) {
last_error.set_error("关卡文件不存在: %s", level_name);
return false;
}

if (!validate_level_format(level_name)) {
last_error.set_error("关卡文件格式错误: %s", level_name);
return false;
}

if (!load_level_resources(level_name)) {
last_error.set_error("加载关卡资源失败: %s", level_name);
return false;
}

return true;
}

最佳实践

  1. 启用严格模式:在开发阶段启用严格模式,确保错误得到及时处理
  2. 及时处理错误:调用可能失败的函数后,立即检查错误状态
  3. 记录堆栈信息:对于关键错误,使用 get_error_with_stack() 获取详细信息
  4. 合理使用警告:对于非致命问题使用警告机制
  5. 配置输出控制:在生产环境中适当控制错误输出,避免信息泄露

总结

last_error 模块提供了一种简单而有效的错误处理机制,特别适合于 GameScript 语言的协程环境。它通过统一的 API 简化了错误处理流程,提供了堆栈跟踪和灵活的配置选项。虽然在功能上有一些限制,但对于大多数应用场景来说,它提供了足够的错误处理能力。

在使用时,建议结合严格模式和及时的错误检查,以确保错误得到妥善处理。对于复杂的错误处理需求,可以考虑在此基础上扩展更多的功能。

API 说明

错误处理 API

set_error(string msg, ...)

设置错误信息,支持格式化字符串。

参数:

  • msg: 错误信息格式字符串
  • ...: 格式字符串的参数

示例:

last_error.set_error("连接失败: 服务器 %s 端口 %d 不可达", "192.168.1.100", 3306);

get_error(coroutine co = nil)

获取错误信息字符串。

参数:

  • co: 协程对象,默认为当前协程

返回值:

  • string: 错误信息字符串,如果没有错误则返回 nil

示例:

string error_msg = last_error.get_error();
if (error_msg) {
write("发生错误: " + error_msg);
}

get_error_with_stack(coroutine co = nil)

获取带堆栈信息的错误对象。

参数:

  • co: 协程对象,默认为当前协程

返回值:

  • LastError: 错误对象,包含错误信息和堆栈信息

示例:

LastError error_obj = last_error.get_error_with_stack();
if (error_obj) {
write("错误消息: " + error_obj.msg);
write("调用堆栈: " + sprintf("%O", error_obj.stack));
}

警告处理 API

set_warning(string msg, ...)

设置警告信息,支持格式化字符串。

参数:

  • msg: 警告信息格式字符串
  • ...: 格式字符串的参数

示例:

last_error.set_warning("配置文件 %s 使用了过时的参数", "config.ini");

get_warning(coroutine co = nil)

获取警告信息字符串。

参数:

  • co: 协程对象,默认为当前协程

返回值:

  • string: 警告信息字符串,如果没有警告则返回 nil

get_warning_with_stack(coroutine co = nil)

获取带堆栈信息的警告对象。

参数:

  • co: 协程对象,默认为当前协程

返回值:

  • LastWarning: 警告对象,包含警告信息和堆栈信息

配置管理 API

set_is_write_error(bool val)

设置是否将错误输出到控制台。

get_is_write_error()

获取是否将错误输出到控制台的状态。

set_is_write_warning(bool val)

设置是否将警告输出到控制台。

get_is_write_warning()

获取是否将警告输出到控制台的状态。

set_is_strict(bool val)

设置是否启用严格模式。启用严格模式后,如果上一个错误没有处理,设置新错误会触发异常。

get_is_strict()

获取是否启用严格模式的状态。

使用示例

基本错误处理

// 设置错误信息
last_error.set_error("文件不存在");

// 获取错误信息
string error_msg = last_error.get_error();
if (error_msg) {
write("发生错误: " + error_msg);
}

格式化错误信息

// 设置格式化的错误信息
last_error.set_error("连接失败: 服务器 %s 端口 %d 不可达", "192.168.1.100", 3306);

// 获取格式化的错误信息
string formatted_error = last_error.get_error();
write("格式化错误: " + formatted_error);

堆栈跟踪

// 设置错误信息
last_error.set_error("数据库查询失败");

// 获取带堆栈信息的错误对象
LastError error_obj = last_error.get_error_with_stack();
if (error_obj) {
write("错误消息: " + error_obj.msg);
write("调用堆栈: " + sprintf("%O", error_obj.stack));
}

严格模式使用

// 启用严格模式
last_error.set_is_strict(true);

// 设置错误
last_error.set_error("第一个错误");

try {
last_error.set_error("第二个错误"); // 这会抛出异常
} catch (e) {
write("捕获到异常: " + e.msg);
}

// 处理错误并清理
string error = last_error.get_error();
last_error.set_is_strict(false);

实际应用场景

bool connect_to_database(string host, int port, string dbname, string password)
{
// 模拟连接失败
if (host == "localhost" && port == 3306) {
last_error.set_error("数据库连接被拒绝: 主机 %s:%d, 数据库 %s", host, port, dbname);
return false;
}
return true;
}

// 使用
if (!connect_to_database("localhost", 3306, "myapp", "password")) {
LastError error = last_error.get_error_with_stack();
write("数据库连接失败!");
write("错误: " + error.msg);
write("堆栈: " + error.stack[0]);
return;
}

组件接口

last_error.gs

函数原型函数作用
void set_is_write_error(bool val)设置是否将错误输出到控制台
bool get_is_write_error()获取是否将错误输出的状态
void set_is_write_warning(bool val)设置是否将警告输出到控制台
bool get_is_write_warning()获取是否将警告输出的状态
void set_is_strict(bool val)设置是否启用严格模式, 启用严格模式后,如果上一个错误没有处理,设置新错误会触发异常
bool get_is_strict()获取是否启用严格模式的状态
void set_error(string msg, ...)设置错误信息
void set_warning(string msg, ...)设置警告信息
LastError get_error_with_stack(coroutine co = nil)获取带堆栈信息的错误对象
string get_error(coroutine co = nil)获取错误信息字符串
LastWarning get_warning_with_stack(coroutine co = nil)获取带堆栈信息的警告对象
string get_warning(coroutine co = nil)获取警告信息字符串

LastError

错误对象

成员变量

变量名类型初始值须初始化描述
msgstringnil可选错误信息
stackarraynil可选堆栈信息

成员方法

函数原型函数作用

LastWarning

警告对象

成员变量

变量名类型初始值须初始化描述
msgstringnil可选警告信息
stackarraynil可选堆栈信息

成员方法

函数原型函数作用

样例

/**
* 基本错误处理示例
* 演示如何设置和获取错误信息
*/
public void basic_error_example()
{
write("=== 基本错误处理示例 ===\n");

// 设置一个简单的错误
last_error.set_error("文件不存在");

// 获取错误信息
string error_msg = last_error.get_error();
if (error_msg) {
write("发生错误: " + error_msg + "\n");
}

// 再次获取错误信息(应该返回 nil)
string error_msg2 = last_error.get_error();
if (error_msg2) {
write("第二次获取错误: " + error_msg2 + "\n");
} else {
write("错误信息已被清理\n");
}
}

/**
* 格式化错误信息示例
* 演示如何使用格式化参数设置错误
*/
public void formatted_error_example()
{
write("\n=== 格式化错误信息示例 ===\n");

// 设置格式化的错误信息
last_error.set_error("连接失败: 服务器 %s 端口 %d 不可达", "192.168.1.100", 3306);

// 获取格式化的错误信息
string formatted_error = last_error.get_error();
write("格式化错误: " + formatted_error + "\n");
}

/**
* 警告处理示例
* 演示如何处理警告信息
*/
public void warning_example()
{
write("\n=== 警告处理示例 ===\n");

// 设置警告信息
last_error.set_warning("配置文件 %s 使用了过时的参数", "config.ini");

// 获取警告信息
string warning_msg = last_error.get_warning();
if (warning_msg) {
write("警告: " + warning_msg + "\n");
}
}

/**
* 堆栈信息示例
* 演示如何获取带堆栈信息的错误对象
*/
public void stack_trace_example()
{
write("\n=== 堆栈信息示例 ===\n");

// 设置错误信息
last_error.set_error("数据库查询失败");

// 获取带堆栈信息的错误对象
LastError error_obj = last_error.get_error_with_stack();
if (error_obj) {
write("错误消息: " + error_obj.msg + "\n");
write("调用堆栈: " + sprintf("%O", error_obj.stack) + "\n");
}
}

/**
* 配置管理示例
* 演示如何控制错误和警告的输出
*/
public void config_management_example()
{
write("\n=== 配置管理示例 ===\n");

// 显示当前配置
write("当前错误输出状态: " + (last_error.get_is_write_error() ? "开启" : "关闭") + "\n");
write("当前警告输出状态: " + (last_error.get_is_write_warning() ? "开启" : "关闭") + "\n");
write("当前严格模式状态: " + (last_error.get_is_strict() ? "开启" : "关闭") + "\n");

// 关闭错误输出
last_error.set_is_write_error(false);
last_error.set_error("这个错误不会输出到控制台");
string silent_error = last_error.get_error();
write("静默错误: " + silent_error + "\n");

// 重新开启错误输出
last_error.set_is_write_error(true);
write("错误输出已重新开启\n");
}

/**
* 综合示例:文件操作错误处理
* 演示在实际场景中如何使用 last_error 模块
*/
public void file_operation_example()
{
write("\n=== 文件操作错误处理示例 ===\n");

// 模拟文件读取操作
if (!simulate_file_read("/nonexistent/file.txt")) {
LastError error = last_error.get_error_with_stack();
if (error) {
write("文件读取失败!\n");
write("错误详情: " + error.msg + "\n");
write("发生位置: " + error.stack[0] + "\n");
}
}

// 模拟文件写入操作
if (!simulate_file_write("/readonly/file.txt", "test data")) {
string error_msg = last_error.get_error();
if (error_msg) {
write("文件写入失败: " + error_msg + "\n");
}
}
}

/**
* 模拟文件读取函数
*/
bool simulate_file_read(string filename)
{
// 模拟文件不存在的情况
if (filename == "/nonexistent/file.txt") {
last_error.set_error("文件不存在: %s", filename);
return false;
}
return true;
}

/**
* 模拟文件写入函数
*/
bool simulate_file_write(string filename, string data)
{
// 模拟权限不足的情况
if (filename == "/readonly/file.txt") {
last_error.set_error("权限不足: 无法写入文件 %s", filename);
return false;
}
return true;
}

/**
* 主示例函数 - 运行所有示例
*/
public void pkg_sample()
{
write("Last Error 模块使用示例\n");
write("========================\n");

// 运行所有示例
basic_error_example();
formatted_error_example();
warning_example();
stack_trace_example();
config_management_example();
file_operation_example();

write("\n示例运行完成!\n");
}