ToolJet 审计日志文件输出实战:用 LOG_FILE_PATH 开启 Audit Log 文件化与按日轮转
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
ToolJet 的日志文件(Log File)功能用于把服务端产生的审计日志(Audit Log)持久化到磁盘文件,便于长期留存、离线分析与合规审计。本文围绕官方文档 setup-syslog.md 的核心内容展开:如何通过LOG_FILE_PATH环境变量激活文件输出、理解按日轮转的目录结构、解读日志数据格式,并结合当前仓库server/src下的真实源码,说明这一功能的实现链路(动态模块加载、winston-daily-rotate-file轮转、轮转/停机时的 JSON 伴生文件生成)。读完本文,你可以完成 ToolJet 审计日志文件化的完整配置,并能对照源码理解每一处落盘行为的来龙去脉。
1. 功能定位:把审计日志沉淀为文件
在 ToolJet 中,日志文件是审计日志的综合记录载体,捕获平台内各类关键活动:谁(用户 ID)、在哪个组织(Organization ID)、对什么资源(Resource ID/类型/名称)、执行了什么操作(Action Type)、来自哪个 IP,以及 User-Agent、ToolJet 版本等附加元数据。
- 该功能默认关闭:只有显式设置环境变量
LOG_FILE_PATH后,服务端启动时才会加载日志文件模块; - 它面向自托管/私有化部署场景,为运维与审计人员提供“数据库之外”的、按进程与日期组织的持久日志。
2. 激活与配置
2.1 设置环境变量LOG_FILE_PATH
激活日志文件功能只需设置一个环境变量,其取值即日志根目录的文件夹名(文档示例中使用rsyslog):
LOG_FILE_PATH='rsyslog'该值并非协议标识,而是相对主目录(home directory)的路径。例如服务进程用户的 home 目录为/home/tooljet,则日志将落在/home/tooljet/rsyslog之下。
2.2 重启服务器使配置生效
修改环境变量后必须重启 ToolJet 服务端,日志文件生成流程才会被初始化。从源码结构看,这是由启动时的动态模块加载逻辑决定的:
- loader.ts 中,仅当
process.env.LOG_FILE_PATH存在时,才动态导入并注册LogToFileModule:
if (process.env.LOG_FILE_PATH) { // Add log-to-file module if LOG_FILE_PATH is set const { LogToFileModule } = await import( `${await getImportPath(configs.IS_GET_CONTEXT)}/log-to-file/module` ); dynamicModules.push(await LogToFileModule.register(configs)); }- module.ts 是一个轻量的
SubModule包装,按IS_GET_CONTEXT维度缓存动态模块实例,本身不携带业务逻辑。
由于模块只在进程启动阶段决定是否挂载,运行中修改环境变量不会热生效,这就是“必须重启”的底层原因。
3. 日志目录与文件结构
3.1 自动创建根目录
功能启用后,服务端会自动创建日志根目录。在 constants/index.ts 中可以看到:
const absoluteLogDir = path.join(os.homedir(), filePath, 'tooljet_log'); if (!fs.existsSync(absoluteLogDir)) { fs.mkdirSync(absoluteLogDir, { recursive: true }); }即目录不存在时用fs.mkdirSync(recursive: true)递归创建;已存在则跳过并记录一条确认日志。
3.2 路径结构:按进程 + 日期两级组织
官方文档给出的路径结构为:
homepath/rsyslog/{process_id}-{date}/audit.log其中{process_id}为进程唯一标识,{date}为当前日期。这样的两级组织(进程 × 日期)让不同服务进程、不同天的审计日志天然隔离,便于追溯与分析。
结合当前源码可以更精确地描述实际落盘路径:logFileTransportConfig在~/{LOG_FILE_PATH}/tooljet_log基础上,再以winston-daily-rotate-file的dirname拼接${processId}-%DATE%,最终结构为~/{LOG_FILE_PATH}/tooljet_log/{process_id}-{date}/audit.log(相比文档早期描述,当前实现多了一层tooljet_log子目录,以源码为准)。
这一设计也意味着:若同一台机器上运行多个 ToolJet 服务进程(如多副本部署在同一容器用户下),各进程会写入各自的{pid}-日期子目录,互不冲突——从dirname使用processId的源码结构看,这是有意为之的隔离策略。
4. 按日轮转的实现细节
日志文件每天轮转一次(daily rotation),为每一天生成独立的日志文件,便于管理审计数据。实现上采用winston生态的winston-daily-rotate-file,关键配置位于 log-to-file/constants/index.ts:
const transport = new DailyRotateFile({ filename: `audit.log`, // 固定文件名 level: 'info', // 仅捕获 info 及以上级别 zippedArchive: false, // 轮转文件不压缩 dirname: `${absoluteLogDir}/${processId}-%DATE%`, // 目录随日期变化 datePattern: 'YYYY-MM-DD', // 按日切分 format: winston.format.combine(winston.format.prettyPrint()), json: true, });要点解析:
| 参数 | 取值 | 含义 |
|---|---|---|
filename | audit.log | 每个日期目录下的固定文件名,与文档一致 |
level | info | 文件仅记录info及以上级别日志 |
datePattern | YYYY-MM-DD | 日期目录命名规则,每天 0 点后切换目录 |
zippedArchive | false | 轮转后旧文件保留明文,不生成压缩包(需自行规划磁盘清理策略) |
json/format | true/prettyPrint() | 以多行 pretty-print 的 JSON 对象形式写入 |
4.1 轮转事件:自动生成 JSON 伴生文件
Transport 监听rotate事件(constants/index.ts):
transport.on('rotate', function (oldFilename, newFilename) { console.log(`Rotating old log file - ${oldFilename} and creating new log file ${newFilename}`); readObjectFromLines(oldFilename); }); transport.on('error', (err) => { console.error('Log file generation error:', err); });每次轮转时,readObjectFromLines会读取刚被切走的旧文件,逐行解析 pretty-print 出的 JSON 对象,汇总为一个 JSON 数组后写入同名的audit.log.json伴生文件(JSON.stringify(..., null, 2))。这样审计系统除了原始多行文本外,还有一份可直接加载的结构化 JSON 数据。
5. 日志格式与示例数据
5.1 行格式
写入文件前的最终格式化由logFormat组合而成(constants/index.ts):
const logForm = winston.format.printf( (info) => `${info.timestamp} ${info.level} [${info.label}]: ${info.message}` ); export const logFormat = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), auditLog(), logForm );- 时间戳统一为
YYYY-MM-DD HH:mm:ss; auditLog()是自定义 winston format(见 audit-logs/constants/index.ts):把日志调用时传入的options重命名为auditLog,并从auditLog.resourceType提取出label(即行内[APP]这类方括号标签):
export const auditLog = winston.format((info) => { info.auditLog = info.options; delete info.options; info.label = info.auditLog?.['resourceType']; return info; });5.2 示例日志数据
单条日志捕获的关键字段包括:用户 ID、组织 ID、资源 ID、资源类型、操作类型、资源名称、IP 地址与附加元数据。文档给出的完整示例如下:
{ level: 'info', message: 'PERFORM APP_CREATE OF awdasdawdwd APP', timestamp: '2023-11-02 17:12:40', auditLog: { userId: '0ad48e21-e7a2-4597-9568-c4535aedf687', organizationId: 'cf8e132f-a68a-4c81-a0d4-3617b79e7b17', resourceId: 'eac02f79-b8e2-495a-bffe-82633416c829', resourceType: 'APP', actionType: 'APP_CREATE', resourceName: 'awdasdawdwd', ipAddress: '::1', metadata: { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36', tooljetVersion: '2.22.2-ee2.8.3' } }, label: 'APP' }可以看到message采用PERFORM {actionType} OF {resourceName} {resourceType}的自然语言模板,而结构化字段全部收敛在auditLog对象中,label与resourceType一致,方便按资源类型做行级过滤(如grep ' \[APP\]:')。
6. 停机收尾:优雅关闭时补写 JSON
按日轮转只覆盖“跨天”场景;若服务在当天内重启或停机,当天的audit.log也需要被转换。仓库中 shut-down.hook.ts 通过 NestJS 的OnApplicationShutdown钩子补齐了这一环:
async onApplicationShutdown(signal?: string): Promise<void> { const envFilePath = this.configService.get<string>('LOG_FILE_PATH'); if (envFilePath) { console.log('Creating log json file before shutting down server'); const absoluteLogDir = join(homedir(), envFilePath, 'tooljet_log'); const formattedDate = new Date().toISOString().slice(0, 10); const filePath = `${absoluteLogDir}/${process.pid}-${formattedDate}/audit.log`; readObjectFromLines(filePath); console.log('JSON log file for this process is created'); } }即:优雅停机前,定位当前进程(process.pid)当天日期对应的audit.log,执行与轮转时相同的 JSON 化转换。因此无论是午夜轮转还是受控停机,audit.log.json都会保持可用。
7. 运维注意事项
结合文档与源码,落地时建议关注以下几点:
- 未设置即关闭:不设置
LOG_FILE_PATH时LogToFileModule根本不会被加载,服务端只有控制台日志; - 必须重启生效:模块加载发生在启动阶段(loader.ts),改值后务必重启服务端;
- 相对 home 目录:日志位置取决于服务进程运行用户的 home 目录(
os.homedir()),容器化部署时应确认容器内该用户的 HOME 及磁盘卷挂载,避免日志落在容器层而非持久卷; - 多进程隔离:每个进程写入独立的
{pid}-{date}子目录,横向扩容场景下需要按进程维度收集日志; - 磁盘容量规划:
zippedArchive: false表示轮转文件不压缩,长期运行建议配合外部策略(如定期归档/清理旧日期目录)管理磁盘; - JSON 伴生文件的时机:
audit.log.json在“轮转”和“优雅停机”两个时机生成;直接kill -9等强杀方式可能使当天文件缺少 JSON 转换,生产环境应优先受控重启。
8. 小结
ToolJet 的日志文件功能以最小配置(一个环境变量 + 一次重启)换取了结构化的审计日志落盘能力:~/{LOG_FILE_PATH}/tooljet_log/{process_id}-{date}/audit.log的“进程 × 日期”两级组织、基于winston-daily-rotate-file的每日轮转、auditLog结构化的行格式,以及轮转/停机时自动生成的 JSON 伴生文件,共同构成了一个面向审计与合规的可追溯日志方案。核心实现集中在 server/src/modules/log-to-file/ 与 server/src/modules/audit-logs/constants/index.ts 中,可直接在仓库内对照本文各小节继续深入。
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考