gin 操作日志实现步骤

goBlog 项目目录




1.创建日志文件

mkdir storeage/logs/logs.log

2.sudo vim bootstrap/logger.go 初始化 日志的文件

package bootstrap

import (
	"github.com/spf13/cast"
	"github.com/spf13/viper"
	"goBlog/pkg/logger"
)

// SetupLogger 初始化 Logger
func SetupLogger() {
	logger.InitLogger(
		"storage/logs/logs.log",
		64,
		5,
		30,
		true,
		cast.ToString(viper.Get("log.log_type")),
		cast.ToString(viper.Get("log.log_level")),
	)
}

3.sudo vim pkg/logger/logger.go 日志包

// Package logger.go 处理日志相关逻辑
package logger

import (
	"encoding/json"
	"fmt"
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
	"strings"
	"time"

	"github.com/spf13/viper"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// Logger 全局 Logger 对象
var Logger *zap.Logger

// InitLogger 日志初始化
func InitLogger(filename string, maxSize, maxBackup, maxAge int, compress bool, logType string, level string) {

	// 获取日志写入介质
	writeSyncer := getLogWriter(filename, maxSize, maxBackup, maxAge, compress, logType)

	// 设置日志等级,具体请见 config/log.go 文件
	logLevel := new(zapcore.Level)
	if err := logLevel.UnmarshalText([]byte(level)); err != nil {
		fmt.Println("日志初始化错误,日志级别设置有误。请修改 config/log.go 文件中的 log.level 配置项")
	}

	// 初始化 core
	core := zapcore.NewCore(getEncoder(), writeSyncer, logLevel)

	// 初始化 Logger
	Logger = zap.New(core,
		zap.AddCaller(),                   // 调用文件和行号,内部使用 runtime.Caller
		zap.AddCallerSkip(1),              // 封装了一层,调用文件去除一层(runtime.Caller(1))
		zap.AddStacktrace(zap.ErrorLevel), // Error 时才会显示 stacktrace
	)

	// 将自定义的 logger.go 替换为全局的 logger.go
	// zap.L().Fatal() 调用时,就会使用我们自定的 Logger
	zap.ReplaceGlobals(Logger)
}

// getEncoder 设置日志存储格式
func getEncoder() zapcore.Encoder {

	// 日志格式规则
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger.go",
		CallerKey:      "caller", // 代码调用,如 paginator/paginator.go:148
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "message",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,      // 每行日志的结尾添加 "\n"
		EncodeLevel:    zapcore.CapitalLevelEncoder,    // 日志级别名称大写,如 ERROR、INFO
		EncodeTime:     customTimeEncoder,              // 时间格式,我们自定义为 2006-01-02 15:04:05
		EncodeDuration: zapcore.SecondsDurationEncoder, // 执行时间,以秒为单位
		EncodeCaller:   zapcore.ShortCallerEncoder,     // Caller 短格式,如:types/converter.go:17,长格式为绝对路径
	}

	// 本地环境配置
	if viper.Get("app_env") == "local" {
		// 终端输出的关键词高亮
		encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
		// 本地设置内置的 Console 解码器(支持 stacktrace 换行)
		return zapcore.NewConsoleEncoder(encoderConfig)
	}

	// 线上环境使用 JSON 编码器
	return zapcore.NewJSONEncoder(encoderConfig)
}

// customTimeEncoder 自定义友好的时间格式
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.Format("2006-01-02 15:04:05"))
}

// getLogWriter 日志记录介质。Gohub 中使用了两种介质,os.Stdout 和文件
func getLogWriter(filename string, maxSize, maxBackup, maxAge int, compress bool, logType string) zapcore.WriteSyncer {

	// 如果配置了按照日期记录日志文件
	if logType == "daily" {
		logname := time.Now().Format("2006-01-02.log")
		filename = strings.ReplaceAll(filename, "logs.log", logname)
	}

	// 滚动日志,详见 config/log.go
	lumberJackLogger := &lumberjack.Logger{
		Filename:   filename,
		MaxSize:    maxSize,
		MaxBackups: maxBackup,
		MaxAge:     maxAge,
		Compress:   compress,
	}

	// 配置输出介质
	if viper.Get("app_env") == "local" {
		// 本地开发终端打印和记录文件
		return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(lumberJackLogger))
	} else {
		// 生产环境只记录文件
		return zapcore.AddSync(lumberJackLogger)
	}
}

// Dump 调试专用,不会中断程序,会在终端打印出 warning 消息。
// 第一个参数会使用 json.Marshal 进行渲染,第二个参数消息(可选)
//         logger.Dump(user.User{Name:"test"})
//         logger.Dump(user.User{Name:"test"}, "用户信息")
func Dump(value interface{}, msg ...string) {
	valueString := jsonString(value)
	// 判断第二个参数是否传参 msg
	if len(msg) > 0 {
		Logger.Warn("Dump", zap.String(msg[0], valueString))
	} else {
		Logger.Warn("Dump", zap.String("data", valueString))
	}
}

// LogIf 当 err != nil 时记录 error 等级的日志
func LogIf(err error) {
	if err != nil {
		Logger.Error("Error Occurred:", zap.Error(err))
	}
}

// LogWarnIf 当 err != nil 时记录 warning 等级的日志
func LogWarnIf(err error) {
	if err != nil {
		Logger.Warn("Error Occurred:", zap.Error(err))
	}
}

// LogInfoIf 当 err != nil 时记录 info 等级的日志
func LogInfoIf(err error) {
	if err != nil {
		Logger.Info("Error Occurred:", zap.Error(err))
	}
}

// Debug 调试日志,详尽的程序日志
// 调用示例:
//        logger.Debug("Database", zap.String("sql", sql))
func Debug(moduleName string, fields ...zap.Field) {
	Logger.Debug(moduleName, fields...)
}

// Info 告知类日志
func Info(moduleName string, fields ...zap.Field) {
	Logger.Info(moduleName, fields...)
}

// Warn 警告类
func Warn(moduleName string, fields ...zap.Field) {
	Logger.Warn(moduleName, fields...)
}

// Error 错误时记录,不应该中断程序,查看日志时重点关注
func Error(moduleName string, fields ...zap.Field) {
	Logger.Error(moduleName, fields...)
}

// Fatal 级别同 Error(), 写完 log 后调用 os.Exit(1) 退出程序
func Fatal(moduleName string, fields ...zap.Field) {
	Logger.Fatal(moduleName, fields...)
}

// DebugString 记录一条字符串类型的 debug 日志,调用示例:
//         logger.DebugString("SMS", "短信内容", string(result.RawResponse))
func DebugString(moduleName, name, msg string) {
	Logger.Debug(moduleName, zap.String(name, msg))
}

func InfoString(moduleName, name, msg string) {
	Logger.Info(moduleName, zap.String(name, msg))
}

func WarnString(moduleName, name, msg string) {
	Logger.Warn(moduleName, zap.String(name, msg))
}

func ErrorString(moduleName, name, msg string) {
	Logger.Error(moduleName, zap.String(name, msg))
}

func FatalString(moduleName, name, msg string) {
	Logger.Fatal(moduleName, zap.String(name, msg))
}

// DebugJSON 记录对象类型的 debug 日志,使用 json.Marshal 进行编码。调用示例:
//         logger.DebugJSON("Auth", "读取登录用户", auth.CurrentUser())
func DebugJSON(moduleName, name string, value interface{}) {
	Logger.Debug(moduleName, zap.String(name, jsonString(value)))
}

func InfoJSON(moduleName, name string, value interface{}) {
	Logger.Info(moduleName, zap.String(name, jsonString(value)))
}

func WarnJSON(moduleName, name string, value interface{}) {
	Logger.Warn(moduleName, zap.String(name, jsonString(value)))
}

func ErrorJSON(moduleName, name string, value interface{}) {
	Logger.Error(moduleName, zap.String(name, jsonString(value)))
}

func FatalJSON(moduleName, name string, value interface{}) {
	Logger.Fatal(moduleName, zap.String(name, jsonString(value)))
}

func jsonString(value interface{}) string {
	b, err := json.Marshal(value)
	if err != nil {
		Logger.Error("Logger", zap.String("JSON marshal error", err.Error()))
	}
	return string(b)
}


4.sudo vim middlewares/middleware_log/logger.go 中间件

package middleware_log

import (
	"bytes"
	"goBlog/pkg/helps"
	"goBlog/pkg/logger"
	"io/ioutil"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/spf13/cast"
	"go.uber.org/zap"
)

type responseBodyWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (r responseBodyWriter) Write(b []byte) (int, error) {
	r.body.Write(b)
	return r.ResponseWriter.Write(b)
}

// Logger 记录请求日志
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {

		// 获取 response 内容
		w := &responseBodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
		c.Writer = w

		// 获取请求数据
		var requestBody []byte
		if c.Request.Body != nil {
			// c.Request.Body 是一个 buffer 对象,只能读取一次
			requestBody, _ = ioutil.ReadAll(c.Request.Body)
			// 读取后,重新赋值 c.Request.Body ,以供后续的其他操作
			c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(requestBody))
		}

		// 设置开始时间
		start := time.Now()
		c.Next()

		// 开始记录日志的逻辑
		cost := time.Since(start)
		resposeStatus := c.Writer.Status()

		logFields := []zap.Field{
			zap.Int("status", resposeStatus),
			zap.String("request", c.Request.Method+" "+c.Request.URL.String()),
			zap.String("query", c.Request.URL.RawQuery),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.String("time", helpers.MicrosecondsStr(cost)),
		}
		if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
			// 请求的内容
			logFields = append(logFields, zap.String("Request Body", string(requestBody)))

			// 响应的内容
			logFields = append(logFields, zap.String("Response Body", w.body.String()))
		}

		if resposeStatus > 400 && resposeStatus <= 499 {
			// 除了 StatusBadRequest 以外,warning 提示一下,常见的有 403 404,开发时都要注意
			logger.Warn("HTTP Warning "+cast.ToString(resposeStatus), logFields...)
		} else if resposeStatus >= 500 && resposeStatus <= 599 {
			// 除了内部错误,记录 error
			logger.Error("HTTP Error "+cast.ToString(resposeStatus), logFields...)
		} else {
			logger.Debug("HTTP Access Log", logFields...)
		}
	}
}


5.将 main.go 加上
bootstrap.SetupLogger()


6.配置文件 config.toml 追加
log_level = “DEBUG”


7.给文件赋予权限
chmod -R 777 storage/logs/logs.log

<think>好的,我现在需要帮助用户解决如何使用Go语言Gin框架实现登录功能的问题。首先,我得确认用户的需求是什么。用户提到了参考的引用内容,特别是引用[1]中提到了使用Go和Gin构建Web应用并添加认证,这可能包含用户需要的登录功能。另外,引用[2]和[3]提到的库可能对测试和Windows集成有帮助,但用户当前的问题主要集中在登录功能的实现上。 接下来,我需要回忆Gin框架的基本结构和处理HTTP请求的方法。Gin是一个高性能的HTTP框架,使用中间件来处理请求。登录功能通常涉及接收用户凭证、验证、生成会话或令牌等步骤。用户可能需要了解如何路由处理、中间件验证以及安全存储密码等方面的知识。 然后,我应该考虑用户可能的实现步骤。首先,设置路由处理登录请求,比如POST /login。然后解析用户提交的用户名和密码。接着,需要验证这些凭证,可能涉及数据库查询。这里可能需要使用到Go的数据库库,比如database/sql和相应的驱动。密码安全方面,必须使用哈希处理,比如bcrypt,而不能明文存储。之后,生成并返回认证令牌,比如JWT,用于后续请求的验证。 另外,用户可能关心如何保护路由,确保只有登录用户才能访问某些资源。这可以通过中间件来实现,验证请求中的令牌是否有效。中间件会检查Authorization头中的JWT,解析并验证其有效性,然后允许或拒绝访问。 还需要考虑错误处理,比如无效凭证的情况返回401状态码,服务器错误返回500等。同时,引用[2]提到的gomock或gock可能用于测试登录功能的HTTP请求和数据库交互,确保代码的可靠性。 可能用户没有明确提到的点包括密码哈希的具体实现、JWT的生成和解析细节、以及如何安全地存储密钥。这些都需要在回答中详细说明,避免安全漏洞。例如,使用bcrypt的GenerateFromPassword和CompareHashAndPassword函数来处理密码哈希和验证。 另外,引用[1]中的示例可能包含具体的代码结构,比如路由定义、控制器函数等。需要参考这些结构,确保代码示例的正确性。同时,要提醒用户注意环境配置,比如安装Gin框架、JWT库以及数据库驱动。 最后,生成的相关问题需要围绕登录功能的扩展,比如安全加固、第三方登录、会话管理、测试方法等,帮助用户进一步学习和解决问题。</think>以下是如何使用Go语言Gin框架实现登录功能的详细步骤: ### 一、基础环境配置 1. 安装所需依赖: ```bash go get -u github.com/gin-gonic/gin go get -u github.com/golang-jwt/jwt/v5 go get -u golang.org/x/crypto/bcrypt ``` ### 二、核心功能实现 #### 1. 用户模型定义 ```go type User struct { ID uint `gorm:"primaryKey"` Username string `gorm:"unique"` Password string } ``` #### 2. 密码安全处理 ```go // 密码加密 func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) return string(bytes), err } // 密码验证 func CheckPasswordHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } ``` #### 3. JWT令牌生成 ```go func GenerateToken(username string) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": username, "exp": time.Now().Add(time.Hour * 24).Unix(), }) return token.SignedString([]byte("your-secret-key")) } ``` ### 三、路由配置 ```go func main() { r := gin.Default() // 登录接口 r.POST("/login", func(c *gin.Context) { var credentials struct { Username string `json:"username"` Password string `json:"password"` } if err := c.ShouldBindJSON(&credentials); err != nil { c.JSON(400, gin.H{"error": "无效的请求格式"}) return } // 数据库验证(示例伪代码) user, err := db.FindUser(credentials.Username) if err != nil || !CheckPasswordHash(credentials.Password, user.Password) { c.JSON(401, gin.H{"error": "无效的用户名或密码"}) return } token, err := GenerateToken(user.Username) if err != nil { c.JSON(500, gin.H{"error": "无法生成令牌"}) return } c.JSON(200, gin.H{"token": token}) }) r.Run(":8080") } ``` ### 四、中间件验证 ```go func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { tokenString := c.GetHeader("Authorization") if tokenString == "" { c.AbortWithStatusJSON(401, gin.H{"error": "需要认证"}) return } token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("非预期的签名方法: %v", token.Header["alg"]) } return []byte("your-secret-key"), nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { c.Set("username", claims["username"]) c.Next() } else { c.AbortWithStatusJSON(401, gin.H{"error": "无效的令牌"}) } } } ``` ### 五、安全增强建议 1. 使用HTTPS加密通信 2. 设置合理的令牌有效期(建议不超过24小时) 3. 实现刷新令牌机制 4. 使用环境变量存储密钥(避免硬编码) 5. 记录登录审计日志
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值