feat(logger): 添加 OpenObserve 传输器以支持日志发送,移除不必要的依赖
All checks were successful
/ release (push) Successful in 30s

This commit is contained in:
zhaoyingbo 2025-03-18 13:06:22 +00:00
parent 50bef945f7
commit 1577c02ed3
4 changed files with 146 additions and 1994 deletions

1951
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,6 @@
"dependencies": { "dependencies": {
"@gitbeaker/rest": "^41.2.0", "@gitbeaker/rest": "^41.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"winston": "3.14.2", "winston": "3.14.2"
"winston-daily-rotate-file": "5.0.0"
} }
} }

View File

@ -17,7 +17,6 @@
"author": "RainSun <zhaoyingbo@live.cn>", "author": "RainSun <zhaoyingbo@live.cn>",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"winston": "*", "winston": "*"
"winston-daily-rotate-file": "*"
} }
} }

View File

@ -1,58 +1,159 @@
import winston, { format } from "winston" import winston, { format } from "winston"
import DailyRotateFile from "winston-daily-rotate-file" import Transport from "winston-transport"
import zlib from "zlib"
/**
*
* 'dev'
*/
const isProd = process.env.NODE_ENV !== "dev" const isProd = process.env.NODE_ENV !== "dev"
const transports: any[] = [ /**
* OpenObserve传输器配置接口
* Transport.TransportStreamOptions
* @interface OpenObserveTransportOptions
* @property {string} host - OpenObserve
* @property {number} port - OpenObserve
* @property {string} uri - OpenObserve API
* @property {string} username -
* @property {string} password -
* @property {boolean} [tls] - 使 TLS/SSL true
* @property {string} [level] - 'info'
*/
interface OpenObserveTransportOptions extends Transport.TransportStreamOptions {
host: string
port: number
uri: string
username: string
password: string
tls?: boolean
level?: string
}
/**
* OpenObserve HTTP传输器
* OpenObserve
* @class OpenObserveTransport
* @extends Transport
*/
class OpenObserveTransport extends Transport {
private host: string
private port: number
private uri: string
private username: string
private password: string
private tls: boolean
/**
*
* @param {OpenObserveTransportOptions} opts -
*/
constructor(opts: OpenObserveTransportOptions) {
super(opts)
this.host = opts.host
this.port = opts.port
this.uri = opts.uri
this.username = opts.username
this.password = opts.password
this.tls = opts.tls !== false
}
/**
* OpenObserve
* @param {Record<string, any>} info -
* @param {() => void} callback -
* @returns {Promise<void>}
*/
async log(info: Record<string, any>, callback: () => void): Promise<void> {
// 异步通知日志已被记录
setImmediate(() => {
this.emit("logged", info)
})
try {
// 添加时间戳字段到日志数据
const logData = {
...info,
_timestamp: new Date().toISOString(),
}
// 压缩数据以减少传输大小
const compressedData = await new Promise<Buffer>((resolve, reject) => {
zlib.gzip(JSON.stringify(logData), (err, result) => {
if (err) reject(err)
else resolve(result)
})
})
// 构建目标 URL
const protocol = this.tls ? "https" : "http"
const url = `${protocol}://${this.host}:${this.port}${this.uri}`
// 使用 fetch 替代 axios 发送数据到 OpenObserve
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Encoding": "gzip",
Authorization: `Basic ${Buffer.from(`${this.username}:${this.password}`).toString("base64")}`,
},
body: compressedData,
})
// 检查响应状态
if (!response.ok) {
throw new Error(
`OpenObserve 响应错误: ${response.status} ${response.statusText}`
)
}
} catch (error) {
console.error("向 OpenObserve 发送日志时出错:", error)
}
// 执行回调,表示处理完成
callback()
}
}
/**
*
* OpenObserve
*/
const transports: Array<winston.transport> = [
// 控制台传输器,生产环境使用 info 级别,开发环境使用 silly 级别
new winston.transports.Console({ new winston.transports.Console({
level: isProd ? "info" : "silly", level: isProd ? "info" : "silly",
}), }),
// OpenObserve 传输器配置
new OpenObserveTransport({
level: "silly",
host: "lark-egg-ob-preview.ai.xiaomi.com",
port: 443,
uri: "/api/default/default/_json",
username: "zhaoyingbo@live.cn",
password: "GI81PQiPQvHtRBMV",
tls: true,
}),
] ]
if (isProd) { /**
const config = { *
datePattern: "YYYY-MM-DD", *
zippedArchive: true, */
maxSize: "20m",
maxFiles: "14d",
}
transports.push(
new DailyRotateFile({
level: "debug",
filename:
process.env.LOG_FILE_NAME_DEBUG ??
"/home/work/log/egg-debug-%DATE%.log",
...config,
})
)
transports.push(
new DailyRotateFile({
level: "silly",
filename:
process.env.LOG_FILE_NAME_SILLY ??
"/home/work/log/egg-silly-%DATE%.log",
...config,
})
)
}
const formatList = [ const formatList = [
format.simple(), // 简单文本格式化 format.simple(), // 简单文本格式化
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // 添加格式化的时间戳
format.json(), // json格式化 format.json(), // JSON 格式输出
// format.printf(({ level, message, timestamp, requestId }) => {
// const singleLineMessage = isProd
// ? message.replace(/\n/g, " ") // 将换行符替换为空格
// : message
// return `${timestamp} [${level}]${requestId ? ` [RequestId: ${requestId}]` : ""}: ${singleLineMessage}`
// }),
] ]
/**
* Winston
* @type {winston.Logger}
*/
const logger = winston.createLogger({ const logger = winston.createLogger({
level: "silly", level: "silly", // 设置最低日志级别
format: format.combine.apply(null, formatList), format: format.combine.apply(null, formatList), // 应用格式化处理器
transports, transports, // 设置传输器
}) })
export default logger export default logger