news 2026/9/23 18:02:23

RedwoodJS 日志体系实战指南:基于 pino 的 API 侧日志、Prisma 查询追踪与云端传输

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RedwoodJS 日志体系实战指南:基于 pino 的 API 侧日志、Prisma 查询追踪与云端传输
  • 后端
  • 前端
  • Web框架
  • 开发工具

【免费下载链接】redwood

RedwoodGraphQL

项目地址:https://gitcode.com/gh_mirrors/re/redwood
点击查看免费下载

RedwoodJS 内置了一套以 pino 为核心的“有主见”的日志方案(@redwoodjs/api/logger),覆盖从本地开发的可读性输出到 Serverless 生产环境的第三方日志服务流式传输。本篇指南将带你掌握 RedwoodJS 日志的全部核心能力:初始化与快速上手、日志级别与红action 脱敏、LogFormatter 格式化输出、文件与传输流(Transport Stream)两类 destination 配置,以及 Prisma 查询日志与慢查询阈值调优,并深入源码验证其默认配置与底层实现。

说明:RedwoodJS 日志仅针对 api 侧设计;浏览器端与 web 侧的错误上报功能计划在后续版本提供。

Quick Start:三分钟接入 API 侧日志

RedwoodJS 在服务(service)、函数(function)或任意 lib 中引入logger即可开始记录日志,用法与console几乎一致。项目模板中 api/src/lib/logger.ts 已经为你创建好了默认实例:

import { createLogger } from '@redwoodjs/api/logger' /** * Creates a logger with RedwoodLoggerOptions * * These extend and override default LoggerOptions, * can define a destination like a file or other supported pino log transport stream, * and sets whether or not to show the logger configuration settings (defaults to false) * * @param RedwoodLoggerOptions * * RedwoodLoggerOptions have * @param {options} LoggerOptions - defines how to log, such as redaction and format * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file * @param {boolean} showConfig - whether to display logger configuration on initialization */ export const logger = createLogger({})

createLogger接收一个RedwoodLoggerOptions对象,其结构定义在 packages/api/src/logger/index.ts:

  • optionsLoggerOptions(即 pino 的 options),定义如何记录日志,如级别、脱敏、格式;
  • destinationstring | DestinationStream,定义记录到何处——标准输出、文件或远程传输流;
  • showConfigboolean,默认false,置为true时会在初始化时把日志配置打印到控制台,便于调试。

然后在 service、lib 或 function 中按熟悉的console风格使用:

// then, in your api service, lib, or function import { logger } from 'src/lib/logger' //... logger.trace(`>> items service -> About to save item ${item.name}`) logger.info(`Saving item ${item.name}`) logger.debug({ item }, `Item ${item.name} detail`) logger.warn(item, `Item ${item.id} is missing a name`) logger.warn({ missing: { name: item.name } }, `Item ${item.id} is missing values`) logger.error(error, `Failed to save item`)

每个日志方法的第一参数可以是普通元数据对象,也可以直接传错误对象,第二参数为日志消息字符串。从源码实现看,createLogger最终调用 pino 的pino(options, stream)(packages/api/src/logger/index.ts),因此 pino 丰富的 API(childflushlevel等)在 RedwoodJS 中全部可用。

旧版升级手动配置

如果你的应用早于 v0.28 且需要补齐日志,只需从 "Create Redwood Application" 模板复制两个文件:

  • 复制 packages/create-redwood-app/templates/ts/api/src/lib/logger.ts 到api/src/lib/logger.ts(必选)。该文件定义了 logger 实例,之后可逐步用logger.info()/logger.debug()替换原有的console.log()
  • 可选:复制packages/create-redwood-app/templates/ts/api/src/lib/db.ts替换api/src/lib/db.ts(或.js),用于配置 Prisma 日志(详见下文)。

Options:如何记录日志(How to Log)

日志级别(Log Level)

可选值:fatalerrorwarninfodebugtracesilent。日志级别是最小级别语义——例如级别设为info,则fatalerrorwarninfo都会被输出。silent则完全关闭日志。

RedwoodJS 会根据运行环境自动选择合理的默认最低级别,源码逻辑见 packages/api/src/logger/index.ts:

环境默认级别说明
Development(NODE_ENV=developmenttrace开发服务器输出详尽
Production(非 dev/test)warn只保留关键告警与错误
Test(NODE_ENV=testsilent测试时默认静默

可用LOG_LEVEL环境变量或options.level覆盖默认值。isDevelopment/isTest/isProduction三个判定也导出在 packages/api/src/logger/index.ts,其中isProduction的定义是“非 development 且非 test”。

import { createLogger } from '@redwoodjs/api/logger' /** * Creates a logger with RedwoodLoggerOptions * * These extend and override default LoggerOptions, * can define a destination like a file or other supported pino log transport stream, * and sets whether or not to show the logger configuration settings (defaults to false) * * @param RedwoodLoggerOptions * * RedwoodLoggerOptions have * @param {options} LoggerOptions - defines how to log, such as redaction and format * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file * @param {boolean} showConfig - whether to display logger configuration on initialization */ export const logger = createLogger({ options: { level: 'info' } })

排障提示:部署后看不到日志输出时,可考虑把级别临时下调到infodebug(例如线上环境默认只输出warn及以上)。

敏感信息脱敏(Redaction)

日志中泄露邮箱、密码、Token 是常见事故源。RedwoodJS 提供默认脱敏列表redactionsList,源码位于 packages/api/src/logger/index.ts,除文档中列出的access_tokenaccessTokenDATABASE_URLemailevent.headers.authorizationhostjwtJWTpasswordparamssecret外,还额外覆盖了data.*嵌套路径下的emailpasswordsalthashedPasswordjwtsecretaccess_token等组合,基本覆盖了 GraphQL 请求载荷data结构中常见的敏感字段:

import { createLogger } from '@redwoodjs/api/logger' /** * Custom redaction list */ //... export const logger = createLogger({ options: { redact: [...redactionsList, 'ssn,credit_card_number'] }, })

注意:如果自定义redact,请务必展开redactionsList保留默认项,否则只会脱敏你列出的键(例如仅'ssn,credit_card_number')。

pino 的redact选项支持三种形态(见 pino redaction 文档),因此在任何未显式覆盖redact的 logger 上,这些敏感键默认都会被自动打码。

LogFormatter 日志格式化(原“Pretty Printing”)

重要:自 v0.41 起,RedwoodJS 不再支持 pino 的 “pretty printing”(pino-pretty已废弃,且生产环境格式化会带来额外开销、无法送往传输流)。取而代之的是 RedwoodJS 自研的LogFormatter

LogFormatter基于 pino-colada)。

把日志管道给格式化器:

echo "{\"level\": 30, \"message\": \"Hello RedwoodJS\"}" | yarn rw-log-formatter

输出:

11:00:28 🌲 Hello RedwoodJS ✨ Done in 0.14s.

使用方式yarn rw dev已自动开启格式化;rw serve时可手动管道:

yarn rw dev yarn rw serve | yarn rw-log-formatter yarn rw serve api | yarn rw-log-formatter

注意:rw serve会把 Node 环境设为production,因此默认只输出warn/error级别;若想看到更多输出,需把日志级别配置为debug或更低。

格式化后的输出用 emoji 区分级别,如 🐛 表示debug、🌲 表示info。格式化器的具体实现与测试可参考 packages/api-server/src/logFormatter/README.md 与 packages/api-server/src/tests/logFormatter.test.ts。

自定义日志载荷(Custom Payload)

除了querydata等 GraphQL 预设字段,你还可以用custom键记录自己的消息或对象。以下post表示一篇含idtitlecommentCountdescription的博客文章:

// 记录单个标题 logger.debug({ custom: post.title }, 'The title of a Post') // 记录自定义对象载荷 logger.debug( { custom: { title: post.title, comments: post.commentCount, }, }, 'Post with count of comments' ) // 更深的嵌套载荷 logger.debug( { custom: { title: post.title, details: { id: post.id, description: post.description, comments: post.commentCount, }, }, }, 'Post details' ) // 记录整个对象 logger.debug( { custom: post, }, 'Post details' )

GraphQL 日志

RedwoodJS 的 GraphQL 服务通过useRedwoodLoggerenvelop 插件(详见 GraphQL 日志文档)注入额外的日志数据:

  • Request Id
  • User-Agent
  • GraphQL Operation Name
  • GraphQL Query
  • GraphQL Data

这些数据在调试 GraphQL 请求链路时非常关键。

生产环境日志建议

生产环境通常不适用 LogFormatter 格式化,而是:

  • 以 ndjson 格式把日志交给宿主平台的日志处理器或应用监控服务去处理、存储与展示;
  • 只记录warnerror级别,避免info/debug的噪音(这些更适合 staging 或集成环境)。

嵌套日志(Nested Logging)避免键冲突

当元数据键与 pino 或第三方传输流需要的键冲突时,可以用nestedKey把元数据嵌套到logpayload等自定义属性下:

nestedKey: 'log',

注意:使用nestedKey后,redact路径需要手动加上前缀。例如嵌套键为log时,脱敏email应改为log.email

Destination:记录到哪里(Where to Log)

destination选项决定 API 侧日志语句的去向:标准输出、文件或传输流。createLogger源码(packages/api/src/logger/index.ts)会区分三种情况:

  • isFiledestination为字符串路径,即写文件;
  • isStreamdestination为传输流对象;
  • 均未提供:输出到标准输出。

此外,showConfig: true会在初始化时打印环境判定、logLevel、合并后的optionsdestination,方便确认实际生效配置。

开发服务器(Dev Server)

开发环境中日志直接输出到 dev server 的标准输出。

写文件(Log to File)

在开发环境或其它可写文件系统的环境中,可把destination指向文件路径:

/** * Log to a File */ export const logger = createLogger({ //options: {}, destination: '/path/to/file/api.log', })

注意:部署到 Netlify 或 Vercel 时不允许写文件。源码中当isFile && isProduction时会给出警告,提示必须确保生产环境具备文件系统访问能力。

传输流(Transport Streams)

Serverless 函数的执行是瞬时的,日志输出同样转瞬即逝——若不及时监控,关键告警、错误或异常很容易丢失。因此生产环境推荐把日志发送到“传输流”以便持久化与检索。

pino 的“传输流”是消费 pino 日志的补充工具,pino 官方提供多种已知传输(见 pino transports)。注意并非所有已知 pino 传输都适用于 Serverless 环境。下面给出 Logflare 与 Datadog 的配置示例(完整可参考下方配置示例小节)。

默认配置总览(Default Configuration Overview)

RedwoodJS 提供的“有主见”默认配置(定义于 packages/api/src/logger/index.ts 的defaultLoggerOptionslogLevel):

  • 使用自定义 LogFormatter 着色并加 emoji;
  • 忽略hostnamepid等事件属性,让日志更干净;
  • 为日志输出加级别前缀;
  • 使用省略服务器名的短消息;
  • 时间以 GMT 人类可读格式呈现;
  • 开发/测试环境默认级别trace,生产环境默认warn
  • 可通过LOG_LEVEL环境变量覆盖默认级别;
  • 通过内置redactionsList脱敏host及其它敏感键。

配置示例:常见覆盖与自定义

覆盖最低日志级别

把生产环境的默认warn下调为debug

/** * Override minimum log level to debug */ export const logger = createLogger({ options: { level: 'debug' }, })

自定义脱敏列表

追加自定义键my_secret_key到默认列表:

/** * Customize a redactions list to add `my_secret_key` */ import { redactionsList } from '@redwoodjs/api/logger' export const logger = createLogger({ options: { redact: [...redactionsList, 'my_secret_key'] }, })

写物理文件

/** * Log to a File */ export const logger = createLogger({ options: {}, destination: '/path/to/file/api.log', })

(同样受 Netlify / Vercel 写文件限制约束。)

自定义传输流:以 Honeybadger 为例

若 pino 没有对应服务的现成传输包,可用 Node.js 内置stream包的Writable类自行实现:

yarn workspace api add stream yarn workspace api add @honeybadger-io/js
// api/src/lib/logger.ts import { createLogger } from '@redwoodjs/api/logger' import { Writable } from 'stream' const Honeybadger = require('@honeybadger-io/js') Honeybadger.configure({ apiKey: process.env.HONEYBADGER_API_KEY, }) const HoneybadgerStream = () => { const stream = new Writable({ write(chunk: any, encoding: BufferEncoding, fnOnFlush: (error?: Error | null) => void) { Honeybadger.notify(chunk.toString()) fnOnFlush() }, }) return stream } /** * Creates a logger. Options define how to log. Destination defines where to log. * If no destination, std out. */ export const logger = createLogger({ options: { level: 'debug' }, destination: HoneybadgerStream(), })

运行前确保环境变量HONEYBADGER_API_KEY已配置。Writable.write(chunk, encoding, callback)的接口说明见 Node.js stream 文档。

传输到 Datadog

yarn workspace api add pino-datadog
// api/src/lib/logger.ts import datadog from 'pino-datadog' /** * Creates a synchronous pino-datadog stream * * @param {object} options - Datadog options including your account's API Key * * @typedef {DestinationStream} */ export const stream = datadog.createWriteStreamSync({ apiKey: process.env.DATADOG_API_KEY, ddsource: 'my-source-name', ddtags: 'tag,not,it', service: 'my-service-name', size: 1, }) /** * Creates a logger with RedwoodLoggerOptions * * These extend and override default LoggerOptions, * can define a destination like a file or other supported pino log transport stream, * and sets whether or not to show the logger configuration settings (defaults to false) * * @param RedwoodLoggerOptions * * RedwoodLoggerOptions have * @param {options} LoggerOptions - defines how to log, such as redaction and format * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file * @param {boolean} showConfig - whether to display logger configuration on initialization */ export const logger = createLogger({ options: {}, destination: stream, })

传输到 Logflare

yarn workspace api add pino-logflare
// api/src/lib/logger.ts import { createWriteStream } from 'pino-logflare' /** * Creates a pino-logflare stream * * @param {object} options - Logflare options including * your account's API Key and source token id * * @typedef {DestinationStream} */ export const stream = createWriteStream({ apiKey: process.env.LOGFLARE_API_KEY, sourceToken: process.env.LOGFLARE_SOURCE_TOKEN, }) export const logger = createLogger({ options: {}, destination: stream, })

传输到 logDNA

yarn workspace api add pino-logdna
// api/src/lib/logger.ts import pinoLogDna from 'pino-logdna' const stream = pinoLogDna({ key: process.env.LOGDNA_INGESTION_KEY, onError: console.error, }) /** * Creates a logger with RedwoodLoggerOptions * * These extend and override default LoggerOptions, * can define a destination like a file or other supported pino log transport stream, * and sets whether or not to show the logger configuration settings (defaults to false) * * @param RedwoodLoggerOptions * * RedwoodLoggerOptions have * @param {options} LoggerOptions - defines how to log, such as redaction and format * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file * @param {boolean} showConfig - whether to display logger configuration on initialization */ export const logger = createLogger({ options: {}, destination: stream, })

传输到 Papertrail

yarn workspace api add pino-papertrail
import papertrail from 'pino-papertrail' const stream = papertrail.createWriteStream({ appname: 'my-app', host: '*****.papertrailapp.com', port: '*****', }) /** * Creates a logger with RedwoodLoggerOptions * * These extend and override default LoggerOptions, * can define a destination like a file or other supported pino log transport stream, * and sets whether or not to show the logger configuration settings (defaults to false) * * @param RedwoodLoggerOptions * * RedwoodLoggerOptions have * @param {options} LoggerOptions - defines how to log, such as redaction and format * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file * @param {boolean} showConfig - whether to display logger configuration on initialization */ export const logger = createLogger({ options: {}, destination: stream, })

Papertrail 选项说明

可在 options 对象中传入以下属性:

PropertyTypeDescription
appname(默认 pino)string应用名称
host(默认 localhost)stringPapertrail 目标地址
port(默认 1234)numberPapertrail 目标端口
connection(默认 udp)stringPapertrail 连接方式(tls/tcp/udp)
echo(默认 true)boolean是否在控制台回显消息
message-only(默认 false)boolean只发送msg属性作为消息给 Papertrail
backoff-strategy(默认new ExponentialStrategy()BackoffStrategytls/tcp 套接字错误的指数退避重试策略

Prisma 日志(Prisma Logging)

RedwoodJS 的日志方案与 Prisma Client 深度集成:既能观测数据库连接问题、慢查询,也能捕获意外错误。集成分两步,模板实现见 packages/create-redwood-app/templates/ts/api/src/lib/db.ts:

  1. 创建 Prisma Client 时用emitLogLevels设置要发出的日志级别(emit: 'event');
  2. handlePrismaLogging把 Prisma 发出的事件接入 Redwood logger,并传入相同的级别集合。
/* * Instance of the Prisma Client */ export const db = new PrismaClient({ log: emitLogLevels(['info', 'warn', 'error']), }) handlePrismaLogging({ db, logger, logLevels: ['info', 'warn', 'error'], })

两个工具函数emitLogLevelshandlePrismaLogging均由@redwoodjs/api/logger导出,实现细节见 packages/api/src/logger/index.ts:

  • emitLogLevels(levels)把每个级别映射为{ emit: 'event', level }LogDefinition
  • handlePrismaLogging内部先创建子 logger 注入prisma.clientVersion(读取自db['_clientVersion']),再对每个级别通过db.$on(level, handler)订阅事件,把 Prisma 事件转成对应的logger.info/logger.warn/logger.error

默认级别为infowarnerrordefaultLogLevels,packages/api/src/logger/index.ts)。如需记录每一条查询,加上query级别:

log: emitLogLevels(['info', 'warn', 'error', 'query']),

如果想去掉info,只保留['warn', 'error']即可。

慢查询(Slow Queries)

若启用了 Prismaquery级别且 Logger 处于debug级别,则所有查询语句都会被记录。否则,超过阈值的查询会以warn级别记录。

默认阈值是 2 秒(源码常量DEFAULT_SLOW_QUERY_THRESHOLD = 2_000,即 2000ms,见 packages/api/src/logger/index.ts)。可通过slowQueryThreshold自定义:

handlePrismaLogging({ db, logger, logLevels: ['query', 'info', 'warn', 'error'], slowQueryThreshold: 5_000, // in ms })

从源码看,query事件处理会依据event.duration >= slowQueryThreshold分支:超阈值记录Slow Query performed in ${duration} msec(warn 级别),未超阈值记录Query performed in ${duration} msec(debug 级别)。也就是说,调低 Logger 级别到debug后,配合query级别即可看到全部查询及耗时,这为定位 N+1 查询与索引问题提供了直接依据。

高级用法(Advanced Use)

子 Logger(Child Loggers)

有时需要为每一条日志附加固定信息,此时使用 pino 的子 Logger。子 Logger 会在每条输出中自动带上绑定字段:

import { db } from 'src/lib/db' import { logger } from 'src/lib/logger' export const userExamples = ({}, { info }) => { // Adds path to the log const childLogger = logger.child({ path: info.fieldName }) childLogger.trace('I am in find many user examples resolver') return db.userExample.findMany() } export const userExample = async ({ id }, { info }) => { // Adds id and the path to the log const childLogger = logger.child({ id, path: info.fieldName }) childLogger.trace('I am in the find a user example by id resolver') const result = await db.userExample.findUnique({ where: { id }, }) // Since this is the child logger, here id and path will be included as well childLogger.debug({ ...result }, 'This is the detail for the user') return result }

RedwoodJS 自身的 Prisma 日志集成正是利用子 Logger 注入 Prisma Client 版本号,让每条 Prisma 日志都携带版本上下文(packages/api/src/logger/index.ts):

logger.child({ prisma: { clientVersion: db['_clientVersion'] }, })

刷新日志缓冲(Flushing the Log)

当使用异步 destination(可能缓冲日志行)时,可手动刷新缓冲:

logger.flush()

该场景主要面向异步日志——在其它行写入期间,日志可能被缓冲在内存中。

总结

RedwoodJS 日志方案以createLogger({ options, destination, showConfig })为统一入口,围绕“如何记录(options)”与“记录到何处(destination)”两条主线展开:开发环境用 LogFormatter 获得带颜色与 emoji 的可读输出,LOG_LEVEL与默认环境级别保证不同环境的行为可预期,redactionsList默认脱敏阻断敏感信息外泄;生产环境则通过传输流把 ndjson 日志送往 Datadog、Logflare、logDNA、Papertrail 等监控服务,并结合 Prisma 的emitLogLevels/handlePrismaLogging观测查询与慢查询。整套体系基于 pino 构建,完整继承了 pino 的 options、子 Logger、传输流生态,是 RedwoodJS api 侧可观测性的开箱即用底座。

  • 后端
  • 前端
  • Web框架
  • 开发工具

【免费下载链接】redwood

RedwoodGraphQL

项目地址:https://gitcode.com/gh_mirrors/re/redwood
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 18:02:08

交通灯检测数据集:XML转TXT与YOLO训练实战指南

简介:这是一份面向交通场景目标检测实验的交通标志与交通信号灯数据集,原创并由LabelImg手工标注,覆盖限速牌、警告牌以及红灯、绿灯、黄灯等常见类别,图片为真实路况高清照片,适合目标检测入门、模型效果对比和毕业设…

作者头像 李华
网站建设 2026/9/23 17:56:23

STM32开源项目三件套实测:代码、原理图与仿真的完整上手指南

从收藏夹吃灰到真正跑通,我花了两个晚上把一套网上开源的STM32项目完整过了一遍。这套项目就是很多初学者硬盘里都有的江科大STM32,代码、原理图、仿真三件套配得很齐。网上讨论这套资源的帖子很多,但大多数停留在"视频讲得好"&quo…

作者头像 李华
网站建设 2026/9/23 17:51:03

超声腹部多器官分割实战:从数据预处理到模型训练避坑指南

简介:超声腹部多器官图像分割数据集面向医学影像分析、深度学习与计算机辅助诊断研究者,覆盖肝脏、肾脏、胆囊、脾脏、胰腺、血管及肾上腺等主要腹部结构,适合多器官分割模型的训练、验证与算法对比。包内共1855个文件,主体为1853…

作者头像 李华