API
日志
TEN framework 允许使用不同语言开发的 extension 在同一进程中运行。这使得需要以统一的日志格式和信息在统一的日志中查看所有这些 extension 的日志,从而简化调试。
为了解决这个问题,ten_env 提供了一个日志 API。在 extension 的每个回调中,都可以访问 ten_env 的实例。使用此实例,您可以调用日志 API,将使用各种语言开发的 extension 的日志输出到统一的日志输出中。
日志级别
TEN framework 支持以下日志级别,按严重程度递增:
- DEBUG (1): 调试信息
- INFO (2): 一般信息
- WARN (3): 警告信息
- ERROR (4): 错误信息
API 接口
C++ 语言
#include "ten_runtime/binding/cpp/ten.h"
class Extension {
public:
void on_cmd(ten::ten_env_t& ten_env, std::unique_ptr<ten::cmd_t> cmd) {
// 使用便捷宏
TEN_ENV_LOG_DEBUG(ten_env, "Processing command");
TEN_ENV_LOG_INFO(ten_env, "Command name: " + cmd->get_name());
TEN_ENV_LOG_WARN(ten_env, "Warning message");
TEN_ENV_LOG_ERROR(ten_env, "Error occurred");
// 直接调用方法,支持 category 和 fields 参数
ten_env.log(TEN_LOG_LEVEL_INFO, __func__, __FILE__, __LINE__,
"Direct log call", nullptr, nullptr);
}
};C++ 提供了以下便捷宏:
TEN_ENV_LOG_DEBUG(ten_env, msg)TEN_ENV_LOG_INFO(ten_env, msg)TEN_ENV_LOG_WARN(ten_env, msg)TEN_ENV_LOG_ERROR(ten_env, msg)
Python 语言
from ten_runtime import Extension, TenEnv
class MyExtension(Extension):
def on_cmd(self, ten_env: TenEnv, cmd):
# 使用不同级别的日志方法
ten_env.log_debug("调试信息")
ten_env.log_info("一般信息")
ten_env.log_warn("警告信息")
ten_env.log_error("错误信息")
# 支持格式化字符串和额外参数
test_value = "example"
ten_env.log_info(f"处理命令,测试值: {test_value}")
# 支持 category 和 fields 参数
ten_env.log_info("带分类的日志", category="my_extension")Python 提供了以下方法:
log_debug(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]log_info(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]log_warn(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]log_error(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]
Go 语言
package main
import "ten_runtime/ten"
type MyExtension struct {
ten.DefaultExtension
}
func (ext *MyExtension) OnCmd(tenEnv ten.TenEnv, cmd ten.Cmd) {
// 使用不同级别的日志方法
tenEnv.LogDebug("调试信息")
tenEnv.LogInfo("一般信息")
tenEnv.LogWarn("警告信息")
tenEnv.LogError("错误信息")
// 支持格式化
cmdName := cmd.GetName()
tenEnv.LogInfo("处理命令: " + cmdName)
// 使用完整的 Log 方法支持更多参数
category := "my_extension"
tenEnv.Log(ten.LogLevelInfo, "带分类的日志", &category, nil, nil)
}Go 提供了以下方法:
LogDebug(msg string) errorLogInfo(msg string) errorLogWarn(msg string) errorLogError(msg string) errorLog(level LogLevel, msg string, category *string, fields *Value, option *TenLogOption) error
Node.js/TypeScript 语言
import { Extension, TenEnv, Cmd } from "ten-runtime-nodejs";
class MyExtension extends Extension {
async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
// 使用不同级别的日志方法
tenEnv.logDebug("调试信息");
tenEnv.logInfo("一般信息");
tenEnv.logWarn("警告信息");
tenEnv.logError("错误信息");
// 支持字符串拼接
const cmdName = cmd.getName();
tenEnv.logInfo("处理命令: " + cmdName);
// 支持 category 和 fields 参数
tenEnv.logInfo("带分类的日志", "my_extension");
}
}Node.js 提供了以下方法:
logDebug(message: string, category?: string, fields?: Value): TenError | undefinedlogInfo(message: string, category?: string, fields?: Value): TenError | undefinedlogWarn(message: string, category?: string, fields?: Value): TenError | undefinedlogError(message: string, category?: string, fields?: Value): TenError | undefined
使用示例
基本使用
# Python 示例
def on_start(self, ten_env: TenEnv):
ten_env.log_info("extension 启动")
ten_env.on_start_done()
def on_cmd(self, ten_env: TenEnv, cmd):
cmd_name = cmd.get_name()
ten_env.log_debug(f"收到命令: {cmd_name}")
# 处理命令逻辑
if cmd_name == "hello":
ten_env.log_info("处理 hello 命令", category="command_handler")
# ... 处理逻辑
else:
ten_env.log_warn(f"未知命令: {cmd_name}")在线程中使用
import threading
import time
def log_routine(self, ten_env: TenEnv):
"""在单独线程中记录日志"""
while not self.stop_flag:
self.log_count += 1
ten_env.log_info(f"日志消息 {self.log_count}")
time.sleep(0.05)
def on_start(self, ten_env: TenEnv):
# 启动日志线程
self.log_thread = threading.Thread(
target=self.log_routine, args=(ten_env,)
)
self.log_thread.start()
ten_env.on_start_done()错误处理
def on_cmd(self, ten_env: TenEnv, cmd):
try:
# 业务逻辑
result = self.process_command(cmd)
ten_env.log_info("命令处理成功")
except ValueError as e:
ten_env.log_error(f"参数错误: {str(e)}", category="error_handling")
except Exception as e:
ten_env.log_error(f"严重错误: {str(e)}", category="error_handling")
raise日志格式和输出
TEN framework 的日志系统会自动包含以下信息:
- 时间戳: 日志记录的时间
- 日志级别: DEBUG、INFO、WARN、ERROR
- 函数名: 调用日志的函数名(如果可用)
- 文件名: 调用日志的源文件名(如果可用)
- 行号: 调用日志的代码行号(如果可用)
- 分类: 可选的日志分类信息
- 消息内容: 用户提供的日志消息
输出格式类似:
2025-01-01 12:00:00.123 [INFO] [function_name@file.py:123] [category] 用户日志消息高级日志配置
TEN framework 支持通过 property.json 文件配置高级日志功能。这允许您自定义日志的输出格式、目标和过滤规则。
配置结构
在 property.json 文件中,您可以在 ten.log 部分定义高级日志配置:
{
"ten": {
"log": {
"handlers": [
{
"matchers": [
{
"level": "debug",
"category": "my_extension"
}
],
"formatter": {
"type": "plain",
"colored": true
},
"emitter": {
"type": "console",
"config": {
"stream": "stdout"
}
}
}
]
}
}
}配置组件说明
匹配器 (Matchers)
匹配器用于过滤日志消息:
{
"level": "info", // 日志级别: "off", "debug", "info", "warn", "error"
"category": "auth" // 可选:日志分类(字符串)
}支持的日志级别:
"off": 关闭日志"debug": 调试级别"info": 信息级别"warn": 警告级别"error": 错误级别
格式器 (Formatter)
控制日志的输出格式:
{
"type": "plain", // 格式类型: "plain" 或 "json"
"colored": true // 可选:是否启用颜色输出(仅对 plain 格式有效)
}- plain: 人类可读的纯文本格式
- json: JSON 结构化格式,便于日志分析工具处理
发射器 (Emitter)
定义日志的输出目标:
控制台输出
{
"type": "console",
"config": {
"stream": "stdout", // "stdout" 或 "stderr"
"encryption": { // 可选:加密配置
"enabled": true,
"algorithm": "AES-CTR",
"params": {
"key": "your-encryption-key",
"nonce": "your-nonce"
}
}
}
}文件输出
{
"type": "file",
"config": {
"path": "/path/to/logfile.log", // 日志文件路径
"encryption": { // 可选:加密配置
"enabled": true,
"algorithm": "AES-CTR",
"params": {
"key": "your-encryption-key",
"nonce": "your-nonce"
}
}
}
}OTLP 输出
OTLP (OpenTelemetry Protocol) 输出允许您将日志发送到支持 OpenTelemetry 的后端系统,如 Jaeger、Prometheus、Grafana 等。
{
"type": "otlp",
"config": {
"endpoint": "http://localhost:4317", // OTLP 接收端点地址
"protocol": "grpc", // 可选:传输协议,"grpc"(默认)或 "http"
"headers": { // 可选:自定义 HTTP 头
"Authorization": "Bearer token",
"X-Custom-Header": "value"
},
"service_name": "my-service" // 可选:服务名称,用于标识日志来源
}
}配置参数说明:
- endpoint: OTLP 接收端点的 URL(必需)
- protocol: 传输协议,支持
"grpc"或"http",默认为"grpc" - headers: 自定义 HTTP 请求头,用于认证或其他元数据传递(可选)
- service_name: 服务名称,用于在日志聚合系统中标识不同的服务(可选)
配置示例
示例 1: 多级别日志分离
{
"ten": {
"log": {
"handlers": [
{
"matchers": [
{
"level": "error"
}
],
"formatter": {
"type": "json"
},
"emitter": {
"type": "file",
"config": {
"path": "/var/log/app/errors.log"
}
}
},
{
"matchers": [
{
"level": "info"
}
],
"formatter": {
"type": "plain",
"colored": true
},
"emitter": {
"type": "console",
"config": {
"stream": "stdout"
}
}
}
]
}
}
}示例 2: 按分类分离日志
{
"ten": {
"log": {
"handlers": [
{
"matchers": [
{
"level": "debug",
"category": "database"
}
],
"formatter": {
"type": "json"
},
"emitter": {
"type": "file",
"config": {
"path": "/var/log/app/database.log"
}
}
},
{
"matchers": [
{
"level": "info",
"category": "api"
}
],
"formatter": {
"type": "plain"
},
"emitter": {
"type": "console",
"config": {
"stream": "stdout"
}
}
}
]
}
}
}示例 3: 加密日志输出
{
"ten": {
"log": {
"handlers": [
{
"matchers": [
{
"level": "info"
}
],
"formatter": {
"type": "json"
},
"emitter": {
"type": "file",
"config": {
"path": "/secure/logs/app.log",
"encryption": {
"enabled": true,
"algorithm": "AES-CTR",
"params": {
"key": "your-32-character-encryption-key",
"nonce": "your-16-character-nonce"
}
}
}
}
}
]
}
}
}示例 4: OTLP 日志输出到 OpenTelemetry 收集器
{
"ten": {
"log": {
"handlers": [
{
"matchers": [
{
"level": "info"
}
],
"formatter": {
"type": "json"
},
"emitter": {
"type": "otlp",
"config": {
"endpoint": "http://localhost:4317",
"protocol": "grpc",
"service_name": "ten-framework-app"
}
}
},
{
"matchers": [
{
"level": "error"
}
],
"formatter": {
"type": "json"
},
"emitter": {
"type": "otlp",
"config": {
"endpoint": "https://api.monitoring.example.com/v1/logs",
"protocol": "http",
"headers": {
"Authorization": "Bearer your-api-token",
"X-Environment": "production"
},
"service_name": "ten-framework-app"
}
}
}
]
}
}
}配置注意事项
- 多个处理器: 可以定义多个处理器来实现复杂的日志路由策略
- 匹配优先级: 按照配置文件中的顺序匹配处理器
- 加密安全: 加密密钥和 nonce 应该安全存储,不要硬编码在配置文件中
- 文件权限: 确保日志文件路径具有适当的写入权限
- 性能考虑: JSON 格式可能会比 plain 格式产生更大的日志文件
- OTLP 端点:
- 使用 gRPC 协议时,默认端口通常为 4317
- 使用 HTTP 协议时,默认端口通常为 4318
- 确保 OTLP 收集器端点可访问且配置正确
- 认证: 使用 OTLP 时,如果后端需要认证,请在 headers 中添加相应的认证信息
最佳实践
-
选择合适的日志级别:
- 使用
DEBUG记录详细的调试信息 - 使用
INFO记录正常的业务流程 - 使用
WARN记录潜在问题 - 使用
ERROR记录错误信息
- 使用
-
提供有意义的日志消息:
- 包含上下文信息(如命令名称、参数值等)
- 使用描述性的消息而不是简单的标识符
- 合理使用 category 参数对日志进行分类
-
避免过度日志记录:
- 不要在高频循环中使用过多的日志
- 在生产环境中适当调整日志级别
- 使用高级日志配置的匹配器来过滤不必要的日志
-
线程安全:
- TEN framework 的日志 API 是线程安全的
- 可以在任何线程中安全调用日志方法
-
高级配置建议:
- 为不同的模块使用不同的 category 便于日志分析
- 在生产环境中考虑使用文件输出以便于日志持久化
- 对敏感日志使用加密功能
- 定期轮转日志文件以控制磁盘使用
- 使用 JSON 格式便于与日志分析工具集成
- 考虑使用 OTLP 输出将日志集成到现代可观测性平台(如 Grafana、Jaeger、Datadog 等)
- OTLP 输出与 OpenTelemetry 生态系统无缝集成,便于实现分布式追踪和日志关联
整体效果如下图所示:

在 GitHub 上编辑
最后更新