Compare commits

...

1 Commits

Author SHA1 Message Date
Yening Qin
b49ab44818 refactor: http support tracing (#3083) 2026-02-12 17:00:45 +08:00
86 changed files with 894 additions and 170 deletions

View File

@@ -54,6 +54,7 @@ func (rt *Router) Config(r *gin.Engine) {
service.POST("/make-event", rt.makeEvent)
service.GET("/event-detail/:hash", rt.eventDetail)
service.GET("/alert-eval-detail/:id", rt.alertEvalDetail)
service.GET("/trace-logs/:traceid", rt.traceLogs)
}
func Render(c *gin.Context, data, msg interface{}) {

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"github.com/ccfos/nightingale/v6/pkg/loggrep"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) alertEvalDetail(c *gin.Context) {

View File

@@ -13,9 +13,9 @@ import (
"github.com/ccfos/nightingale/v6/alert/queue"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"github.com/ccfos/nightingale/v6/pkg/loggrep"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) eventDetail(c *gin.Context) {

View File

@@ -0,0 +1,28 @@
package router
import (
"fmt"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/ccfos/nightingale/v6/pkg/loggrep"
"github.com/gin-gonic/gin"
)
func (rt *Router) traceLogs(c *gin.Context) {
traceId := ginx.UrlParamStr(c, "traceid")
if !loggrep.IsValidTraceID(traceId) {
ginx.Bomb(200, "invalid trace id format")
}
instance := fmt.Sprintf("%s:%d", rt.Alert.Heartbeat.IP, rt.HTTP.Port)
keyword := "trace_id=" + traceId
logs, err := loggrep.GrepLatestLogFiles(rt.LogDir, keyword)
ginx.Dangerous(err)
ginx.NewRender(c).Data(loggrep.EventDetailResp{
Logs: logs,
Instance: instance,
}, nil)
}

View File

@@ -24,11 +24,11 @@ import (
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/storage"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"gorm.io/gorm"
"github.com/gin-gonic/gin"
"github.com/rakyll/statik/fs"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
@@ -421,6 +421,7 @@ func (rt *Router) Config(r *gin.Engine) {
pages.GET("/event-notify-records/:eid", rt.notificationRecordList)
pages.GET("/event-detail/:hash", rt.eventDetailPage)
pages.GET("/alert-eval-detail/:id", rt.alertEvalDetailPage)
pages.GET("/trace-logs/:traceid", rt.traceLogsPage)
// card logic
pages.GET("/alert-cur-events/list", rt.auth(), rt.user(), rt.alertCurEventsList)

View File

@@ -4,9 +4,9 @@ import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// no param

View File

@@ -10,9 +10,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -12,9 +12,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/loggrep"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// alertEvalDetailPage renders an HTML log viewer page for alert rule evaluation logs.

View File

@@ -8,9 +8,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"golang.org/x/exp/slices"
)

View File

@@ -16,12 +16,12 @@ import (
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pushgw/pconf"
"github.com/ccfos/nightingale/v6/pushgw/writer"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/prompb"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -9,9 +9,9 @@ import (
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -7,9 +7,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -8,10 +8,10 @@ import (
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)

View File

@@ -5,9 +5,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"gorm.io/gorm"
)

View File

@@ -3,8 +3,8 @@ package router
import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/prom"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) metricFilterGets(c *gin.Context) {

View File

@@ -7,9 +7,9 @@ import (
"github.com/ccfos/nightingale/v6/center/integration"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -9,8 +9,8 @@ import (
"github.com/BurntSushi/toml"
"github.com/ccfos/nightingale/v6/center/integration"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -5,9 +5,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -5,9 +5,9 @@ import (
"time"
"github.com/ccfos/nightingale/v6/storage"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
captcha "github.com/mojocn/base64Captcha"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -5,9 +5,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) chartShareGets(c *gin.Context) {

View File

@@ -4,9 +4,9 @@ import (
"encoding/json"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) notifyChannelsGets(c *gin.Context) {

View File

@@ -4,9 +4,9 @@ import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
const EMBEDDEDDASHBOARD = "embedded-dashboards"

View File

@@ -2,9 +2,9 @@ package router
import (
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
type confPropCrypto struct {

View File

@@ -7,9 +7,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func checkAnnotationPermission(c *gin.Context, ctx *ctx.Context, dashboardId int64) {

View File

@@ -15,8 +15,8 @@ import (
"github.com/ccfos/nightingale/v6/datasource/opensearch"
"github.com/ccfos/nightingale/v6/dskit/clickhouse"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
"github.com/toolkits/pkg/logger"
)

View File

@@ -6,10 +6,10 @@ import (
"github.com/ccfos/nightingale/v6/dscache"
"github.com/ccfos/nightingale/v6/dskit/types"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
func (rt *Router) ShowDatabases(c *gin.Context) {
@@ -18,7 +18,7 @@ func (rt *Router) ShowDatabases(c *gin.Context) {
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(c.Request.Context(), "cluster:%d not exists", f.DatasourceId)
ginx.Bomb(200, "cluster not exists")
}
@@ -48,7 +48,7 @@ func (rt *Router) ShowTables(c *gin.Context) {
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(c.Request.Context(), "cluster:%d not exists", f.DatasourceId)
ginx.Bomb(200, "cluster not exists")
}
@@ -78,7 +78,7 @@ func (rt *Router) DescribeTable(c *gin.Context) {
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(c.Request.Context(), "cluster:%d not exists", f.DatasourceId)
ginx.Bomb(200, "cluster not exists")
}
// 只接受一个入参

View File

@@ -5,9 +5,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) embeddedProductGets(c *gin.Context) {

View File

@@ -3,10 +3,10 @@ package router
import (
"github.com/ccfos/nightingale/v6/datasource/es"
"github.com/ccfos/nightingale/v6/dscache"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type IndexReq struct {
@@ -34,7 +34,7 @@ func (rt *Router) QueryIndices(c *gin.Context) {
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(c.Request.Context(), "cluster:%d not exists", f.DatasourceId)
ginx.Bomb(200, "cluster not exists")
}
@@ -50,7 +50,7 @@ func (rt *Router) QueryFields(c *gin.Context) {
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(c.Request.Context(), "cluster:%d not exists", f.DatasourceId)
ginx.Bomb(200, "cluster not exists")
}
@@ -66,7 +66,7 @@ func (rt *Router) QueryESVariable(c *gin.Context) {
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(c.Request.Context(), "cluster:%d not exists", f.DatasourceId)
ginx.Bomb(200, "cluster not exists")
}

View File

@@ -5,8 +5,8 @@ import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// 创建 ES Index Pattern

View File

@@ -11,9 +11,9 @@ import (
"github.com/ccfos/nightingale/v6/alert/naming"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/loggrep"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// eventDetailPage renders an HTML log viewer page (for pages group).

View File

@@ -8,10 +8,10 @@ import (
"github.com/ccfos/nightingale/v6/alert/pipeline/engine"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
"github.com/toolkits/pkg/logger"
)

View File

@@ -7,9 +7,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
const defaultLimit = 300

View File

@@ -15,9 +15,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -14,16 +14,16 @@ import (
"github.com/ccfos/nightingale/v6/pkg/dingtalk"
"github.com/ccfos/nightingale/v6/pkg/feishu"
"github.com/ccfos/nightingale/v6/pkg/ldapx"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/pkg/oauth2x"
"github.com/ccfos/nightingale/v6/pkg/oidcx"
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/pelletier/go-toml/v2"
"github.com/pkg/errors"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"gorm.io/gorm"
)
@@ -37,7 +37,9 @@ type loginForm struct {
func (rt *Router) loginPost(c *gin.Context) {
var f loginForm
ginx.BindJSON(c, &f)
logger.Infof("username:%s login from:%s", f.Username, c.ClientIP())
rctx := c.Request.Context()
logx.Infof(rctx, "username:%s login from:%s", f.Username, c.ClientIP())
if rt.HTTP.ShowCaptcha.Enable {
if !CaptchaVerify(f.Captchaid, f.Verifyvalue) {
@@ -50,23 +52,25 @@ func (rt *Router) loginPost(c *gin.Context) {
if rt.HTTP.RSA.OpenRSA {
decPassWord, err := secu.Decrypt(f.Password, rt.HTTP.RSA.RSAPrivateKey, rt.HTTP.RSA.RSAPassWord)
if err != nil {
logger.Errorf("RSA Decrypt failed: %v username: %s", err, f.Username)
logx.Errorf(rctx, "RSA Decrypt failed: %v username: %s", err, f.Username)
ginx.NewRender(c).Message(err)
return
}
authPassWord = decPassWord
}
reqCtx := rt.Ctx.WithContext(rctx)
var user *models.User
var err error
lc := rt.Sso.LDAP.Copy()
if lc.Enable {
user, err = ldapx.LdapLogin(rt.Ctx, f.Username, authPassWord, lc.DefaultRoles, lc.DefaultTeams, lc)
user, err = ldapx.LdapLogin(reqCtx, f.Username, authPassWord, lc.DefaultRoles, lc.DefaultTeams, lc)
if err != nil {
logger.Debugf("ldap login failed: %v username: %s", err, f.Username)
logx.Debugf(rctx, "ldap login failed: %v username: %s", err, f.Username)
var errLoginInN9e error
// to use n9e as the minimum guarantee for login
if user, errLoginInN9e = models.PassLogin(rt.Ctx, rt.Redis, f.Username, authPassWord); errLoginInN9e != nil {
if user, errLoginInN9e = models.PassLogin(reqCtx, rt.Redis, f.Username, authPassWord); errLoginInN9e != nil {
ginx.NewRender(c).Message("ldap login failed: %v; n9e login failed: %v", err, errLoginInN9e)
return
}
@@ -74,7 +78,7 @@ func (rt *Router) loginPost(c *gin.Context) {
user.RolesLst = strings.Fields(user.Roles)
}
} else {
user, err = models.PassLogin(rt.Ctx, rt.Redis, f.Username, authPassWord)
user, err = models.PassLogin(reqCtx, rt.Redis, f.Username, authPassWord)
ginx.Dangerous(err)
}
@@ -98,7 +102,8 @@ func (rt *Router) loginPost(c *gin.Context) {
}
func (rt *Router) logoutPost(c *gin.Context) {
logger.Infof("username:%s logout from:%s", c.GetString("username"), c.ClientIP())
rctx := c.Request.Context()
logx.Infof(rctx, "username:%s logout from:%s", c.GetString("username"), c.ClientIP())
metadata, err := rt.extractTokenMetadata(c.Request)
if err != nil {
ginx.NewRender(c, http.StatusBadRequest).Message("failed to parse jwt token")
@@ -117,7 +122,7 @@ func (rt *Router) logoutPost(c *gin.Context) {
// 获取用户的 id_token
idToken, err := rt.fetchIdToken(c.Request.Context(), user.Id)
if err != nil {
logger.Debugf("fetch id_token failed: %v, user_id: %d", err, user.Id)
logx.Debugf(rctx, "fetch id_token failed: %v, user_id: %d", err, user.Id)
idToken = "" // 如果获取失败,使用空字符串
}
@@ -220,7 +225,7 @@ func (rt *Router) refreshPost(c *gin.Context) {
// 注意:这里不会获取新的 id_token只是延长 Redis 中现有 id_token 的 TTL
if idToken, err := rt.fetchIdToken(c.Request.Context(), userid); err == nil && idToken != "" {
if err := rt.saveIdToken(c.Request.Context(), userid, idToken); err != nil {
logger.Debugf("refresh id_token ttl failed: %v, user_id: %d", err, userid)
logx.Debugf(c.Request.Context(), "refresh id_token ttl failed: %v, user_id: %d", err, userid)
}
}
@@ -271,12 +276,13 @@ type CallbackOutput struct {
}
func (rt *Router) loginCallback(c *gin.Context) {
rctx := c.Request.Context()
code := ginx.QueryStr(c, "code", "")
state := ginx.QueryStr(c, "state", "")
ret, err := rt.Sso.OIDC.Callback(rt.Redis, c.Request.Context(), code, state)
ret, err := rt.Sso.OIDC.Callback(rt.Redis, rctx, code, state)
if err != nil {
logger.Errorf("sso_callback fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
logx.Errorf(rctx, "sso_callback fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
ginx.NewRender(c).Data(CallbackOutput{}, err)
return
}
@@ -299,7 +305,7 @@ func (rt *Router) loginCallback(c *gin.Context) {
for _, gid := range rt.Sso.OIDC.DefaultTeams {
err = models.UserGroupMemberAdd(rt.Ctx, gid, user.Id)
if err != nil {
logger.Errorf("user:%v UserGroupMemberAdd: %s", user, err)
logx.Errorf(rctx, "user:%v UserGroupMemberAdd: %s", user, err)
}
}
}
@@ -309,12 +315,12 @@ func (rt *Router) loginCallback(c *gin.Context) {
userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username)
ts, err := rt.createTokens(rt.HTTP.JWTAuth.SigningKey, userIdentity)
ginx.Dangerous(err)
ginx.Dangerous(rt.createAuth(c.Request.Context(), userIdentity, ts))
ginx.Dangerous(rt.createAuth(rctx, userIdentity, ts))
// 保存 id_token 到 Redis用于登出时使用
if ret.IdToken != "" {
if err := rt.saveIdToken(c.Request.Context(), user.Id, ret.IdToken); err != nil {
logger.Errorf("save id_token failed: %v, user_id: %d", err, user.Id)
if err := rt.saveIdToken(rctx, user.Id, ret.IdToken); err != nil {
logx.Errorf(rctx, "save id_token failed: %v, user_id: %d", err, user.Id)
}
}
@@ -355,7 +361,7 @@ func (rt *Router) loginRedirectCas(c *gin.Context) {
}
if !rt.Sso.CAS.Enable {
logger.Error("cas is not enable")
logx.Errorf(c.Request.Context(), "cas is not enable")
ginx.NewRender(c).Data("", nil)
return
}
@@ -370,17 +376,18 @@ func (rt *Router) loginRedirectCas(c *gin.Context) {
}
func (rt *Router) loginCallbackCas(c *gin.Context) {
rctx := c.Request.Context()
ticket := ginx.QueryStr(c, "ticket", "")
state := ginx.QueryStr(c, "state", "")
ret, err := rt.Sso.CAS.ValidateServiceTicket(c.Request.Context(), ticket, state, rt.Redis)
ret, err := rt.Sso.CAS.ValidateServiceTicket(rctx, ticket, state, rt.Redis)
if err != nil {
logger.Errorf("ValidateServiceTicket: %s", err)
logx.Errorf(rctx, "ValidateServiceTicket: %s", err)
ginx.NewRender(c).Data("", err)
return
}
user, err := models.UserGet(rt.Ctx, "username=?", ret.Username)
if err != nil {
logger.Errorf("UserGet: %s", err)
logx.Errorf(rctx, "UserGet: %s", err)
}
ginx.Dangerous(err)
if user != nil {
@@ -399,10 +406,10 @@ func (rt *Router) loginCallbackCas(c *gin.Context) {
userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username)
ts, err := rt.createTokens(rt.HTTP.JWTAuth.SigningKey, userIdentity)
if err != nil {
logger.Errorf("createTokens: %s", err)
logx.Errorf(rctx, "createTokens: %s", err)
}
ginx.Dangerous(err)
ginx.Dangerous(rt.createAuth(c.Request.Context(), userIdentity, ts))
ginx.Dangerous(rt.createAuth(rctx, userIdentity, ts))
redirect := "/"
if ret.Redirect != "/login" {
@@ -475,12 +482,13 @@ func (rt *Router) loginRedirectDingTalk(c *gin.Context) {
}
func (rt *Router) loginCallbackDingTalk(c *gin.Context) {
rctx := c.Request.Context()
code := ginx.QueryStr(c, "code", "")
state := ginx.QueryStr(c, "state", "")
ret, err := rt.Sso.DingTalk.Callback(rt.Redis, c.Request.Context(), code, state)
ret, err := rt.Sso.DingTalk.Callback(rt.Redis, rctx, code, state)
if err != nil {
logger.Errorf("sso_callback DingTalk fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
logx.Errorf(rctx, "sso_callback DingTalk fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
ginx.NewRender(c).Data(CallbackOutput{}, err)
return
}
@@ -550,12 +558,13 @@ func (rt *Router) loginRedirectFeiShu(c *gin.Context) {
}
func (rt *Router) loginCallbackFeiShu(c *gin.Context) {
rctx := c.Request.Context()
code := ginx.QueryStr(c, "code", "")
state := ginx.QueryStr(c, "state", "")
ret, err := rt.Sso.FeiShu.Callback(rt.Redis, c.Request.Context(), code, state)
ret, err := rt.Sso.FeiShu.Callback(rt.Redis, rctx, code, state)
if err != nil {
logger.Errorf("sso_callback FeiShu fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
logx.Errorf(rctx, "sso_callback FeiShu fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
ginx.NewRender(c).Data(CallbackOutput{}, err)
return
}
@@ -583,7 +592,7 @@ func (rt *Router) loginCallbackFeiShu(c *gin.Context) {
if len(defaultUserGroups) > 0 {
err = user.AddToUserGroups(rt.Ctx, defaultUserGroups)
if err != nil {
logger.Errorf("sso feishu add user group error %v", ret, err)
logx.Errorf(rctx, "sso feishu add user group error %v %v", ret, err)
}
}
@@ -610,12 +619,13 @@ func (rt *Router) loginCallbackFeiShu(c *gin.Context) {
}
func (rt *Router) loginCallbackOAuth(c *gin.Context) {
rctx := c.Request.Context()
code := ginx.QueryStr(c, "code", "")
state := ginx.QueryStr(c, "state", "")
ret, err := rt.Sso.OAuth2.Callback(rt.Redis, c.Request.Context(), code, state)
ret, err := rt.Sso.OAuth2.Callback(rt.Redis, rctx, code, state)
if err != nil {
logger.Debugf("sso.callback() get ret %+v error %v", ret, err)
logx.Debugf(rctx, "sso.callback() get ret %+v error %v", ret, err)
ginx.NewRender(c).Data(CallbackOutput{}, err)
return
}

View File

@@ -12,10 +12,10 @@ import (
"github.com/ccfos/nightingale/v6/pkg/slice"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) messageTemplatesAdd(c *gin.Context) {

View File

@@ -2,9 +2,9 @@ package router
import (
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) metricsDescGetFile(c *gin.Context) {

View File

@@ -4,9 +4,9 @@ import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// no param

View File

@@ -9,9 +9,9 @@ import (
"github.com/ccfos/nightingale/v6/alert/mute"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -11,11 +11,11 @@ import (
"github.com/ccfos/nightingale/v6/center/cstats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/toolkits/pkg/ginx"
)
const (

View File

@@ -6,9 +6,9 @@ import (
"github.com/ccfos/nightingale/v6/alert/sender"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -11,8 +11,8 @@ import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) notifyChannelsAdd(c *gin.Context) {

View File

@@ -10,10 +10,10 @@ import (
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/pelletier/go-toml/v2"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)

View File

@@ -10,9 +10,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/slice"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -11,9 +11,9 @@ import (
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)

View File

@@ -3,9 +3,9 @@ package router
import (
"github.com/ccfos/nightingale/v6/datasource/opensearch"
"github.com/ccfos/nightingale/v6/dscache"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -12,12 +12,13 @@ import (
"sync"
"time"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/pkg/poster"
pkgprom "github.com/ccfos/nightingale/v6/pkg/prom"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/prometheus/common/model"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/net/httplib"
)
@@ -38,15 +39,16 @@ func (rt *Router) promBatchQueryRange(c *gin.Context) {
var f BatchQueryForm
ginx.Dangerous(c.BindJSON(&f))
lst, err := PromBatchQueryRange(rt.PromClients, f)
lst, err := PromBatchQueryRange(c.Request.Context(), rt.PromClients, f)
ginx.NewRender(c).Data(lst, err)
}
func PromBatchQueryRange(pc *prom.PromClientMap, f BatchQueryForm) ([]model.Value, error) {
func PromBatchQueryRange(ctx context.Context, pc *prom.PromClientMap, f BatchQueryForm) ([]model.Value, error) {
var lst []model.Value
cli := pc.GetCli(f.DatasourceId)
if cli == nil {
logx.Warningf(ctx, "no such datasource id: %d", f.DatasourceId)
return lst, fmt.Errorf("no such datasource id: %d", f.DatasourceId)
}
@@ -57,8 +59,9 @@ func PromBatchQueryRange(pc *prom.PromClientMap, f BatchQueryForm) ([]model.Valu
Step: time.Duration(item.Step) * time.Second,
}
resp, _, err := cli.QueryRange(context.Background(), item.Query, r)
resp, _, err := cli.QueryRange(ctx, item.Query, r)
if err != nil {
logx.Warningf(ctx, "query range error: query:%s err:%v", item.Query, err)
return lst, err
}
@@ -81,22 +84,23 @@ func (rt *Router) promBatchQueryInstant(c *gin.Context) {
var f BatchInstantForm
ginx.Dangerous(c.BindJSON(&f))
lst, err := PromBatchQueryInstant(rt.PromClients, f)
lst, err := PromBatchQueryInstant(c.Request.Context(), rt.PromClients, f)
ginx.NewRender(c).Data(lst, err)
}
func PromBatchQueryInstant(pc *prom.PromClientMap, f BatchInstantForm) ([]model.Value, error) {
func PromBatchQueryInstant(ctx context.Context, pc *prom.PromClientMap, f BatchInstantForm) ([]model.Value, error) {
var lst []model.Value
cli := pc.GetCli(f.DatasourceId)
if cli == nil {
logger.Warningf("no such datasource id: %d", f.DatasourceId)
logx.Warningf(ctx, "no such datasource id: %d", f.DatasourceId)
return lst, fmt.Errorf("no such datasource id: %d", f.DatasourceId)
}
for _, item := range f.Queries {
resp, _, err := cli.Query(context.Background(), item.Query, time.Unix(item.Time, 0))
resp, _, err := cli.Query(ctx, item.Query, time.Unix(item.Time, 0))
if err != nil {
logx.Warningf(ctx, "query instant error: query:%s err:%v", item.Query, err)
return lst, err
}
@@ -189,7 +193,7 @@ func (rt *Router) dsProxy(c *gin.Context) {
modifyResponse := func(r *http.Response) error {
if r.StatusCode == http.StatusUnauthorized {
logger.Warningf("proxy path:%s unauthorized access ", c.Request.URL.Path)
logx.Warningf(c.Request.Context(), "proxy path:%s unauthorized access ", c.Request.URL.Path)
return fmt.Errorf("unauthorized access")
}

View File

@@ -8,9 +8,9 @@ import (
"github.com/ccfos/nightingale/v6/alert/eval"
"github.com/ccfos/nightingale/v6/dscache"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type CheckDsPermFunc func(c *gin.Context, dsId int64, cate string, q interface{}) bool
@@ -47,6 +47,7 @@ func QueryLogBatchConcurrently(anonymousAccess bool, ctx *gin.Context, f QueryFr
var mu sync.Mutex
var wg sync.WaitGroup
var errs []error
rctx := ctx.Request.Context()
for _, q := range f.Queries {
if !anonymousAccess && !CheckDsPerm(ctx, q.Did, q.DsCate, q) {
@@ -55,14 +56,14 @@ func QueryLogBatchConcurrently(anonymousAccess bool, ctx *gin.Context, f QueryFr
plug, exists := dscache.DsCache.Get(q.DsCate, q.Did)
if !exists {
logger.Warningf("cluster:%d not exists query:%+v", q.Did, q)
logx.Warningf(rctx, "cluster:%d not exists query:%+v", q.Did, q)
return LogResp{}, fmt.Errorf("cluster not exists")
}
// 根据数据源类型对 Query 进行模板渲染处理
err := eval.ExecuteQueryTemplate(q.DsCate, q.Query, nil)
if err != nil {
logger.Warningf("query template execute error: %v", err)
logx.Warningf(rctx, "query template execute error: %v", err)
return LogResp{}, fmt.Errorf("query template execute error: %v", err)
}
@@ -70,12 +71,12 @@ func QueryLogBatchConcurrently(anonymousAccess bool, ctx *gin.Context, f QueryFr
go func(query Query) {
defer wg.Done()
data, total, err := plug.QueryLog(ctx.Request.Context(), query.Query)
data, total, err := plug.QueryLog(rctx, query.Query)
mu.Lock()
defer mu.Unlock()
if err != nil {
errMsg := fmt.Sprintf("query data error: %v query:%v\n ", err, query)
logger.Warningf(errMsg)
logx.Warningf(rctx, "%s", errMsg)
errs = append(errs, err)
return
}
@@ -121,6 +122,7 @@ func QueryDataConcurrently(anonymousAccess bool, ctx *gin.Context, f models.Quer
var mu sync.Mutex
var wg sync.WaitGroup
var errs []error
rctx := ctx.Request.Context()
for _, q := range f.Queries {
if !anonymousAccess && !CheckDsPerm(ctx, f.DatasourceId, f.Cate, q) {
@@ -129,7 +131,7 @@ func QueryDataConcurrently(anonymousAccess bool, ctx *gin.Context, f models.Quer
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(rctx, "cluster:%d not exists", f.DatasourceId)
return nil, fmt.Errorf("cluster not exists")
}
@@ -137,16 +139,16 @@ func QueryDataConcurrently(anonymousAccess bool, ctx *gin.Context, f models.Quer
go func(query interface{}) {
defer wg.Done()
data, err := plug.QueryData(ctx.Request.Context(), query)
data, err := plug.QueryData(rctx, query)
if err != nil {
logger.Warningf("query data error: req:%+v err:%v", query, err)
logx.Warningf(rctx, "query data error: req:%+v err:%v", query, err)
mu.Lock()
errs = append(errs, err)
mu.Unlock()
return
}
logger.Debugf("query data: req:%+v resp:%+v", query, data)
logx.Debugf(rctx, "query data: req:%+v resp:%+v", query, data)
mu.Lock()
resp = append(resp, data...)
mu.Unlock()
@@ -192,6 +194,7 @@ func QueryLogConcurrently(anonymousAccess bool, ctx *gin.Context, f models.Query
var mu sync.Mutex
var wg sync.WaitGroup
var errs []error
rctx := ctx.Request.Context()
for _, q := range f.Queries {
if !anonymousAccess && !CheckDsPerm(ctx, f.DatasourceId, f.Cate, q) {
@@ -200,7 +203,7 @@ func QueryLogConcurrently(anonymousAccess bool, ctx *gin.Context, f models.Query
plug, exists := dscache.DsCache.Get(f.Cate, f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists query:%+v", f.DatasourceId, f)
logx.Warningf(rctx, "cluster:%d not exists query:%+v", f.DatasourceId, f)
return LogResp{}, fmt.Errorf("cluster not exists")
}
@@ -208,11 +211,11 @@ func QueryLogConcurrently(anonymousAccess bool, ctx *gin.Context, f models.Query
go func(query interface{}) {
defer wg.Done()
data, total, err := plug.QueryLog(ctx.Request.Context(), query)
logger.Debugf("query log: req:%+v resp:%+v", query, data)
data, total, err := plug.QueryLog(rctx, query)
logx.Debugf(rctx, "query log: req:%+v resp:%+v", query, data)
if err != nil {
errMsg := fmt.Sprintf("query data error: %v query:%v\n ", err, query)
logger.Warningf(errMsg)
logx.Warningf(rctx, "%s", errMsg)
mu.Lock()
errs = append(errs, err)
mu.Unlock()
@@ -250,6 +253,7 @@ func (rt *Router) QueryLogV2(c *gin.Context) {
func (rt *Router) QueryLog(c *gin.Context) {
var f models.QueryParam
ginx.BindJSON(c, &f)
rctx := c.Request.Context()
var resp []interface{}
for _, q := range f.Queries {
@@ -259,13 +263,13 @@ func (rt *Router) QueryLog(c *gin.Context) {
plug, exists := dscache.DsCache.Get("elasticsearch", f.DatasourceId)
if !exists {
logger.Warningf("cluster:%d not exists", f.DatasourceId)
logx.Warningf(rctx, "cluster:%d not exists", f.DatasourceId)
ginx.Bomb(200, "cluster not exists")
}
data, _, err := plug.QueryLog(c.Request.Context(), q)
data, _, err := plug.QueryLog(rctx, q)
if err != nil {
logger.Warningf("query data error: %v", err)
logx.Warningf(rctx, "query data error: %v", err)
ginx.Bomb(200, "err:%v", err)
continue
}

View File

@@ -7,9 +7,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) recordingRuleGets(c *gin.Context) {

View File

@@ -6,9 +6,9 @@ import (
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) rolesGets(c *gin.Context) {

View File

@@ -5,8 +5,8 @@ import (
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -5,9 +5,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/slice"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) savedViewGets(c *gin.Context) {

View File

@@ -5,10 +5,10 @@ import (
"github.com/ccfos/nightingale/v6/pkg/flashduty"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/google/uuid"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -4,9 +4,9 @@ import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) serversGet(c *gin.Context) {

View File

@@ -5,10 +5,10 @@ import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/google/uuid"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// sourceTokenAdd 生成新的源令牌

View File

@@ -13,10 +13,10 @@ import (
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/storage"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/prometheus/common/model"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -7,9 +7,9 @@ import (
"github.com/ccfos/nightingale/v6/alert/sender"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)

View File

@@ -8,9 +8,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
"github.com/toolkits/pkg/str"
)

View File

@@ -8,8 +8,8 @@ import (
"github.com/ccfos/nightingale/v6/datasource/tdengine"
"github.com/ccfos/nightingale/v6/dscache"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
type databasesQueryForm struct {

View File

@@ -0,0 +1,136 @@
package router
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/ccfos/nightingale/v6/pkg/loggrep"
"github.com/toolkits/pkg/logger"
"github.com/gin-gonic/gin"
)
// traceLogsPage renders an HTML log viewer page for trace logs.
func (rt *Router) traceLogsPage(c *gin.Context) {
traceId := ginx.UrlParamStr(c, "traceid")
if !loggrep.IsValidTraceID(traceId) {
c.String(http.StatusBadRequest, "invalid trace id format")
return
}
logs, instance, err := rt.getTraceLogs(traceId)
if err != nil {
c.String(http.StatusInternalServerError, "Error: %v", err)
return
}
c.Header("Content-Type", "text/html; charset=utf-8")
err = loggrep.RenderTraceLogsHTML(c.Writer, loggrep.TraceLogsPageData{
TraceID: traceId,
Instance: instance,
Logs: logs,
Total: len(logs),
})
if err != nil {
c.String(http.StatusInternalServerError, "render error: %v", err)
}
}
// traceLogsJSON returns JSON for trace logs.
func (rt *Router) traceLogsJSON(c *gin.Context) {
traceId := ginx.UrlParamStr(c, "traceid")
if !loggrep.IsValidTraceID(traceId) {
ginx.Bomb(200, "invalid trace id format")
}
logs, instance, err := rt.getTraceLogs(traceId)
ginx.Dangerous(err)
ginx.NewRender(c).Data(loggrep.EventDetailResp{
Logs: logs,
Instance: instance,
}, nil)
}
// getTraceLogs finds the same-engine instances and queries each one
// until trace logs are found. Trace logs belong to a single instance.
func (rt *Router) getTraceLogs(traceId string) ([]string, string, error) {
keyword := "trace_id=" + traceId
instance := fmt.Sprintf("%s:%d", rt.Alert.Heartbeat.IP, rt.HTTP.Port)
engineName := rt.Alert.Heartbeat.EngineName
// try local first
logs, err := loggrep.GrepLatestLogFiles(rt.LogDir, keyword)
if err == nil && len(logs) > 0 {
return logs, instance, nil
}
// find all instances with the same engineName
servers, err := models.AlertingEngineGetsInstances(rt.Ctx,
"engine_cluster = ? and clock > ?",
engineName, time.Now().Unix()-30)
if err != nil {
return nil, "", err
}
// loop through remote instances until we find logs
for _, node := range servers {
if node == instance {
continue // already tried local
}
logs, nodeAddr, err := rt.forwardTraceLogs(node, traceId)
if err != nil {
logger.Errorf("forwardTraceLogs failed: %v", err)
continue
}
if len(logs) > 0 {
return logs, nodeAddr, nil
}
}
return nil, instance, nil
}
func (rt *Router) forwardTraceLogs(node, traceId string) ([]string, string, error) {
url := fmt.Sprintf("http://%s/v1/n9e/trace-logs/%s", node, traceId)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, node, err
}
for user, pass := range rt.HTTP.APIForService.BasicAuth {
req.SetBasicAuth(user, pass)
break
}
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, node, fmt.Errorf("forward to %s failed: %v", node, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024))
if err != nil {
return nil, node, err
}
var result struct {
Dat loggrep.EventDetailResp `json:"dat"`
Err string `json:"err"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, node, err
}
if result.Err != "" {
return nil, node, fmt.Errorf("%s", result.Err)
}
return result.Dat.Logs, result.Dat.Instance, nil
}

View File

@@ -9,9 +9,9 @@ import (
"github.com/ccfos/nightingale/v6/pkg/flashduty"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"gorm.io/gorm"
)

View File

@@ -7,9 +7,9 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/flashduty"
"github.com/ccfos/nightingale/v6/pkg/strx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -5,9 +5,9 @@ import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) userVariableConfigGets(context *gin.Context) {

View File

@@ -12,6 +12,8 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/toolkits/pkg/logger"
"github.com/ccfos/nightingale/v6/pkg/logx"
)
const (
@@ -178,14 +180,9 @@ func (c *Clickhouse) QueryData(ctx context.Context, query interface{}) ([]models
rows, err := c.QueryTimeseries(ctx, ckQueryParam)
if err != nil {
logger.Warningf("query:%+v get data err:%v", ckQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", ckQueryParam, err)
return nil, err
}
if err != nil {
logger.Warningf("query:%+v get data err:%v", ckQueryParam, err)
return []models.DataResp{}, err
}
data := make([]models.DataResp, 0)
for i := range rows {
data = append(data, models.DataResp{
@@ -214,7 +211,7 @@ func (c *Clickhouse) QueryLog(ctx context.Context, query interface{}) ([]interfa
rows, err := c.Query(ctx, ckQueryParam)
if err != nil {
logger.Warningf("query:%+v get data err:%v", ckQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", ckQueryParam, err)
return nil, 0, err
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/logx"
)
type FixedField string
@@ -543,7 +544,7 @@ func QueryData(ctx context.Context, queryParam interface{}, cliTimeout int64, ve
source, _ := queryString.Source()
b, _ := json.Marshal(source)
logger.Debugf("query_data q:%+v indexArr:%+v tsAggr:%+v query_string:%s", param, indexArr, tsAggr, string(b))
logx.Debugf(ctx, "query_data q:%+v indexArr:%+v tsAggr:%+v query_string:%s", param, indexArr, tsAggr, string(b))
searchSource := elastic.NewSearchSource().
Query(queryString).
@@ -551,29 +552,29 @@ func QueryData(ctx context.Context, queryParam interface{}, cliTimeout int64, ve
searchSourceString, err := searchSource.Source()
if err != nil {
logger.Warningf("query_data searchSource:%s to string error:%v", searchSourceString, err)
logx.Warningf(ctx, "query_data searchSource:%s to string error:%v", searchSourceString, err)
}
jsonSearchSource, err := json.Marshal(searchSourceString)
if err != nil {
logger.Warningf("query_data searchSource:%s to json error:%v", searchSourceString, err)
logx.Warningf(ctx, "query_data searchSource:%s to json error:%v", searchSourceString, err)
}
result, err := search(ctx, indexArr, searchSource, param.Timeout, param.MaxShard)
if err != nil {
logger.Warningf("query_data searchSource:%s query_data error:%v", searchSourceString, err)
logx.Warningf(ctx, "query_data searchSource:%s query_data error:%v", searchSourceString, err)
return nil, err
}
// 检查是否有 shard failures有部分数据时仅记录警告继续处理
if shardErr := checkShardFailures(result.Shards, "query_data", searchSourceString); shardErr != nil {
if shardErr := checkShardFailures(ctx, result.Shards, "query_data", searchSourceString); shardErr != nil {
if len(result.Aggregations["ts"]) == 0 {
return nil, shardErr
}
// 有部分数据checkShardFailures 已记录警告,继续处理
}
logger.Debugf("query_data searchSource:%s resp:%s", string(jsonSearchSource), string(result.Aggregations["ts"]))
logx.Infof(ctx, "query_data searchSource:%s resp:%s", string(jsonSearchSource), string(result.Aggregations["ts"]))
js, err := simplejson.NewJson(result.Aggregations["ts"])
if err != nil {
@@ -611,7 +612,7 @@ func QueryData(ctx context.Context, queryParam interface{}, cliTimeout int64, ve
}
// checkShardFailures 检查 ES 查询结果中的 shard failures返回格式化的错误信息
func checkShardFailures(shards *elastic.ShardsInfo, logPrefix string, queryContext interface{}) error {
func checkShardFailures(ctx context.Context, shards *elastic.ShardsInfo, logPrefix string, queryContext interface{}) error {
if shards == nil || shards.Failed == 0 || len(shards.Failures) == 0 {
return nil
}
@@ -638,7 +639,7 @@ func checkShardFailures(shards *elastic.ShardsInfo, logPrefix string, queryConte
if len(failureReasons) > 0 {
errMsg := fmt.Sprintf("elasticsearch shard failures (%d/%d failed): %s", shards.Failed, shards.Total, strings.Join(failureReasons, "; "))
logger.Warningf("%s query:%v %s", logPrefix, queryContext, errMsg)
logx.Warningf(ctx, "%s query:%v %s", logPrefix, queryContext, errMsg)
return fmt.Errorf("%s", errMsg)
}
return nil
@@ -723,12 +724,12 @@ func QueryLog(ctx context.Context, queryParam interface{}, timeout int64, versio
sourceBytes, _ := json.Marshal(source)
result, err := search(ctx, indexArr, source, param.Timeout, param.MaxShard)
if err != nil {
logger.Warningf("query_log source:%s error:%v", string(sourceBytes), err)
logx.Warningf(ctx, "query_log source:%s error:%v", string(sourceBytes), err)
return nil, 0, err
}
// 检查是否有 shard failures有部分数据时仅记录警告继续处理
if shardErr := checkShardFailures(result.Shards, "query_log", string(sourceBytes)); shardErr != nil {
if shardErr := checkShardFailures(ctx, result.Shards, "query_log", string(sourceBytes)); shardErr != nil {
if len(result.Hits.Hits) == 0 {
return nil, 0, shardErr
}
@@ -737,17 +738,17 @@ func QueryLog(ctx context.Context, queryParam interface{}, timeout int64, versio
total := result.TotalHits()
var ret []interface{}
logger.Debugf("query_log source:%s len:%d total:%d", string(sourceBytes), len(result.Hits.Hits), total)
logx.Debugf(ctx, "query_log source:%s len:%d total:%d", string(sourceBytes), len(result.Hits.Hits), total)
resultBytes, _ := json.Marshal(result)
logger.Debugf("query_log source:%s result:%s", string(sourceBytes), string(resultBytes))
logx.Debugf(ctx, "query_log source:%s result:%s", string(sourceBytes), string(resultBytes))
if strings.HasPrefix(version, "6") {
for i := 0; i < len(result.Hits.Hits); i++ {
var x map[string]interface{}
err := json.Unmarshal(result.Hits.Hits[i].Source, &x)
if err != nil {
logger.Warningf("Unmarshal source error:%v", err)
logx.Warningf(ctx, "Unmarshal source error:%v", err)
continue
}

View File

@@ -14,6 +14,8 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/toolkits/pkg/logger"
"github.com/ccfos/nightingale/v6/pkg/logx"
)
const (
@@ -148,7 +150,7 @@ func (d *Doris) QueryData(ctx context.Context, query interface{}) ([]models.Data
}
}
items, err := d.QueryTimeseries(context.TODO(), &doris.QueryParam{
items, err := d.QueryTimeseries(ctx, &doris.QueryParam{
Database: dorisQueryParam.Database,
Sql: dorisQueryParam.SQL,
Keys: types.Keys{
@@ -159,7 +161,7 @@ func (d *Doris) QueryData(ctx context.Context, query interface{}) ([]models.Data
},
})
if err != nil {
logger.Warningf("query:%+v get data err:%v", dorisQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", dorisQueryParam, err)
return []models.DataResp{}, err
}
data := make([]models.DataResp, 0)
@@ -172,7 +174,7 @@ func (d *Doris) QueryData(ctx context.Context, query interface{}) ([]models.Data
}
// parse resp to time series data
logger.Infof("req:%+v keys:%+v \n data:%v", dorisQueryParam, dorisQueryParam.Keys, data)
logx.Infof(ctx, "req:%+v keys:%+v \n data:%v", dorisQueryParam, dorisQueryParam.Keys, data)
return data, nil
}
@@ -208,7 +210,7 @@ func (d *Doris) QueryLog(ctx context.Context, query interface{}) ([]interface{},
Sql: dorisQueryParam.SQL,
})
if err != nil {
logger.Warningf("query:%+v get data err:%v", dorisQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", dorisQueryParam, err)
return []interface{}{}, 0, err
}
logs := make([]interface{}, 0)

View File

@@ -19,7 +19,8 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/olivere/elastic/v7"
"github.com/toolkits/pkg/logger"
"github.com/ccfos/nightingale/v6/pkg/logx"
)
const (
@@ -380,14 +381,14 @@ func (e *Elasticsearch) QueryMapData(ctx context.Context, query interface{}) ([]
var result []map[string]string
for _, item := range res {
logger.Debugf("query:%v item:%v", query, item)
logx.Debugf(ctx, "query:%v item:%v", query, item)
if itemMap, ok := item.(*elastic.SearchHit); ok {
mItem := make(map[string]string)
// 遍历 fields 字段的每个键值对
sourceMap := make(map[string]interface{})
err := json.Unmarshal(itemMap.Source, &sourceMap)
if err != nil {
logger.Warningf("unmarshal source%s error:%v", string(itemMap.Source), err)
logx.Warningf(ctx, "unmarshal source%s error:%v", string(itemMap.Source), err)
continue
}

View File

@@ -15,6 +15,8 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/toolkits/pkg/logger"
"github.com/ccfos/nightingale/v6/pkg/logx"
)
const (
@@ -165,7 +167,7 @@ func (m *MySQL) QueryData(ctx context.Context, query interface{}) ([]models.Data
})
if err != nil {
logger.Warningf("query:%+v get data err:%v", mysqlQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", mysqlQueryParam, err)
return []models.DataResp{}, err
}
data := make([]models.DataResp, 0)
@@ -207,7 +209,7 @@ func (m *MySQL) QueryLog(ctx context.Context, query interface{}) ([]interface{},
})
if err != nil {
logger.Warningf("query:%+v get data err:%v", mysqlQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", mysqlQueryParam, err)
return []interface{}{}, 0, err
}
logs := make([]interface{}, 0)

View File

@@ -16,6 +16,8 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/mitchellh/mapstructure"
"github.com/toolkits/pkg/logger"
"github.com/ccfos/nightingale/v6/pkg/logx"
)
const (
@@ -197,7 +199,7 @@ func (p *PostgreSQL) QueryData(ctx context.Context, query interface{}) ([]models
})
if err != nil {
logger.Warningf("query:%+v get data err:%v", postgresqlQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", postgresqlQueryParam, err)
return []models.DataResp{}, err
}
data := make([]models.DataResp, 0)
@@ -210,7 +212,7 @@ func (p *PostgreSQL) QueryData(ctx context.Context, query interface{}) ([]models
}
// parse resp to time series data
logger.Infof("req:%+v keys:%+v \n data:%v", postgresqlQueryParam, postgresqlQueryParam.Keys, data)
logx.Infof(ctx, "req:%+v keys:%+v \n data:%v", postgresqlQueryParam, postgresqlQueryParam.Keys, data)
return data, nil
}
@@ -249,7 +251,7 @@ func (p *PostgreSQL) QueryLog(ctx context.Context, query interface{}) ([]interfa
Sql: postgresqlQueryParam.SQL,
})
if err != nil {
logger.Warningf("query:%+v get data err:%v", postgresqlQueryParam, err)
logx.Warningf(ctx, "query:%+v get data err:%v", postgresqlQueryParam, err)
return []interface{}{}, 0, err
}
logs := make([]interface{}, 0)

View File

@@ -12,6 +12,8 @@ import (
"github.com/prometheus/common/model"
"github.com/toolkits/pkg/logger"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/datasource"
td "github.com/ccfos/nightingale/v6/dskit/tdengine"
"github.com/ccfos/nightingale/v6/models"
@@ -118,7 +120,7 @@ func (td *TDengine) MakeTSQuery(ctx context.Context, query interface{}, eventTag
}
func (td *TDengine) QueryData(ctx context.Context, queryParam interface{}) ([]models.DataResp, error) {
return td.Query(queryParam, 0)
return td.Query(ctx, queryParam, 0)
}
func (td *TDengine) QueryLog(ctx context.Context, queryParam interface{}) ([]interface{}, int64, error) {
@@ -170,7 +172,7 @@ func (td *TDengine) QueryMapData(ctx context.Context, query interface{}) ([]map[
return nil, nil
}
func (td *TDengine) Query(query interface{}, delay ...int) ([]models.DataResp, error) {
func (td *TDengine) Query(ctx context.Context, query interface{}, delay ...int) ([]models.DataResp, error) {
b, err := json.Marshal(query)
if err != nil {
return nil, err
@@ -212,7 +214,7 @@ func (td *TDengine) Query(query interface{}, delay ...int) ([]models.DataResp, e
if err != nil {
return nil, err
}
logger.Debugf("tdengine query:%s result: %+v", q.Query, data)
logx.Debugf(ctx, "tdengine query:%s result: %+v", q.Query, data)
return ConvertToTStData(data, q.Keys, q.Ref)
}

View File

@@ -15,8 +15,8 @@ import (
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/ccfos/nightingale/v6/pkg/unit"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -12,6 +12,7 @@ import (
"unicode"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/ccfos/nightingale/v6/pkg/secu"
@@ -601,14 +602,14 @@ func incrLoginFailCount(ctx *ctx.Context, redisObj storage.Redis, username strin
}
if err != nil {
logger.Warningf("login_fail_count: failed to get redis value. key:%s, error:%s", key, err)
logx.Warningf(ctx.Ctx, "login_fail_count: failed to get redis value. key:%s, error:%s", key, err)
redisObj.Set(ctx.GetContext(), key, "1", duration)
return
}
count, err := strconv.ParseInt(val, 10, 64)
if err != nil {
logger.Warningf("login_fail_count: failed to parse int64. key:%s, error:%s", key, err)
logx.Warningf(ctx.Ctx, "login_fail_count: failed to parse int64. key:%s, error:%s", key, err)
redisObj.Set(ctx.GetContext(), key, "1", duration)
return
}
@@ -633,18 +634,18 @@ func PassLogin(ctx *ctx.Context, redis storage.Redis, username, pass string) (*U
if needCheck {
pair := strings.Fields(val)
if len(pair) != 2 {
logger.Warningf("login_fail_count config invalid: %s", val)
logx.Warningf(ctx.Ctx, "login_fail_count config invalid: %s", val)
needCheck = false
} else {
seconds, err = strconv.ParseInt(pair[0], 10, 64)
if err != nil {
logger.Warningf("login_fail_count seconds invalid: %s", pair[0])
logx.Warningf(ctx.Ctx, "login_fail_count seconds invalid: %s", pair[0])
needCheck = false
}
count, err = strconv.ParseInt(pair[1], 10, 64)
if err != nil {
logger.Warningf("login_fail_count count invalid: %s", pair[1])
logx.Warningf(ctx.Ctx, "login_fail_count count invalid: %s", pair[1])
needCheck = false
}
}

View File

@@ -320,12 +320,20 @@ func LoggerWithConfig(conf LoggerConfig) gin.HandlerFunc {
param.Path = path
// fmt.Fprint(out, formatter(param))
logger.Info(formatter(param))
traceId := c.GetString("trace_id")
if traceId != "" {
logger.Infof("trace_id=%s %s", traceId, formatter(param))
} else {
logger.Info(formatter(param))
}
if conf.ContainsPath(c.Request.RequestURI) {
respBody := readBody(bytes.NewReader(bodyWriter.body.Bytes()), c.Writer.Header().Get("Content-Encoding"))
reqBody := readBody(rdr1, c.Request.Header.Get("Content-Encoding"))
logger.Debugf("path:%s req body:%s resp:%s", path, reqBody, respBody)
if traceId != "" {
logger.Debugf("trace_id=%s path:%s req body:%s resp:%s", traceId, path, reqBody, respBody)
} else {
logger.Debugf("path:%s req body:%s resp:%s", path, reqBody, respBody)
}
}
}
}

View File

@@ -49,7 +49,10 @@ func RecoveryWithWriter(out io.Writer) gin.HandlerFunc {
if e.Code != 200 {
c.String(e.Code, i18n.Sprintf(c.GetHeader("X-Language"), e.Message))
} else {
c.JSON(e.Code, gin.H{"err": i18n.Sprintf(c.GetHeader("X-Language"), e.Message)})
c.JSON(e.Code, gin.H{
"err": i18n.Sprintf(c.GetHeader("X-Language"), e.Message),
"request_id": c.GetString("trace_id"),
})
}
c.Abort()
return

View File

@@ -43,3 +43,14 @@ func (c *Context) GetContext() context.Context {
func (c *Context) GetDB() *gorm.DB {
return c.DB
}
// WithContext returns a shallow copy with a different standard context.
// Useful for carrying per-request values (e.g. traceId) without mutating the global instance.
func (c *Context) WithContext(stdCtx context.Context) *Context {
return &Context{
DB: c.DB,
CenterApi: c.CenterApi,
Ctx: stdCtx,
IsCenter: c.IsCenter,
}
}

11
pkg/ginx/errorx.go Normal file
View File

@@ -0,0 +1,11 @@
package ginx
import "github.com/toolkits/pkg/errorx"
func Bomb(code int, format string, a ...interface{}) {
errorx.Bomb(code, format, a...)
}
func Dangerous(v interface{}, code ...int) {
errorx.Dangerous(v, code...)
}

19
pkg/ginx/funcs.go Normal file
View File

@@ -0,0 +1,19 @@
package ginx
import (
"github.com/gin-gonic/gin"
)
func Offset(c *gin.Context, limit int, pagenoVarName ...string) int {
if limit <= 0 {
limit = 10
}
pageno := "p"
if len(pagenoVarName) > 0 {
pageno = pagenoVarName[0]
}
page := QueryInt(c, pageno, 1)
return (page - 1) * limit
}

106
pkg/ginx/param.go Normal file
View File

@@ -0,0 +1,106 @@
package ginx
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/errorx"
)
func BindJSON(c *gin.Context, ptr interface{}) {
err := c.ShouldBindJSON(ptr)
if err != nil {
errorx.Bomb(http.StatusBadRequest, "json body invalid: %v", err)
}
}
func UrlParamStr(c *gin.Context, field string) string {
val := c.Param(field)
if val == "" {
errorx.Bomb(http.StatusBadRequest, "url param[%s] is blank", field)
}
return val
}
func UrlParamInt64(c *gin.Context, field string) int64 {
strval := UrlParamStr(c, field)
intval, err := strconv.ParseInt(strval, 10, 64)
if err != nil {
errorx.Bomb(http.StatusBadRequest, "cannot convert %s to int64", strval)
}
return intval
}
func UrlParamInt(c *gin.Context, field string) int {
return int(UrlParamInt64(c, field))
}
func QueryStr(c *gin.Context, key string, defaultVal ...string) string {
val := c.Query(key)
if val != "" {
return val
}
if len(defaultVal) == 0 {
errorx.Bomb(http.StatusBadRequest, "query param[%s] is necessary", key)
}
return defaultVal[0]
}
func QueryInt(c *gin.Context, key string, defaultVal ...int) int {
strv := c.Query(key)
if strv != "" {
intv, err := strconv.Atoi(strv)
if err != nil {
errorx.Bomb(http.StatusBadRequest, "cannot convert [%s] to int", strv)
}
return intv
}
if len(defaultVal) == 0 {
errorx.Bomb(http.StatusBadRequest, "query param[%s] is necessary", key)
}
return defaultVal[0]
}
func QueryInt64(c *gin.Context, key string, defaultVal ...int64) int64 {
strv := c.Query(key)
if strv != "" {
intv, err := strconv.ParseInt(strv, 10, 64)
if err != nil {
errorx.Bomb(http.StatusBadRequest, "cannot convert [%s] to int64", strv)
}
return intv
}
if len(defaultVal) == 0 {
errorx.Bomb(http.StatusBadRequest, "query param[%s] is necessary", key)
}
return defaultVal[0]
}
func QueryBool(c *gin.Context, key string, defaultVal ...bool) bool {
strv := c.Query(key)
if strv != "" {
if strv == "true" || strv == "1" || strv == "on" || strv == "checked" || strv == "yes" || strv == "Y" {
return true
} else if strv == "false" || strv == "0" || strv == "off" || strv == "no" || strv == "N" {
return false
} else {
errorx.Bomb(http.StatusBadRequest, "unknown arg[%s] value: %s", key, strv)
}
}
if len(defaultVal) == 0 {
errorx.Bomb(http.StatusBadRequest, "arg[%s] is necessary", key)
}
return defaultVal[0]
}

66
pkg/ginx/render.go Normal file
View File

@@ -0,0 +1,66 @@
package ginx
import (
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/i18n"
)
type Render struct {
code int
ctx *gin.Context
}
func NewRender(c *gin.Context, code ...int) Render {
r := Render{ctx: c}
if len(code) > 0 {
r.code = code[0]
} else {
r.code = 200
}
return r
}
func (r Render) Message(v interface{}, a ...interface{}) {
requestId := r.ctx.GetString("trace_id")
if v == nil {
if r.code == 200 {
r.ctx.JSON(r.code, gin.H{"err": "", "request_id": requestId})
} else {
r.ctx.String(r.code, "")
}
return
}
switch t := v.(type) {
case string:
msg := i18n.Sprintf(r.ctx.GetHeader("X-Language"), t, a...)
if r.code == 200 {
r.ctx.JSON(r.code, gin.H{"err": msg, "request_id": requestId})
} else {
r.ctx.String(r.code, msg)
}
case error:
msg := i18n.Sprintf(r.ctx.GetHeader("X-Language"), t.Error(), a...)
if r.code == 200 {
r.ctx.JSON(r.code, gin.H{"err": msg, "request_id": requestId})
} else {
r.ctx.String(r.code, msg)
}
}
}
func (r Render) Data(data interface{}, err interface{}, a ...interface{}) {
if err == nil {
r.ctx.JSON(r.code, gin.H{"dat": data, "err": "", "request_id": r.ctx.GetString("trace_id")})
return
}
r.Message(err, a...)
}
func (r Render) ZeroPage() {
r.Data(gin.H{
"list": []int{},
"total": 0,
}, nil)
}

View File

@@ -10,10 +10,12 @@ import (
"time"
"github.com/ccfos/nightingale/v6/pkg/aop"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/pkg/version"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
@@ -91,6 +93,8 @@ func GinEngine(mode string, cfg Config, printBodyPaths func() map[string]struct{
r := gin.New()
r.Use(traceIdMid())
r.Use(recoveryMid)
r.Use(loggerMid)
@@ -126,6 +130,32 @@ func GinEngine(mode string, cfg Config, printBodyPaths func() map[string]struct{
return r
}
func traceIdMid() gin.HandlerFunc {
return func(c *gin.Context) {
id := c.GetHeader("X-Trace-Id")
if !isValidTraceId(id) {
id = uuid.New().String()
}
c.Set("trace_id", id)
ctx := logx.NewTraceContext(c.Request.Context(), id)
c.Request = c.Request.WithContext(ctx)
c.Header("X-Trace-Id", id)
c.Next()
}
}
func isValidTraceId(id string) bool {
if id == "" || len(id) > 64 {
return false
}
for _, r := range id {
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_') {
return false
}
}
return true
}
func Init(cfg Config, handler http.Handler) func() {
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
srv := &http.Server{

View File

@@ -15,6 +15,7 @@ const MaxLogLines = 5000
var hashPattern = regexp.MustCompile(`^[a-f0-9]{32,64}$`)
var idPattern = regexp.MustCompile(`^[1-9]\d*$`)
var traceIdPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,64}$`)
// IsValidHash checks whether s looks like a valid MD5/SHA hex hash.
func IsValidHash(s string) bool {
@@ -26,6 +27,11 @@ func IsValidRuleID(s string) bool {
return idPattern.MatchString(s)
}
// IsValidTraceID checks whether s looks like a valid trace ID (alphanumeric, hyphens, underscores).
func IsValidTraceID(s string) bool {
return traceIdPattern.MatchString(s)
}
type EventDetailResp struct {
Logs []string `json:"logs"`
Instance string `json:"instance"`
@@ -45,6 +51,13 @@ type AlertEvalPageData struct {
Total int
}
type TraceLogsPageData struct {
TraceID string
Instance string
Logs []string
Total int
}
// GrepLogDir searches all log files in logDir for lines containing keyword,
// sorts them by timestamp descending, and truncates to MaxLogLines.
func GrepLogDir(logDir string, keyword string) ([]string, error) {
@@ -73,6 +86,34 @@ func GrepLogDir(logDir string, keyword string) ([]string, error) {
return logs, nil
}
// GrepLatestLogFiles searches only the current (non-rotated) log files in logDir
// (i.e. files matching *.log without any additional suffix like .log.20240101).
func GrepLatestLogFiles(logDir string, keyword string) ([]string, error) {
logFiles, err := filepath.Glob(filepath.Join(logDir, "*.log"))
if err != nil {
return nil, err
}
var logs []string
for _, logFile := range logFiles {
lines, err := GrepFile(logFile, keyword)
if err != nil {
continue
}
logs = append(logs, lines...)
}
sort.Slice(logs, func(i, j int) bool {
return logs[i] > logs[j]
})
if len(logs) > MaxLogLines {
logs = logs[:MaxLogLines]
}
return logs, nil
}
// GrepFile scans a file line by line and returns lines containing the keyword.
func GrepFile(filePath string, keyword string) ([]string, error) {
f, err := os.Open(filePath)
@@ -103,6 +144,11 @@ func RenderAlertEvalHTML(w io.Writer, data AlertEvalPageData) error {
return alertEvalHtmlTpl.Execute(w, data)
}
// RenderTraceLogsHTML writes the trace logs HTML page to w.
func RenderTraceLogsHTML(w io.Writer, data TraceLogsPageData) error {
return traceLogsHtmlTpl.Execute(w, data)
}
var htmlTpl = template.Must(template.New("event-detail").Parse(`<!DOCTYPE html>
<html lang="en">
<head>
@@ -303,6 +349,196 @@ var htmlTpl = template.Must(template.New("event-detail").Parse(`<!DOCTYPE html>
</html>
`))
var traceLogsHtmlTpl = template.Must(template.New("trace-logs").Parse(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trace Logs - {{.TraceID}}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #f0f2f5; color: #333;
}
.header {
background: #fff; border-bottom: 1px solid #ebebeb;
padding: 16px 24px; position: sticky; top: 0; z-index: 10;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
.header-top { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
.header h1 { font-size: 18px; font-weight: 600; color: #333; }
.header h1 span { color: #6C53B1; font-family: "SFMono-Regular", Consolas, monospace; font-size: 15px; }
.badges { display: flex; gap: 8px; flex-wrap: wrap; }
.badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 500;
}
.badge-instance { background: #f0ecf7; color: #6C53B1; }
.badge-count { background: #f5f5f5; color: #666; }
.toolbar {
margin-top: 12px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.search-box {
flex: 1; min-width: 200px; position: relative;
}
.search-box input {
width: 100%; padding: 6px 12px 6px 32px;
background: #fff; border: 1px solid #d9d9d9; border-radius: 6px;
color: #333; font-size: 13px; outline: none;
}
.search-box input:focus { border-color: #6C53B1; box-shadow: 0 0 0 2px rgba(108,83,177,0.2); }
.search-box svg {
position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
width: 16px; height: 16px; fill: #bfbfbf;
}
.filter-btns button {
padding: 4px 12px; border-radius: 6px; border: 1px solid #d9d9d9;
background: #fff; color: #666; font-size: 12px; cursor: pointer;
}
.filter-btns button:hover { border-color: #6C53B1; color: #6C53B1; }
.filter-btns button.active { background: #f0ecf7; border-color: #6C53B1; color: #6C53B1; }
.log-container { padding: 8px 0; background: #fff; margin: 12px; border-radius: 8px; border: 1px solid #ebebeb; }
.log-line {
display: flex; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 12px; line-height: 20px; padding: 0 24px;
border-bottom: 1px solid transparent;
}
.log-line:hover { background: #fafafa; border-color: #ebebeb; }
.line-no {
min-width: 48px; text-align: right; color: #bfbfbf;
padding-right: 16px; user-select: none; flex-shrink: 0;
}
.line-content { white-space: pre-wrap; word-break: break-all; flex: 1; color: #333; }
.line-content .ts { color: #1890ff; }
.line-content .lv-DEBUG { color: #8c8c8c; }
.line-content .lv-INFO { color: #1890ff; }
.line-content .lv-WARNING { color: #fa8c16; }
.line-content .lv-WARNINGF { color: #fa8c16; }
.line-content .lv-ERROR { color: #f5222d; }
.line-content .lv-ERRORF { color: #f5222d; }
.line-content .hl { background: #fff7e6; color: #d46b08; border-radius: 2px; padding: 0 2px; }
.hidden { display: none !important; }
.empty-state {
text-align: center; padding: 64px 24px; color: #bfbfbf; font-size: 14px;
}
.match-count { font-size: 12px; color: #999; white-space: nowrap; }
</style>
</head>
<body>
<div class="header">
<div class="header-top">
<h1>Trace Logs &mdash; <span>{{.TraceID}}</span></h1>
<div class="badges">
<span class="badge badge-instance">&#9881; {{.Instance}}</span>
<span class="badge badge-count" id="countBadge">{{.Total}} lines</span>
</div>
</div>
<div class="toolbar">
<div class="search-box">
<svg viewBox="0 0 16 16"><path d="M11.5 7a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm-.82 4.74a6 6 0 1 1 1.06-1.06l3.04 3.04a.75.75 0 1 1-1.06 1.06l-3.04-3.04Z"/></svg>
<input type="text" id="searchInput" placeholder="Filter logs..." autocomplete="off">
</div>
<div class="filter-btns" id="levelBtns">
<button data-level="all" class="active">All</button>
<button data-level="ERROR">Error</button>
<button data-level="WARNING">Warn</button>
<button data-level="INFO">Info</button>
<button data-level="DEBUG">Debug</button>
</div>
<span class="match-count" id="matchCount"></span>
</div>
</div>
<div class="log-container" id="logContainer">
{{- if eq .Total 0}}
<div class="empty-state">No log lines found for trace ID {{.TraceID}}.</div>
{{- else}}
{{- range $i, $line := .Logs}}
<div class="log-line" data-idx="{{$i}}"><span class="line-no">{{$i}}</span><span class="line-content">{{$line}}</span></div>
{{- end}}
{{- end}}
</div>
<script>
(function() {
var keyword = "trace_id=" + {{.TraceID}};
var lines = document.querySelectorAll('.log-line');
var searchInput = document.getElementById('searchInput');
var levelBtns = document.getElementById('levelBtns').querySelectorAll('button');
var matchCount = document.getElementById('matchCount');
var countBadge = document.getElementById('countBadge');
var activeLevel = 'all';
var LEVELS = ['DEBUG','INFO','WARNING','WARNINGF','ERROR','ERRORF'];
var LEVEL_RE = /\b(DEBUG|INFO|WARNING|WARNINGF|ERROR|ERRORF)\b/;
var TS_RE = /^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}[.\d]*)/;
lines.forEach(function(el) {
var content = el.querySelector('.line-content');
var text = content.textContent;
var html = escapeHtml(text);
html = html.replace(TS_RE, '<span class="ts">$1</span>');
html = html.replace(LEVEL_RE, function(m) { return '<span class="lv-'+m+'">'+m+'</span>'; });
if (keyword) {
html = html.split(escapeHtml(keyword)).join('<span class="hl">'+escapeHtml(keyword)+'</span>');
}
content.innerHTML = html;
el.dataset.level = detectLevel(text);
});
var debounceTimer;
searchInput.addEventListener('input', function() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(applyFilters, 150);
});
levelBtns.forEach(function(btn) {
btn.addEventListener('click', function() {
levelBtns.forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
activeLevel = btn.dataset.level;
applyFilters();
});
});
function applyFilters() {
var q = searchInput.value.toLowerCase();
var visible = 0;
lines.forEach(function(el) {
var text = el.querySelector('.line-content').textContent.toLowerCase();
var levelOk = activeLevel === 'all' || matchLevel(el.dataset.level, activeLevel);
var searchOk = !q || text.indexOf(q) !== -1;
if (levelOk && searchOk) {
el.classList.remove('hidden');
visible++;
} else {
el.classList.add('hidden');
}
});
matchCount.textContent = q || activeLevel !== 'all' ? visible + ' / ' + lines.length + ' shown' : '';
}
function matchLevel(lineLevel, filter) {
if (filter === 'ERROR') return lineLevel === 'ERROR' || lineLevel === 'ERRORF';
if (filter === 'WARNING') return lineLevel === 'WARNING' || lineLevel === 'WARNINGF';
return lineLevel === filter;
}
function detectLevel(text) {
var m = text.match(LEVEL_RE);
return m ? m[1] : '';
}
function escapeHtml(s) {
var d = document.createElement('div');
d.appendChild(document.createTextNode(s));
return d.innerHTML;
}
})();
</script>
</body>
</html>
`))
var alertEvalHtmlTpl = template.Must(template.New("alert-eval-detail").Parse(`<!DOCTYPE html>
<html lang="en">
<head>

View File

@@ -1,6 +1,7 @@
package logx
import (
"context"
"fmt"
"github.com/pkg/errors"
@@ -46,3 +47,44 @@ func Init(c Config) (func(), error) {
logger.Close()
}, nil
}
// traceKey is the context key for storing traceId.
type traceKey struct{}
// NewTraceContext returns a new context carrying the given traceId.
func NewTraceContext(ctx context.Context, traceId string) context.Context {
return context.WithValue(ctx, traceKey{}, traceId)
}
// GetTraceId extracts the traceId from ctx, or returns "" if absent.
func GetTraceId(ctx context.Context) string {
if ctx == nil {
return ""
}
id, _ := ctx.Value(traceKey{}).(string)
return id
}
func prefix(ctx context.Context) string {
id := GetTraceId(ctx)
if id == "" {
return ""
}
return "trace_id=" + id + " "
}
func Infof(ctx context.Context, format string, args ...interface{}) {
logger.Infof(prefix(ctx)+format, args...)
}
func Errorf(ctx context.Context, format string, args ...interface{}) {
logger.Errorf(prefix(ctx)+format, args...)
}
func Warningf(ctx context.Context, format string, args ...interface{}) {
logger.Warningf(prefix(ctx)+format, args...)
}
func Debugf(ctx context.Context, format string, args ...interface{}) {
logger.Debugf(prefix(ctx)+format, args...)
}

View File

@@ -12,12 +12,12 @@ import (
"github.com/ccfos/nightingale/v6/center/metas"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/ccfos/nightingale/v6/pkg/httpx"
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/pushgw/pconf"
"github.com/ccfos/nightingale/v6/pushgw/pstat"
"github.com/ccfos/nightingale/v6/pushgw/writer"
"github.com/ccfos/nightingale/v6/pkg/ginx"
)
type HandleTSFunc func(pt *prompb.TimeSeries) *prompb.TimeSeries

View File

@@ -10,8 +10,8 @@ import (
"github.com/ccfos/nightingale/v6/center/metas"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -8,11 +8,11 @@ import (
"sync/atomic"
"github.com/ccfos/nightingale/v6/pushgw/pstat"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/gogo/protobuf/proto"
"github.com/golang/snappy"
"github.com/prometheus/prometheus/prompb"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)

View File

@@ -2,8 +2,8 @@ package router
import (
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) targetUpdate(c *gin.Context) {