package middleware import ( "sort" "strings" "time" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" "hy2xs-admin/model/constant" ) func InitLog() { logrus.SetOutput(&lumberjack.Logger{ Filename: constant.SystemLogPath, MaxSize: 1, MaxBackups: 2, MaxAge: 30, Compress: true, LocalTime: true, }) logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"}) logrus.SetLevel(logrus.InfoLevel) } // requestQueryKeys возвращает ИМЕНА query-параметров запроса, без значений. // // Для диагностики важно, был ли параметр передан, а не что в нём лежало. // Список сортируется, чтобы одинаковые запросы давали одинаковую строку в // логе и её можно было группировать. func requestQueryKeys(c *gin.Context) string { query := c.Request.URL.Query() if len(query) == 0 { return "" } keys := make([]string, 0, len(query)) for key := range query { keys = append(keys, key) } sort.Strings(keys) return strings.Join(keys, ",") } // LogHandler пишет журнал HTTP-запросов админки. // // В лог попадает ПУТЬ, а не RequestURI. // // Разница здесь не косметическая. RequestURI включает query string, а Hysteria // обращается к машинному endpoint'у как // /internal/hysteria/auth?access_token= — то есть при каждом // подключении пира. Пока логировался RequestURI, действующий machine token // оседал открытым текстом в /var/log/hy2xs/hy2xs-admin.log, а этот файл // отдаётся оператору через ExportLog и попадает в diagnostics-бандл. // Получался постоянный канал утечки секрета в обычной штатной работе, мимо // всей структурной редакции, сделанной для конфигов и env. // // Значения query-параметров не логируются вовсе: список того, «что можно», при // таком подходе пришлось бы поддерживать вручную и он неизбежно разошёлся бы с // набором маршрутов. Имена параметров сохранены — их для диагностики хватает. func LogHandler() gin.HandlerFunc { return func(c *gin.Context) { startTime := time.Now() c.Next() statusCode := c.Writer.Status() latencyTime := time.Since(startTime) fields := logrus.Fields{ "statusCode": statusCode, "latencyTime": latencyTime.Milliseconds(), "clientIP": c.ClientIP(), "reqMethod": c.Request.Method, "reqPath": c.Request.URL.Path, } if keys := requestQueryKeys(c); keys != "" { fields["reqQueryKeys"] = keys } entry := logrus.WithFields(fields) if statusCode >= 500 { entry.Error() } else if statusCode >= 400 { entry.Warn() } else { entry.Info() } } }