204 lines
6.8 KiB
Go
204 lines
6.8 KiB
Go
package errorlog
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/goravel/framework/contracts/http"
|
|
"github.com/goravel/framework/facades"
|
|
|
|
"goravel/app/models"
|
|
"goravel/app/utils/logger"
|
|
"goravel/app/utils/traceid"
|
|
)
|
|
|
|
// RecordHTTP 同时记录文件日志和数据库日志(用于系统级错误,默认 error 级别)
|
|
// 使用场景:数据库操作失败、系统服务异常、关键业务逻辑错误等
|
|
//
|
|
// 示例:
|
|
//
|
|
// if err != nil {
|
|
// errorlog.RecordHTTP(ctx, "auth", "Failed to save admin profile", map[string]any{
|
|
// "error": err.Error(),
|
|
// "admin_id": admin.ID,
|
|
// }, "Save admin profile error: %v", err)
|
|
// return response.Error(ctx, http.StatusInternalServerError, "update_failed")
|
|
// }
|
|
func RecordHTTP(ctx http.Context, module, message string, attributes map[string]any, format string, args ...any) {
|
|
RecordHTTPWithLevel(ctx, "error", module, message, attributes, format, args...)
|
|
}
|
|
|
|
// RecordHTTPWithLevel 同时记录文件日志和数据库日志(可指定日志级别)
|
|
// level: 日志级别,支持 "error", "warning", "info", "debug"
|
|
// 使用场景:需要记录不同级别的系统日志
|
|
//
|
|
// 示例:
|
|
//
|
|
// // 记录警告
|
|
// errorlog.RecordHTTPWithLevel(ctx, "warning", "auth", "Unusual login pattern detected", map[string]any{
|
|
// "admin_id": admin.ID,
|
|
// "ip": ctx.Request().Ip(),
|
|
// }, "Unusual login pattern: %s", pattern)
|
|
//
|
|
// // 记录信息
|
|
// errorlog.RecordHTTPWithLevel(ctx, "info", "payment", "Payment processed successfully", map[string]any{
|
|
// "order_id": order.ID,
|
|
// "amount": order.Amount,
|
|
// }, "Payment processed: %d", order.ID)
|
|
func RecordHTTPWithLevel(ctx http.Context, level, module, message string, attributes map[string]any, format string, args ...any) {
|
|
// 清理和验证日志级别,防止伪造
|
|
level = sanitizeLogLevel(level)
|
|
|
|
// 清理日志格式字符串,防止格式字符串注入
|
|
sanitizedFormat := sanitizeLogFormat(format)
|
|
|
|
// 清理日志参数,防止注入攻击
|
|
sanitizedArgs := sanitizeLogArgs(args...)
|
|
|
|
// 根据级别选择不同的日志函数
|
|
// 注意:logger 包目前只有 ErrorfHTTP,所以所有级别都使用它
|
|
// 但在日志消息中添加级别前缀,便于区分和过滤
|
|
levelPrefix := "[" + strings.ToUpper(level) + "] "
|
|
formattedMessage := levelPrefix + fmt.Sprintf(sanitizedFormat, sanitizedArgs...)
|
|
|
|
switch level {
|
|
case "error":
|
|
logger.ErrorfHTTP(ctx, formattedMessage)
|
|
case "warning":
|
|
logger.ErrorfHTTP(ctx, formattedMessage)
|
|
case "info", "debug":
|
|
logger.ErrorfHTTP(ctx, formattedMessage)
|
|
default:
|
|
logger.ErrorfHTTP(ctx, formattedMessage)
|
|
}
|
|
|
|
// 记录到数据库(所有级别都记录 trace_id)
|
|
// 清理 message 和 attributes,防止注入
|
|
if ctx != nil {
|
|
sanitizedMessage := sanitizeLogString(message, 500) // 限制消息长度
|
|
sanitizedAttributes := sanitizeAttributes(attributes)
|
|
recordToDatabaseHTTPWithLevel(ctx, level, module, sanitizedMessage, sanitizedAttributes)
|
|
}
|
|
}
|
|
|
|
// Record 同时记录文件日志和数据库日志(用于标准 context,默认 error 级别)
|
|
// 使用场景:goroutine、后台任务等
|
|
//
|
|
// 示例:
|
|
//
|
|
// go func(ctx context.Context) {
|
|
// if err != nil {
|
|
// errorlog.Record(ctx, "operation-log", "Failed to create operation log", map[string]any{
|
|
// "error": err.Error(),
|
|
// }, "Create operation log error: %v", err)
|
|
// }
|
|
// }(traceCtx)
|
|
func Record(ctx context.Context, module, message string, attributes map[string]any, format string, args ...any) {
|
|
RecordWithLevel(ctx, "error", module, message, attributes, format, args...)
|
|
}
|
|
|
|
// RecordWithLevel 同时记录文件日志和数据库日志(可指定日志级别,用于标准 context)
|
|
// level: 日志级别,支持 "error", "warning", "info", "debug"
|
|
// 使用场景:goroutine、后台任务中需要记录不同级别的日志
|
|
//
|
|
// 示例:
|
|
//
|
|
// go func(ctx context.Context) {
|
|
// errorlog.RecordWithLevel(ctx, "info", "background-task", "Task completed", map[string]any{
|
|
// "task_id": taskID,
|
|
// }, "Background task completed: %s", taskID)
|
|
// }(traceCtx)
|
|
func RecordWithLevel(ctx context.Context, level, module, message string, attributes map[string]any, format string, args ...any) {
|
|
// 清理和验证日志级别,防止伪造
|
|
level = sanitizeLogLevel(level)
|
|
|
|
// 清理日志格式字符串,防止格式字符串注入
|
|
sanitizedFormat := sanitizeLogFormat(format)
|
|
|
|
// 清理日志参数,防止注入攻击
|
|
sanitizedArgs := sanitizeLogArgs(args...)
|
|
|
|
// 根据级别选择不同的日志函数
|
|
// 注意:logger 包目前只有 ErrorfContext,所以所有级别都使用它
|
|
// 但在日志消息中添加级别前缀,便于区分和过滤
|
|
levelPrefix := "[" + strings.ToUpper(level) + "] "
|
|
formattedMessage := levelPrefix + fmt.Sprintf(sanitizedFormat, sanitizedArgs...)
|
|
|
|
switch level {
|
|
case "error":
|
|
logger.ErrorfContext(ctx, formattedMessage)
|
|
case "warning", "info", "debug":
|
|
logger.ErrorfContext(ctx, formattedMessage)
|
|
default:
|
|
logger.ErrorfContext(ctx, formattedMessage)
|
|
}
|
|
|
|
// 记录到数据库(所有级别都记录 trace_id)
|
|
// 清理 message 和 attributes,防止注入
|
|
if ctx != nil {
|
|
sanitizedMessage := sanitizeLogString(message, 1000) // 限制消息长度
|
|
sanitizedAttributes := sanitizeAttributes(attributes)
|
|
recordToDatabaseWithLevel(ctx, level, module, sanitizedMessage, sanitizedAttributes)
|
|
}
|
|
}
|
|
|
|
// recordToDatabaseHTTPWithLevel 将日志记录到数据库(HTTP context,支持所有级别)
|
|
func recordToDatabaseHTTPWithLevel(ctx http.Context, level, module, message string, attributes map[string]any) {
|
|
var contextJSON string
|
|
if len(attributes) > 0 {
|
|
if data, err := json.Marshal(attributes); err == nil {
|
|
contextJSON = string(data)
|
|
}
|
|
}
|
|
|
|
traceID := traceid.FromHTTPContext(ctx)
|
|
if traceID == "" {
|
|
traceID = traceid.EnsureHTTPContext(ctx, "")
|
|
}
|
|
|
|
log := models.SystemLog{
|
|
Level: level,
|
|
Module: module,
|
|
TraceID: traceID,
|
|
Message: message,
|
|
Context: contextJSON,
|
|
IP: ctx.Request().Ip(),
|
|
UserAgent: ctx.Request().Header("User-Agent", ""),
|
|
}
|
|
|
|
_ = facades.Orm().Query().Create(&log)
|
|
}
|
|
|
|
// recordToDatabaseWithLevel 将日志记录到数据库(标准 context,支持所有级别)
|
|
func recordToDatabaseWithLevel(ctx context.Context, level, module, message string, attributes map[string]any) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
var contextJSON string
|
|
if len(attributes) > 0 {
|
|
if data, err := json.Marshal(attributes); err == nil {
|
|
contextJSON = string(data)
|
|
}
|
|
}
|
|
|
|
traceID := traceid.FromContext(ctx)
|
|
if traceID == "" {
|
|
var newCtx context.Context
|
|
newCtx, traceID = traceid.EnsureContext(ctx)
|
|
ctx = newCtx
|
|
}
|
|
|
|
log := models.SystemLog{
|
|
Level: level,
|
|
Module: module,
|
|
TraceID: traceID,
|
|
Message: message,
|
|
Context: contextJSON,
|
|
}
|
|
|
|
_ = facades.Orm().Query().Create(&log)
|
|
}
|