Skip to content

Commit

Permalink
#14 🛠️ Inited Glide Logging (#30)
Browse files Browse the repository at this point in the history
* #14 Inited Glide logging based on Zap

* #14 introducing a new telemetry struct

* #14: Introduced a new telemetry struct to hold all observability components

* #14: Added the router struct

* #14: Configured hertz logger to obey the general logging config

* #14 Added more static checks
  • Loading branch information
roma-glushko authored Dec 23, 2023
1 parent 99ab0dc commit 23693c7
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 18 deletions.
24 changes: 24 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ linters:
- nolintlint
- revive
- staticcheck
- asasalint
- bodyclose
- contextcheck
- cyclop
- dupword
- errname
- exhaustive
- loggercheck
- misspell
- nestif
- perfsprint
- prealloc
- predeclared
- testifylint
- testpackage
- unconvert
- usestdlibvars
- wsl

linters-settings:
govet:
Expand All @@ -17,3 +35,9 @@ linters-settings:
- reflectvaluecompare
- sortslice
- unusedwrite
- defers
- atomic
- nilfunc
- printf
- sortslice
- tests
3 changes: 0 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ help:
install-checkers: ## Install static checkers
@echo "🚚 Downloading binaries.."
@GOBIN=$(CHECKER_BIN) go install mvdan.cc/gofumpt@latest
@GOBIN=$(CHECKER_BIN) go install go.uber.org/nilaway/cmd/nilaway@latest
@GOBIN=$(CHECKER_BIN) go install golang.org/x/vuln/cmd/govulncheck@latest
@GOBIN=$(CHECKER_BIN) go install github.com/securego/gosec/v2/cmd/gosec@latest

Expand All @@ -32,8 +31,6 @@ lint: install-checkers ## Lint the source code
static-checks: install-checkers ## Static Analysis
@echo "🧹 GoCI Lint.."
@golangci-lint run ./...
@echo "🧹 Nilaway.."
@$(CHECKER_BIN)/nilaway ./...

vuln: install-checkers ## Check for vulnerabilities
@echo "🔍 Checking for vulnerabilities"
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ go 1.21.5
require (
github.com/cloudwego/hertz v0.7.3
github.com/go-playground/validator/v10 v10.16.0
github.com/hertz-contrib/logger/zap v1.1.0
github.com/spf13/cobra v1.8.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.26.0
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ github.com/henrylee2cn/ameda v1.4.10 h1:JdvI2Ekq7tapdPsuhrc4CaFiqw6QXFvZIULWJgQy
github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 h1:yE9ULgp02BhYIrO6sdV/FPe0xQM6fNHkVQW2IAymfM0=
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ=
github.com/hertz-contrib/logger/zap v1.1.0 h1:4efINiIDJrXEtAFeEdDJvc3Hye0VFxp+0X4BwaZgxNs=
github.com/hertz-contrib/logger/zap v1.1.0/go.mod h1:D/rJJgsYn+SGaHVfVqWS3vHTbbc7ODAlJO+6smWgTeE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
Expand Down Expand Up @@ -88,6 +90,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
Expand Down
21 changes: 16 additions & 5 deletions pkg/api/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,28 @@ import (
"fmt"
"time"

"glide/pkg/pools"
"glide/pkg/telemetry"

"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)

type Server struct {
server *server.Hertz
telemetry *telemetry.Telemetry
router *pools.Router
server *server.Hertz
}

func NewServer(config *ServerConfig) (*Server, error) {
func NewServer(config *ServerConfig, tel *telemetry.Telemetry, router *pools.Router) (*Server, error) {
srv := config.ToServer()

return &Server{
server: config.ToServer(),
telemetry: tel,
router: router,
server: srv,
}, nil
}

Expand All @@ -32,10 +41,12 @@ func (srv *Server) Run() error {
func (srv *Server) Shutdown(_ context.Context) error {
exitWaitTime := srv.server.GetOptions().ExitWaitTimeout

println(fmt.Sprintf("Begin graceful shutdown, wait at most %d seconds...", exitWaitTime/time.Second))
srv.telemetry.Logger.Info(
fmt.Sprintf("Begin graceful shutdown, wait at most %d seconds...", exitWaitTime/time.Second),
)

ctx, cancel := context.WithTimeout(context.Background(), exitWaitTime)
defer cancel()

return srv.server.Shutdown(ctx)
return srv.server.Shutdown(ctx) //nolint:contextcheck
}
10 changes: 7 additions & 3 deletions pkg/api/servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"sync"

"glide/pkg/pools"
"glide/pkg/telemetry"

"glide/pkg/api/http"
)

Expand All @@ -12,13 +15,14 @@ type ServerManager struct {
shutdownWG *sync.WaitGroup
}

func NewServerManager(httpConfig *http.ServerConfig) (*ServerManager, error) {
httpServer, err := http.NewServer(httpConfig)
// TODO: init other servers like gRPC in future
func NewServerManager(httpConfig *http.ServerConfig, tel *telemetry.Telemetry, router *pools.Router) (*ServerManager, error) {
httpServer, err := http.NewServer(httpConfig, tel, router)
if err != nil {
return nil, err
}

// TODO: init other servers like gRPC in future

return &ServerManager{
httpServer: httpServer,
shutdownWG: &sync.WaitGroup{},
Expand Down
2 changes: 1 addition & 1 deletion pkg/buildAPIRequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ var validate *validator.Validate = validator.New()
func BuildAPIRequest(provider string, params map[string]string, mode string) (interface{}, error) {
// provider is the name of the provider, e.g. "openai", params is the map of parameters from the client,
// mode is the mode of the provider, e.g. "chat", configList is the list of provider configurations

var providerConfig map[string]interface{}

if config, ok := configList[provider].(ProviderConfigs); ok {
if modeConfig, ok := config[mode].(map[string]interface{}); ok {
providerConfig = modeConfig
Expand Down
35 changes: 29 additions & 6 deletions pkg/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import (
"os/signal"
"syscall"

"glide/pkg/pools"

"glide/pkg/telemetry"
"go.uber.org/zap"

"glide/pkg/api"
"glide/pkg/api/http"

Expand All @@ -16,6 +21,8 @@ import (
// Gateway represents an instance of running Glide gateway.
// It loads configs, start API server(s), and listen to termination signals to shut down
type Gateway struct {
// telemetry holds logger, meter, and tracer
telemetry *telemetry.Telemetry
// serverManager controls API over different protocols
serverManager *api.ServerManager
// signalChannel is used to receive termination signals from the OS.
Expand All @@ -25,12 +32,28 @@ type Gateway struct {
}

func NewGateway() (*Gateway, error) {
serverManager, err := api.NewServerManager(&http.ServerConfig{})
// TODO: gonna be read from a config file
logConfig := telemetry.NewLogConfig()
logConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
logConfig.Encoding = "console"

tel, err := telemetry.NewTelemetry(&telemetry.Config{LogConfig: logConfig})
if err != nil {
return nil, err
}

router, err := pools.NewRouter(tel)
if err != nil {
return nil, err
}

serverManager, err := api.NewServerManager(&http.ServerConfig{}, tel, router)
if err != nil {
return nil, err
}

return &Gateway{
telemetry: tel,
serverManager: serverManager,
signalC: make(chan os.Signal, 3), // equal to number of signal types we expect to receive
shutdownC: make(chan struct{}),
Expand All @@ -49,16 +72,16 @@ LOOP:
for {
select {
// TODO: Watch for config updates
case <-gw.signalC:
// TODO: log this occurrence
case sig := <-gw.signalC:
gw.telemetry.Logger.Info("Received signal from OS", zap.String("signal", sig.String()))
break LOOP
case <-gw.shutdownC:
// TODO: log this occurrence
gw.telemetry.Logger.Info("received shutdown request")
break LOOP
case <-ctx.Done():
// TODO: log this occurrence
gw.telemetry.Logger.Info("context done, terminating process")
// Call shutdown with background context as the passed in context has been canceled
return gw.shutdown(context.Background())
return gw.shutdown(context.Background()) //nolint:contextcheck
}
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/pools/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pools

import "glide/pkg/telemetry"

type Router struct {
telemetry *telemetry.Telemetry
}

func NewRouter(tel *telemetry.Telemetry) (*Router, error) {
return &Router{
telemetry: tel,
}, nil
}
106 changes: 106 additions & 0 deletions pkg/telemetry/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package telemetry

import (
"github.com/cloudwego/hertz/pkg/common/hlog"
hertzzap "github.com/hertz-contrib/logger/zap"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

type LogConfig struct {
// Level is the minimum enabled logging level.
Level zap.AtomicLevel `yaml:"level"`

// Encoding sets the logger's encoding. Valid values are "json", "console"
Encoding string `yaml:"encoding"`

// DisableCaller stops annotating logs with the calling function's file name and line number.
// By default, all logs are annotated.
DisableCaller bool `yaml:"disable_caller"`

// DisableStacktrace completely disables automatic stacktrace capturing. By
// default, stacktraces are captured for WarnLevel and above logs in
// development and ErrorLevel and above in production.
DisableStacktrace bool `yaml:"disable_stacktrace"`

// OutputPaths is a list of URLs or file paths to write logging output to.
OutputPaths []string `yaml:"output_paths"`

// InitialFields is a collection of fields to add to the root logger.
InitialFields map[string]interface{} `yaml:"initial_fields"`
}

func NewLogConfig() *LogConfig {
return &LogConfig{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
Encoding: "json",
DisableCaller: false,
DisableStacktrace: false,
OutputPaths: []string{"stdout"},
InitialFields: make(map[string]interface{}),
}
}

func (c *LogConfig) ToZapConfig() *zap.Config {
zapConfig := zap.NewProductionConfig()

if c.Encoding == "console" {
zapConfig = zap.NewDevelopmentConfig()

// Human-readable timestamps for console format of logs.
zapConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// Colorized plain console logs
zapConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
}

zapConfig.Level = c.Level
zapConfig.DisableCaller = c.DisableCaller
zapConfig.DisableStacktrace = c.DisableStacktrace
zapConfig.OutputPaths = c.OutputPaths
zapConfig.InitialFields = c.InitialFields

return &zapConfig
}

func NewHertzLogger(zapConfig *zap.Config) (*hertzzap.Logger, error) {
// Both hertzzap and zap have a set of private methods that prevents from leveraging
// their native encoder & sink building functionality
// We had to copy & paste some of those to get it working
var encoder zapcore.Encoder

if zapConfig.Encoding == "console" {
encoder = zapcore.NewConsoleEncoder(zapConfig.EncoderConfig)
} else {
encoder = zapcore.NewJSONEncoder(zapConfig.EncoderConfig)
}

sink, _, err := zap.Open(zapConfig.OutputPaths...)
if err != nil {
return nil, err
}

return hertzzap.NewLogger(
hertzzap.WithCoreEnc(encoder),
hertzzap.WithCoreWs(sink),
hertzzap.WithCoreLevel(zapConfig.Level),
hertzzap.WithZapOptions(zap.AddCallerSkip(3)),
), nil
}

func NewLogger(cfg *LogConfig) (*zap.Logger, error) {
zapConfig := cfg.ToZapConfig()

logger, err := zapConfig.Build()
if err != nil {
return nil, err
}

hertzLogger, err := NewHertzLogger(zapConfig)
if err != nil {
return nil, err
}

hlog.SetLogger(hertzLogger)

return logger, nil
}
26 changes: 26 additions & 0 deletions pkg/telemetry/telemetry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package telemetry

import "go.uber.org/zap"

type Config struct {
LogConfig *LogConfig `json:"logs" yaml:"logs"`
// TODO: add OTEL config
}

type Telemetry struct {
Config *Config
Logger *zap.Logger
// TODO: add OTEL meter, tracer
}

func NewTelemetry(cfg *Config) (*Telemetry, error) {
logger, err := NewLogger(cfg.LogConfig)
if err != nil {
return nil, err
}

return &Telemetry{
Config: cfg,
Logger: logger,
}, nil
}

0 comments on commit 23693c7

Please sign in to comment.