UI-TARS-desktop 同构日志组件:@agent-infra/logger 源码解析与实战指南
@agent-infra/logger 是 UI-TARS-desktop 开源仓库 packages/agent-infra/logger 中面向 Agent Tars 研发的轻量级同构日志库:同一套 API 在 Node.js 终端与浏览器 Console 中都能输出带颜色的分级日志。本文以该组件的官方文档为主体,结合其完整源码逐层拆解 API 设计、颜色输出原理、等级过滤机制与分层日志组织方式,帮助你在自己的 Agent 应用、CLI 工具或 Electron 主进程/渲染进程中快速落地一套一致的日志基建。
它解决什么问题
Agent 类应用通常横跨多类运行时:模型工具调用引擎跑在 Node.js 服务端,可视化界面跑在浏览器渲染进程,桌面端(如 Electron 的 main/renderer)则二者兼具。如果直接使用 console 原生 API,会出现"两端表现不一致、日志前缀混乱、难以按模块分级开关、敏感数据不便脱敏"等问题。@agent-infra/logger 用极小的 API 面把这些诉求收敛为一套 Logger 接口契约,让"写日志"这件事在两端拥有完全一致的心智模型。
特性总览
根据 README 与源码实现,该库具备如下特性:
- Isomorphic(同构):Node.js 与浏览器环境无缝工作,同一段代码无需分支;
- Colorful Output(彩色输出):按平台自动适配 ANSI 转义码(终端)与
%c内联样式(浏览器 Console); - Hierarchical Logging(分级嵌套):通过
spawn创建带前缀的嵌套子 Logger; - Data Handling(结构化数据):记录结构化数据并支持可选的脱敏/变换函数;
- Zero Dependencies(零运行时依赖):不引入任何外部运行时依赖(其 package.json 仅含
@types/node、typescript、@rslib/core三个开发依赖); - TypeScript Support:完整类型定义随包发布;
- Log Levels(日志级别):细粒度控制输出详细程度。
安装
作为发布在 npm 上的独立包,可在任意 Node.js / 前端项目中安装:
npm install @agent-infra/logger
# 或
yarn add @agent-infra/logger
# 或
pnpm add @agent-infra/logger
包的模块格式由 package.json 的 exports 字段提供:import 指向 ./dist/index.mjs(ESM)、require 指向 ./dist/index.js(CJS),类型声明位于 ./dist/index.d.ts。构建产物由 rslib.config.ts 中的 defineConfig 一次性产出 esm 与 cjs 双格式,并开启 dts 与 sourceMap。
快速上手
组件全部对外导出位于 src/index.ts:它 export * 了 types.ts 与 console-logger.ts,并单独导出 colorize、colorLog。最小可用示例:
import { ConsoleLogger } from '@agent-infra/logger';
// 带前缀创建 logger
const logger = new ConsoleLogger('[App]');
// 基础日志
logger.info('Server started');
logger.warn('Resource usage high');
logger.error('Database connection failed');
logger.success('Task completed successfully');
// 携带结构化数据输出,可选 transformer 对敏感字段脱敏
logger.infoWithData(
'User data:',
{ id: 1, name: 'John', email: 'john@example.com' },
// 可选:在输出前变换数据,例如遮盖邮箱
(user) => ({ ...user, email: '***@example.com' }),
);
// 针对某组件创建子 logger
const dbLogger = logger.spawn('Database');
dbLogger.info('Connected to database'); // 输出: [App:Database] Connected to database
仓库 examples/node.js 给出了可直接运行的 ESM 版本示例(需先执行 npm run build 生成 dist/index.mjs);examples/browser.html 则提供了浏览器端交互演示页面——页面上的按钮分别触发 info/warn/error/success、结构化数据与 spawn 日志,在浏览器 DevTools Console 中即可看到带前缀与颜色的输出。
API Reference
Logger 接口
Logger 是所有实现必须满足的核心契约,定义见 types.ts。注意在源码中 info、warn、error、debug 的签名是变长的 (...args: any[]),文档将其抽象为单 message 以便阅读:
| 方法 | 说明 |
|---|---|
log(...args: any[]) |
基础日志,等价于 console.log |
info(message: string) |
信息日志(蓝色前缀) |
warn(message: string) |
警告日志(黄色前缀) |
error(message: string) |
错误日志(红色前缀) |
success(message: string) |
成功日志(绿色前缀) |
infoWithData<T>(message, data?, transformer?) |
输出消息及关联结构化数据,支持可选变换函数 |
spawn(prefix: string) |
创建带额外前缀的新 Logger 实例 |
setLevel(level: LogLevel) |
设置当前日志级别 |
getLevel() |
获取当前日志级别 |
接口中同样包含 debug(...args) 方法(文档表格未单独列出,但源码契约中存在),它与 log 一样仅在 DEBUG 级别下输出。
LogLevel
级别以"严重程度升序"的整数值定义(types.ts):
enum LogLevel {
DEBUG = 0, // 最详细,包含所有日志
INFO = 1, // 常规信息消息
SUCCESS = 2, // 成功消息与操作
WARN = 3, // 警告消息
ERROR = 4, // 错误消息
SILENT = 5 // 不显示任何日志
}
级别判定采用数值比较:当 logger 的当前级别数值 <= 方法的等级数值时才输出。因此把级别调到 WARN(3)后,DEBUG(0)/INFO(1)/SUCCESS(2) 均被屏蔽,只有 WARN(3)/ERROR(4) 会输出;SILENT(5) 则能让任何级别都无法通过比较。
defaultLogger 与 BaseLogger
types.ts 中定义了 BaseLogger —— 实现了 Logger 接口但所有方法均为空操作的基类(spawn 返回新的 BaseLogger,getLevel 恒返回 INFO)。defaultLogger 即 new BaseLogger() 的实例:一个"实现了接口但什么都不做"的空 logger,适合作为可注入依赖的默认值,避免上层代码在未提供 logger 时因空引用崩溃。源码注释明确它是 ConsoleLogger 的继承基类:ConsoleLogger extends BaseLogger。
ConsoleLogger
主实现类,提供带颜色的控制台输出,详见 console-logger.ts。其构造函数签名为 new ConsoleLogger(prefix = '', level: LogLevel = LogLevel.INFO)——前缀可空、默认日志级别为 INFO。
深入源码:同构与着色原理
该库最核心的技术点是"一套日志代码,两种运行时呈现",理解它要从颜色系统与运行时探测入手。
颜色系统抽象
colorize.ts 维护了两套颜色映射:
- ANSI_COLORS:终端使用的转义序列,例如
red: '\x1b[31m'、reset: '\x1b[0m',样式另有bold: '\x1b[1m'; - CSS_COLOR_VALUES:浏览器
%c格式化所需的内联 CSS 颜色值(如blue: '#3B82F6'),以具名导出形式暴露,可通过Object.assign覆盖。
颜色名由 ColorName 联合类型约束(black/red/green/yellow/blue/magenta/cyan/white/gray/reset),文字样式为 TextStyle = 'bold' | 'normal'(见 types.ts)。
运行时探测与色彩支持判定
colorize.ts 顶部执行两次探测:
- 浏览器判定:
typeof window !== 'undefined' && typeof window.document !== 'undefined'; - 色彩支持判定(仅对 Node 有效):
const supportsColor =
!isBrowser &&
!('NO_COLOR' in process.env || process.env.FORCE_COLOR === '0') &&
(process.env.FORCE_COLOR !== undefined || process.stdout?.isTTY);
这意味着:终端输出颜色会尊重社区通用的 NO_COLOR 环境变量(设置后自动降级为纯文本)、支持 FORCE_COLOR 强制开启,并且只有在 TTY(交互终端)下才附加 ANSI 码——日志重定向到文件时不会混入转义序列,这保证了 CI 流水线与日志文件的可读性。
colorize() 函数的具体行为为:浏览器环境直接返回原文本(真正的着色延迟到 log 方法用 %c 完成);Node 环境在支持色彩时用 ANSI 码包裹文本并 reset,不支持时原样返回。
ConsoleLogger 的分级着色与双端分支
ConsoleLogger.colorPrefix 将日志类型映射为前缀颜色:info→blue、warn→yellow、error→red、debug→gray、success→green。在浏览器环境它会先把颜色记录到 lastPrefixColor 并返回纯前缀文本;在 Node 端则直接用 colorize(prefix, color, 'bold') 生成加粗彩色的前缀。
以 info() 为例(console-logger.ts)可以看到典型的三段式结构:
info(...args: any[]): void {
if (this.level <= LogLevel.INFO) { // 1. 级别门槛过滤
const prefix = this.colorPrefix(this.prefix, 'info');
if (typeof window !== 'undefined' && this.lastPrefixColor) {
console.log( // 2. 浏览器:%c + CSS 颜色
`%c${prefix}%c`,
`color: ${CSS_COLOR_VALUES[this.lastPrefixColor]}; font-weight: bold`,
'color: inherit',
...args,
);
this.lastPrefixColor = null;
} else {
console.log(`${prefix}`, ...args); // 3. Node:直接拼接已着色的前缀
}
}
}
warn/error/success/debug 的结构完全一致,区别只在于调用的是 console.warn/console.error/console.log/console.debug,以及各自对应的级别常量和前缀颜色。值得留意的是成功/错误信息既走前缀着色、又通过调用不同 Console 方法(如 console.error)在 DevTools 中自然获得红色基调。
spawn 的实现:层级前缀与级别继承
嵌套 Logger 的实现并不复杂但很关键(console-logger.ts):
spawn(prefix: string): ConsoleLogger {
const newPrefix = this.prefix ? `${this.prefix}:${prefix}` : prefix;
// 把当前日志级别透传给子 logger
return new ConsoleLogger(newPrefix, this.level);
}
即"父前缀:子前缀"拼接(如 [App:Database]),且父级当前设置的级别会自动透传给子级——无需对每个子 Logger 重复 setLevel。当 prefix 为空时,直接以子前缀作为新 Logger 前缀。这一设计使 ConsoleLogger 天然支持文档所述的分层组织方式。
infoWithData 的实现
infoWithData 并非把数据 JSON 化后拼接进字符串,而是先输出消息、再原样打印数据对象(console-logger.ts):
infoWithData<T = any>(message: string, data?: T, transformer?: (value: T) => any): void {
if (this.level <= LogLevel.INFO) {
this.info(message);
if (data) {
console.log(transformer ? transformer(data) : data);
}
}
}
这样浏览器 Console 会渲染出可展开的树形对象,而不是一串难读的 JSON 字符串;传入的 transformer 只在输出前对数据做变换,不会修改原始对象——非常适合脱敏场景。注意 infoWithData 与 info 同属 INFO 级别门槛,受同一级别控制。
使用指南
日志级别控制
import { ConsoleLogger, LogLevel } from '@agent-infra/logger';
const logger = new ConsoleLogger('[App]');
// 设定级别:只有 WARN 和 ERROR 会显示
logger.setLevel(LogLevel.WARN);
// 以下不会显示
logger.debug('Debug message');
logger.info('Info message');
logger.success('Success message');
// 以下会显示
logger.warn('Warning message');
logger.error('Error message');
// 读取当前级别
const currentLevel = logger.getLevel(); // 返回 LogLevel.WARN
需要说明:本库级别模型把 SUCCESS(2) 置于 INFO(1) 与 WARN(3) 之间,属于"非递增严重度"的特殊设计。因此当级别设为 SUCCESS 时,DEBUG/INFO 被屏蔽而 SUCCESS/WARN/ERROR 均放行;setLevel(LogLevel.SILENT) 可彻底静音整个 Logger(包括 success),适合在无需日志的批量任务中关闭输出。
结构化数据脱敏
logger.infoWithData(
'User profile:',
{ id: 123, name: 'Alice', email: 'alice@example.com', password: 'secret123' },
// 输出前遮盖敏感字段
(data) => ({ ...data, password: '********' }),
);
分层 Logger
// 父级 logger
const appLogger = new ConsoleLogger('[App]');
// 为不同组件创建子 logger
const authLogger = appLogger.spawn('Auth');
const dbLogger = appLogger.spawn('Database');
const apiLogger = appLogger.spawn('API');
authLogger.info('User authenticated'); // 输出: [App:Auth] User authenticated
dbLogger.error('Connection failed'); // 输出: [App:Database] Connection failed
apiLogger.warn('Rate limit reached'); // 输出: [App:API] Rate limit reached
进阶实践
按环境差异化配置级别
import { ConsoleLogger, LogLevel } from '@agent-infra/logger';
const logger = new ConsoleLogger('[App]');
if (process.env.NODE_ENV === 'production') {
logger.setLevel(LogLevel.WARN); // 生产环境仅保留警告与错误
} else if (process.env.NODE_ENV === 'test') {
logger.setLevel(LogLevel.ERROR); // 测试环境仅保留错误
} else {
logger.setLevel(LogLevel.DEBUG); // 开发环境输出全部日志
}
对接错误追踪服务
通过方法覆写即可在不改动库的前提下把错误同时上报给 Sentry 等平台:
import { ConsoleLogger } from '@agent-infra/logger';
import * as Sentry from '@sentry/browser'; // 以 Sentry 为例
const logger = new ConsoleLogger('[App]');
// 覆写 error 方法,先走原实现再上报
const originalError = logger.error;
logger.error = function(...args) {
originalError.apply(this, args);
if (args[0] && typeof args[0] === 'string') {
Sentry.captureMessage(args[0], Sentry.Severity.Error);
}
};
由于 error 的级别门槛判断发生在 ConsoleLogger.error 内部,覆写后的包装函数依然保留原方法的级别过滤行为。更常见的接入方式是直接以 BaseLogger 为基类或实现 Logger 接口来定制自有日志后端。
自定义颜色主题
浏览器端颜色来自导出的 CSS_COLOR_VALUES(默认值见 colorize.ts,含 #3B82F6 蓝、#10B981 绿、#F87171 红、#FBBF24 黄等),可直接整体覆盖:
import { ConsoleLogger, CSS_COLOR_VALUES } from '@agent-infra/logger';
// 用自定义主题覆盖默认颜色
Object.assign(CSS_COLOR_VALUES, {
blue: '#3498db',
green: '#2ecc71',
red: '#e74c3c',
yellow: '#f39c12'
});
const logger = new ConsoleLogger('[MyApp]');
需注意该方法影响的是浏览器 %c 着色;Node 终端 ANSI 色彩与数值色盘无关,如需修改终端配色需自行扩展 colorize.ts 中的 ANSI 映射。
在仓库中的真实应用形态
@agent-infra/logger 被本仓库的 GUI Agent 技术栈广泛消费(@agent-infra/logger 依赖声明可在各子包 package.json 中查见)。从源码结构看,其典型用法如下:
- 在 GUIAgent.ts 中,模块级创建
new ConsoleLogger('[GUIAgent]', LogLevel.DEBUG)作为默认 logger,随后将用户注入的 logger(或该默认值)经spawn('[GUIAgent]')派生子实例使用; - 在 ToolCallEngine.ts 中直接以
[GUIAgent:ToolCallEngine]形式命名顶层 logger,与spawn的分层前缀约定保持一致; - action-parser 与各 operator(如 operator-adb、operator-browser、operator-aio)均把该包作为开发期可观测性基础设施引入。
从这些调用形态可以推断,该库的设计意图是让"应用名:子模块"前缀贯穿整套 Agent 流水线,借助一个 logger 实例贯穿上下文、借助 spawn 拆分关注点,同时通过 DEBUG 级别观察完整工具调用链、在生产端收敛到 WARN/ERROR。
最佳实践
按模块组织 Logger
对较大应用,推荐"单一根 Logger + 按 feature 派生并集中导出"的工厂模式:
// logger.ts
import { ConsoleLogger, LogLevel } from '@agent-infra/logger';
// 根 logger
const rootLogger = new ConsoleLogger('[MyApp]');
// 按环境设置级别(spawn 会把级别透传给所有子 logger)
rootLogger.setLevel(
process.env.NODE_ENV === 'production' ? LogLevel.WARN : LogLevel.INFO
);
// 导出各 feature 专属 logger
export const authLogger = rootLogger.spawn('Auth');
export const apiLogger = rootLogger.spawn('API');
export const dbLogger = rootLogger.spawn('DB');
export const uiLogger = rootLogger.spawn('UI');
统一日志格式(便于机器解析)
借助 infoWithData 将事件字段结构化、保持一致 schema:
import { ConsoleLogger } from '@agent-infra/logger';
const logger = new ConsoleLogger('[API]');
// 统一记录结构化事件
function logApiEvent(eventType, data) {
const event = {
timestamp: new Date().toISOString(),
type: eventType,
...data
};
logger.infoWithData(`API ${eventType}`, event);
}
logApiEvent('request', {
method: 'GET',
path: '/users',
duration: 120
});
小结
@agent-infra/logger 用不到三百行 TypeScript(src 下仅 4 个源文件)完整交付了分级、着色、同构、分层与数据脱敏能力:Logger 接口 + BaseLogger/defaultLogger 定义了可替换的契约与空实现,ConsoleLogger 提供开箱即用的控制台实现,colorize/CSS_COLOR_VALUES 抽象了双端着色差异。对于希望复用 UI-TARS-desktop 生态日志风格的开发者,可直接依赖本包;对于需要定制日志后端的场景,则可以 Logger 接口为边界自行实现并保持 spawn 语义一致。若需了解包级构建与版本管理,可继续查看 package.json、rslib.config.ts 与 CHANGELOG.md。
许可说明:本组件版权归 ByteDance, Inc. 及其关联公司所有(Copyright (c) 2025),采用 Apache License 2.0 开源许可。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

