stream id generation and middleware

This commit is contained in:
Pavel 2024-09-19 18:10:14 +03:00 committed by GitHub
commit 2be8669393
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 88 additions and 11 deletions

View File

@ -17,7 +17,7 @@ import (
type logRecord struct { type logRecord struct {
LevelName string `json:"level_name"` LevelName string `json:"level_name"`
DateTime null.Time `json:"datetime"` DateTime null.Time `json:"datetime"`
Caller string `json:"caller"` StreamID string `json:"streamId"`
Message string `json:"message"` Message string `json:"message"`
Handler string `json:"handler,omitempty"` Handler string `json:"handler,omitempty"`
Connection string `json:"connection,omitempty"` Connection string `json:"connection,omitempty"`

View File

@ -7,7 +7,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// GinMiddleware will construct Gin middleware which will log requests. // GinMiddleware will construct Gin middleware which will log requests and provide logger with unique request ID.
func GinMiddleware(log Logger) gin.HandlerFunc { func GinMiddleware(log Logger) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
// Start timer // Start timer
@ -15,6 +15,11 @@ func GinMiddleware(log Logger) gin.HandlerFunc {
path := c.Request.URL.Path path := c.Request.URL.Path
raw := c.Request.URL.RawQuery raw := c.Request.URL.RawQuery
streamID := generateStreamID()
log := log.With(StreamID(streamID))
c.Set(StreamIDAttr, streamID)
c.Set("logger", log)
// Process request // Process request
c.Next() c.Next()
@ -35,3 +40,7 @@ func GinMiddleware(log Logger) gin.HandlerFunc {
) )
} }
} }
func MustGet(c *gin.Context) Logger {
return c.MustGet("logger").(Logger)
}

View File

@ -16,6 +16,8 @@ func TestGinMiddleware(t *testing.T) {
r := gin.New() r := gin.New()
r.Use(GinMiddleware(log)) r.Use(GinMiddleware(log))
r.GET("/mine", func(c *gin.Context) { r.GET("/mine", func(c *gin.Context) {
log := MustGet(c)
log.Info("some very important message")
c.JSON(http.StatusOK, gin.H{}) c.JSON(http.StatusOK, gin.H{})
}) })
r.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/mine", nil)) r.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/mine", nil))
@ -23,16 +25,19 @@ func TestGinMiddleware(t *testing.T) {
require.Equal(t, http.StatusOK, rr.Code) require.Equal(t, http.StatusOK, rr.Code)
items, err := newJSONBufferedLogger(log).ScanAll() items, err := newJSONBufferedLogger(log).ScanAll()
require.NoError(t, err) require.NoError(t, err)
require.Len(t, items, 1) require.Len(t, items, 2)
require.NotEmpty(t, items[0].Context) require.NotEmpty(t, items[0].StreamID)
assert.NotEmpty(t, items[0].Context["startTime"]) require.NotEmpty(t, items[1].StreamID)
assert.NotEmpty(t, items[0].Context["endTime"]) assert.Equal(t, "some very important message", items[0].Message)
require.NotEmpty(t, items[1].Context)
assert.NotEmpty(t, items[1].Context["startTime"])
assert.NotEmpty(t, items[1].Context["endTime"])
assert.True(t, func() bool { assert.True(t, func() bool {
_, ok := items[0].Context["latency"] _, ok := items[1].Context["latency"]
return ok return ok
}()) }())
assert.NotEmpty(t, items[0].Context["remoteAddress"]) assert.NotEmpty(t, items[1].Context["remoteAddress"])
assert.NotEmpty(t, items[0].Context[HTTPMethodAttr]) assert.NotEmpty(t, items[1].Context[HTTPMethodAttr])
assert.NotEmpty(t, items[0].Context["path"]) assert.NotEmpty(t, items[1].Context["path"])
assert.NotEmpty(t, items[0].Context["bodySize"]) assert.NotEmpty(t, items[1].Context["bodySize"])
} }

44
core/logger/stream_id.go Normal file
View File

@ -0,0 +1,44 @@
package logger
import (
"encoding/binary"
"sync/atomic"
"time"
"unsafe"
)
var streamIDCounter = uint64(time.Now().Unix())
func hexEncode(dst *[32]byte, src *[8]byte, cnt uint64) int {
const hexDigits = "0123456789abcdef"
for i, b := range src[:] {
dst[i*2] = hexDigits[b>>4]
dst[i*2+1] = hexDigits[b&0x0F]
}
idx := 16
for cnt > 0 {
dst[idx] = hexDigits[cnt&0xF]
cnt >>= 4
idx++
}
for i, j := 16, idx-1; i < j; i, j = i+1, j-1 {
dst[i], dst[j] = dst[j], dst[i]
}
return idx
}
func bytesToString(b *[32]byte, length int) string {
return unsafe.String(&b[0], length)
}
func generateStreamID() string {
var id [8]byte
var hexID [32]byte
cnt := atomic.AddUint64(&streamIDCounter, 1)
timestamp := time.Now().UnixNano()
binary.BigEndian.PutUint64(id[:], uint64(timestamp))
length := hexEncode(&hexID, &id, cnt)
return bytesToString(&hexID, length)
}

View File

@ -0,0 +1,19 @@
package logger
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGenerateStreamID(t *testing.T) {
id1 := generateStreamID()
id2 := generateStreamID()
assert.NotEqual(t, id1, id2)
}
func BenchmarkGenerateStreamID(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = generateStreamID()
}
}