Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add correlation ids #20

Merged
merged 19 commits into from
Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions api/router/correlation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package router

import (
"context"
"net/http"

"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"go.uber.org/zap"
)

const (
CorrelationIDName = "correlation_id"
IntCorrelationIDName = "int_correlation_id"
)

// CorrelationIDMiddleware adds correlationID if it's not specified in HTTP request
func CorrelationIDMiddleware(l *zap.SugaredLogger) gin.HandlerFunc {
return func(c *gin.Context) {
addCorrelationID(c, l)
}
}

func addCorrelationID(c *gin.Context, l *zap.SugaredLogger) {
ctx := c.Request.Context()

correlationID := c.Request.Header.Get("X-Correlation-id")
0xmuralik marked this conversation as resolved.
Show resolved Hide resolved

if correlationID != "" {
ctx = context.WithValue(ctx, CorrelationIDName, correlationID)
c.Writer.Header().Set("X-Correlation-Id", correlationID)
l = l.With(CorrelationIDName, correlationID)
}

id, _ := uuid.NewV4()

ctx = context.WithValue(ctx, IntCorrelationIDName, id.String())
l = l.With(IntCorrelationIDName, id)

c.Set("logger", l)

c.Request = c.Request.WithContext(ctx)

c.Next()
}

func getLoggerFromContext(c *gin.Context) *zap.SugaredLogger {
value, ok := c.Get("logger")
if !ok {
c.AbortWithStatusJSON(http.StatusInternalServerError, "logger does not exists in context")
}

l, ok := value.(*zap.SugaredLogger)
if !ok {
c.AbortWithStatusJSON(http.StatusInternalServerError, "invalid logger format in context")
}

return l
}
92 changes: 92 additions & 0 deletions api/router/correlation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package router_test

import (
"fmt"
"net/http"
"testing"
"time"

"github.com/allinbits/demeris-api-server/api/config"
"github.com/allinbits/demeris-api-server/api/database"
"github.com/allinbits/demeris-api-server/api/router"
"github.com/allinbits/emeris-utils/store"
"github.com/cockroachdb/cockroach-go/v2/testserver"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
)

type chainsResponse struct {
0xmuralik marked this conversation as resolved.
Show resolved Hide resolved
Chains []supportedChain `json:"chains"`
}
0xmuralik marked this conversation as resolved.
Show resolved Hide resolved

type supportedChain struct {
0xmuralik marked this conversation as resolved.
Show resolved Hide resolved
ChainName string `json:"chain_name"`
DisplayName string `json:"display_name"`
Logo string `json:"logo"`
}

func TestCorrelationIDMiddleWare(t *testing.T) {
0xmuralik marked this conversation as resolved.
Show resolved Hide resolved
r, cfg, observedLogs, tDown := setup(t)
defer tDown()
require.NotNil(t, r)

go r.Serve(cfg.ListenAddr)
time.Sleep(2 * time.Second)

client := http.Client{
Timeout: 2 * time.Second,
}
req, err := http.NewRequest("GET", fmt.Sprintf("http://%s%s", cfg.ListenAddr, "/chains"), nil)
require.NoError(t, err)

id, _ := uuid.NewV4()
0xmuralik marked this conversation as resolved.
Show resolved Hide resolved

req.Header.Set("X-Correlation-id", fmt.Sprintf("%x", id))

_, err = client.Do(req)
require.NoError(t, err)

require.Eventually(t, func() bool {
count := 0
for _, info := range observedLogs.All() {
if info.ContextMap()[string(router.IntCorrelationIDName)] != nil {
count++
}
if info.ContextMap()[string(router.CorrelationIDName)] == fmt.Sprintf("%x", id) {
count++
}
}
return count == 2
}, 5*time.Second, 1*time.Second)
}

func setup(t *testing.T) (router.Router, config.Config, *observer.ObservedLogs, func()) {
tServer, err := testserver.NewTestServer()
require.NoError(t, err)

require.NoError(t, tServer.WaitForInit())

connStr := tServer.PGURL().String()
require.NotNil(t, connStr)

cfg := &config.Config{
DatabaseConnectionURL: connStr,
ListenAddr: "127.0.0.1:9090",
RedisAddr: "127.0.0.1:6379",
KubernetesNamespace: "emeris",
Debug: true,
}

db, err := database.Init(cfg)
require.NoError(t, err)

s, err := store.NewClient(cfg.RedisAddr)
require.NoError(t, err)

observedZapCore, observedLogs := observer.New(zap.InfoLevel)
observedLogger := zap.New(observedZapCore)

return *router.New(db, observedLogger.Sugar(), s, nil, "", nil, cfg.Debug), *cfg, observedLogs, func() { tServer.Stop() }
}
8 changes: 8 additions & 0 deletions api/router/deps/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@ func GetDeps(c *gin.Context) *Deps {

// WriteError lgos and return client-facing errors
func (d *Deps) WriteError(c *gin.Context, err Error, logMessage string, keyAndValues ...interface{}) {

// setting error id
value, ok := c.Request.Context().Value("int_correlation_id").(string)
if !ok {
panic("cant get value int_correlation_id")
}
err.ID = value
_ = c.Error(err)

if keyAndValues != nil {
keyAndValues = append(keyAndValues, "id", err.ID)
d.Logger.Errorw(
logMessage,
keyAndValues...,
Expand Down
21 changes: 0 additions & 21 deletions api/router/deps/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,8 @@ package deps

import (
"fmt"
"strconv"
"sync"

"github.com/sony/sonyflake"
)

var flake *sonyflake.Sonyflake
var once sync.Once

func init() {
once.Do(func() {
flake = sonyflake.NewSonyflake(sonyflake.Settings{})
})
}

type Error struct {
ID string `json:"id"`
Namespace string `json:"namespace"`
Expand All @@ -34,15 +21,7 @@ func (e Error) Unwrap() error {
}

func NewError(namespace string, cause error, statusCode int) Error {
id, err := flake.NextID()
if err != nil {
panic(fmt.Errorf("cannot create sonyflake, %w", err))
}

idstr := strconv.FormatUint(id, 10)

return Error{
ID: idstr,
StatusCode: statusCode,
Namespace: namespace,
LowLevelError: cause,
Expand Down
11 changes: 7 additions & 4 deletions api/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import (
type Router struct {
g *gin.Engine
db *database.Database
l *zap.SugaredLogger
s *store.Store
k8s kube.Client
k8sNamespace string
Expand All @@ -57,10 +56,11 @@ func New(

engine := gin.New()

engine.Use(CorrelationIDMiddleware(l))

r := &Router{
g: engine,
db: db,
l: l,
s: s,
k8s: kubeClient,
k8sNamespace: kubeNamespace,
Expand Down Expand Up @@ -90,6 +90,7 @@ func (r *Router) Serve(address string) error {
}

func (r *Router) catchPanicsFunc(c *gin.Context) {
l := getLoggerFromContext(c)
defer func() {
if rval := recover(); rval != nil {
// okay we panic-ed, log it through r's logger and write back internal server error
Expand All @@ -98,7 +99,7 @@ func (r *Router) catchPanicsFunc(c *gin.Context) {
errors.New("internal server error"),
http.StatusInternalServerError)

r.l.Errorw(
l.Errorw(
"panic handler triggered while handling call",
"endpoint", c.Request.RequestURI,
"error", fmt.Sprint(rval),
Expand All @@ -117,8 +118,10 @@ func (r *Router) catchPanicsFunc(c *gin.Context) {
}

func (r *Router) decorateCtxWithDeps(c *gin.Context) {
l := getLoggerFromContext(c)

c.Set("deps", &deps.Deps{
Logger: r.l,
Logger: l,
Database: r.db,
Store: r.s,
KubeNamespace: r.k8sNamespace,
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ require (
github.com/allinbits/emeris-utils v0.0.0-20211210104150-283219be3359
github.com/allinbits/sdk-service-meta v0.0.0-20211213140844-1ad0f7cce207
github.com/allinbits/starport-operator v0.0.1-alpha.26
github.com/cockroachdb/cockroach-go/v2 v2.1.1
github.com/cosmos/cosmos-sdk v0.42.8
github.com/gin-gonic/gin v1.7.4
github.com/go-playground/validator/v10 v10.9.0
github.com/gofrs/uuid v3.3.0+incompatible
github.com/gravity-devs/liquidity v1.2.9
github.com/jmoiron/sqlx v1.3.3
github.com/lib/pq v1.10.3
github.com/sony/sonyflake v1.0.0
github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942
github.com/swaggo/swag v1.7.0
github.com/tendermint/tendermint v0.34.11
github.com/zsais/go-gin-prometheus v0.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3h
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v2.0.1+incompatible h1:rkk9T7FViadPOz28xQ68o18jBSpyShru0mayVumxqYA=
github.com/cockroachdb/cockroach-go v2.0.1+incompatible/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/cockroachdb/cockroach-go/v2 v2.1.1 h1:3XzfSMuUT0wBe1a3o5C0eOTcArhmmFAg2Jzh/7hhKqo=
github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
Expand Down