首页
/ UI-TARS-desktop 同构日志组件:@agent-infra/logger 源码解析与实战指南

UI-TARS-desktop 同构日志组件:@agent-infra/logger 源码解析与实战指南

2026-09-08 11:26:08作者:范垣楠Rhoda

@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/nodetypescript@rslib/core 三个开发依赖);
  • TypeScript Support:完整类型定义随包发布;
  • Log Levels(日志级别):细粒度控制输出详细程度。

浏览器 Console 中的输出效果 Node.js 终端中的输出效果

安装

作为发布在 npm 上的独立包,可在任意 Node.js / 前端项目中安装:

npm install @agent-infra/logger
# 或
yarn add @agent-infra/logger
# 或
pnpm add @agent-infra/logger

包的模块格式由 package.jsonexports 字段提供:import 指向 ./dist/index.mjs(ESM)、require 指向 ./dist/index.js(CJS),类型声明位于 ./dist/index.d.ts。构建产物由 rslib.config.ts 中的 defineConfig 一次性产出 esmcjs 双格式,并开启 dtssourceMap

快速上手

组件全部对外导出位于 src/index.ts:它 export *types.tsconsole-logger.ts,并单独导出 colorizecolorLog。最小可用示例:

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。注意在源码中 infowarnerrordebug 的签名是变长的 (...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 返回新的 BaseLoggergetLevel 恒返回 INFO)。defaultLoggernew 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 顶部执行两次探测:

  1. 浏览器判定typeof window !== 'undefined' && typeof window.document !== 'undefined'
  2. 色彩支持判定(仅对 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→bluewarn→yellowerror→reddebug→graysuccess→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 只在输出前对数据做变换,不会修改原始对象——非常适合脱敏场景。注意 infoWithDatainfo 同属 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-adboperator-browseroperator-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.jsonrslib.config.tsCHANGELOG.md

许可说明:本组件版权归 ByteDance, Inc. 及其关联公司所有(Copyright (c) 2025),采用 Apache License 2.0 开源许可。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389