feat(logger): 添加 OpenObserve 传输器以支持日志发送,移除不必要的依赖
All checks were successful
/ release (push) Successful in 30s
All checks were successful
/ release (push) Successful in 30s
This commit is contained in:
parent
50bef945f7
commit
1577c02ed3
1951
package-lock.json
generated
1951
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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": "*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user