From 59a7f7c890972e06ab3c2dc4f793d293c8e59c84 Mon Sep 17 00:00:00 2001 From: Hoang Trinh Date: Fri, 19 Jan 2024 11:46:33 +0700 Subject: [PATCH] feat: add logger and redis --- .github/pull_request_template.md | 18 +++ .github/workflows/ci.yml | 50 ++++++++ .github/workflows/release.yml | 67 ++++++++++ Makefile | 11 ++ go.mod | 17 +++ go.sum | 35 ++++++ pkg/logger/README.md | 3 + pkg/logger/logger.go | 170 +++++++++++++++++++++++++ pkg/logger/logrus.go | 207 +++++++++++++++++++++++++++++++ pkg/logger/zap.go | 156 +++++++++++++++++++++++ pkg/redis/client.go | 17 +++ pkg/redis/config.go | 17 +++ 12 files changed, 768 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/logger/README.md create mode 100644 pkg/logger/logger.go create mode 100644 pkg/logger/logrus.go create mode 100644 pkg/logger/zap.go create mode 100644 pkg/redis/client.go create mode 100644 pkg/redis/config.go diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f082d63 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ + + + +## Why did we need it? + + +## Related Issue + + + + + +## How Has This Been Tested? + + + + +## Screenshots (if appropriate): \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6e01c6b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: Toolkit Go CI + +concurrency: + group: ci-workflow-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - main + - release-v** + pull_request: + +env: + SERVICE: logger + +jobs: + lint: + name: Run golangci-lint + runs-on: [ ubuntu-22.04 ] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: "1.20.x" + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.52.2 + skip-pkg-cache: true + skip-build-cache: true + args: --timeout=2m + test: + runs-on: [ ubuntu-22.04 ] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: "1.20.x" + - name: Run tests + run: go test -race -coverprofile cover.out -vet=off ./... + + - name: Print coverage + run: | + go tool cover -func cover.out | grep total | awk '{notice="Statement Coverage: " substr($3, 1, length($3))} END {print notice}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ac0142f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: 'Release' + +permissions: write-all + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version' + type: string + required: true + +jobs: + prepare: + runs-on: [ ubuntu-22.04 ] + outputs: + version_tag: ${{ steps.version_tag.outputs.value }} + build_date: ${{ steps.build_date.outputs.value }} + steps: + - name: Format version tag + shell: bash + id: version_tag + env: + INPUT_TAG: ${{ github.event.inputs.version }} + run: | + TAG=${INPUT_TAG#v} + echo "::set-output name=value::v$TAG" + - name: Build date + shell: bash + id: build_date + run: echo "::set-output name=value::$(date +%FT%T%z)" + + release: + needs: + - prepare + runs-on: [ ubuntu-22.04 ] + env: + VERSION_TAG: ${{ needs.prepare.outputs.version_tag }} + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: "1.20.x" + + - name: Setup Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Create tag + run: | + git tag -d "$VERSION_TAG" 2> /dev/null || echo "Release tag '$VERSION_TAG' does NOT exist" + git tag --annotate --message "dmm-aggregator-backend $VERSION_TAG" "$VERSION_TAG" + git push origin "refs/tags/$VERSION_TAG" + + - name: Create release + uses: softprops/action-gh-release@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag_name: ${{ env.VERSION_TAG }} + prerelease: false + name: "Toolkit Go ${{ env.VERSION_TAG }}" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c80bd62 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +generate: + go generate ./... + +lint: + golangci-lint run ./... + +test: + go test ./... -count=1 + + +.PHONY: generate lint test diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..324fff7 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/KyberNetwork/toolkit-go + +go 1.21 + +require ( + github.com/redis/go-redis/v9 v9.4.0 + github.com/sirupsen/logrus v1.9.3 + go.uber.org/zap v1.26.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..23942f9 --- /dev/null +++ b/go.sum @@ -0,0 +1,35 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= +github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.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/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/logger/README.md b/pkg/logger/README.md new file mode 100644 index 0000000..7c62cde --- /dev/null +++ b/pkg/logger/README.md @@ -0,0 +1,3 @@ +# logger + +Flexible logger interface that supports both zap and logrus diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..cbbd63b --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,170 @@ +package logger + +import ( + "errors" + "sync" +) + +// A global variable so that log functions can be directly accessed +var log = DefaultLogger() + +// Fields Type to pass when we want to call WithFields for structured logging +type Fields map[string]interface{} + +// LoggerBackend represents the int enum for backend of logger. +// nolint:revive +type LoggerBackend int + +const ( + // Debug has verbose message + debugLvl = "debug" + // Info is default log level + infoLvl = "info" + // Warn is for logging messages about possible issues + warnLvl = "warn" + // Error is for logging errors + errorLvl = "error" + // Fatal is for logging fatal messages. The system shutdowns after logging the message. + fatalLvl = "fatal" +) + +const ( + // LoggerBackendZap logging using Uber's zap backend + LoggerBackendZap LoggerBackend = iota + // LoggerBackendLogrus logging using logrus backend + LoggerBackendLogrus +) + +var ( + errInvalidLoggerInstance = errors.New("invalid logger instance") + + once sync.Once +) + +// Logger is our contract for the logger +type Logger interface { + Debug(msg string) + Debugf(format string, args ...interface{}) + + Info(msg string) + Infof(format string, args ...interface{}) + Infoln(msg string) + + Warn(msg string) + Warnf(format string, args ...interface{}) + + Error(msg string) + Errorf(format string, args ...interface{}) + + Fatal(msg string) + Fatalf(format string, args ...interface{}) + + WithFields(keyValues Fields) Logger + + // extract instance logger. + GetDelegate() interface{} + SetLogLevel(level string) error +} + +// Configuration stores the config for the logger +// For some loggers there can only be one level across writers, for such the level of Console is picked by default +type Configuration struct { + EnableConsole bool `mapstructure:"enable_console"` + EnableJSONFormat bool `mapstructure:"enable_json_format"` + ConsoleLevel string `mapstructure:"console_level"` + EnableFile bool + FileJSONFormat bool + FileLevel string + FileLocation string +} + +// DefaultLogger creates default logger, which uses zap sugarLogger and outputs to console +func DefaultLogger() Logger { + cfg := Configuration{ + EnableConsole: true, + EnableJSONFormat: false, + ConsoleLevel: "warn", + EnableFile: false, + FileJSONFormat: false, + } + logger, _ := newZapLogger(cfg) + return logger +} + +// InitLogger returns an instance of logger +func InitLogger(config Configuration, backend LoggerBackend) (Logger, error) { + var err error + once.Do(func() { + log, err = NewLogger(config, backend) + }) + return log, err +} + +func NewLogger(config Configuration, backend LoggerBackend) (Logger, error) { + switch backend { + case LoggerBackendZap: + return newZapLogger(config) + + case LoggerBackendLogrus: + return newLogrusLogger(config) + + default: + return nil, errInvalidLoggerInstance + } +} + +func Debug(msg string) { + log.Debugf(msg) +} + +func Debugf(format string, args ...interface{}) { + log.Debugf(format, args...) +} + +func Info(msg string) { + log.Infof(msg) +} + +func Infof(format string, args ...interface{}) { + log.Infof(format, args...) +} + +func Infoln(msg string) { + log.Infoln(msg) +} + +func Warn(msg string) { + log.Warnf(msg) +} + +func Warnf(format string, args ...interface{}) { + log.Warnf(format, args...) +} + +func Error(msg string) { + log.Errorf(msg) +} + +func Errorf(format string, args ...interface{}) { + log.Errorf(format, args...) +} + +func Fatal(msg string) { + log.Fatalf(msg) +} + +func Fatalf(format string, args ...interface{}) { + log.Fatalf(format, args...) +} + +func WithFields(keyValues Fields) Logger { + return log.WithFields(keyValues) +} + +func GetDelegate() interface{} { + return log.GetDelegate() +} + +func SetLogLevel(level string) error { + return log.SetLogLevel(level) +} diff --git a/pkg/logger/logrus.go b/pkg/logger/logrus.go new file mode 100644 index 0000000..88860ba --- /dev/null +++ b/pkg/logger/logrus.go @@ -0,0 +1,207 @@ +package logger + +import ( + "io" + "os" + + "github.com/sirupsen/logrus" + "gopkg.in/natefinch/lumberjack.v2" +) + +type logrusLogEntry struct { + entry *logrus.Entry +} + +type logrusLogger struct { + logger *logrus.Logger +} + +func getFormatter(isJSON bool) logrus.Formatter { + if isJSON { + return &logrus.JSONFormatter{} + } + return &logrus.TextFormatter{ + FullTimestamp: true, + DisableLevelTruncation: true, + ForceColors: true, + } +} + +func newLogrusLogger(config Configuration) (*logrusLogger, error) { + logLevel := config.ConsoleLevel + if logLevel == "" { + logLevel = config.FileLevel + } + + level, err := logrus.ParseLevel(logLevel) + if err != nil { + return nil, err + } + + stdOutHandler := os.Stdout + fileHandler := &lumberjack.Logger{ + Filename: config.FileLocation, + MaxSize: 100, + Compress: true, + MaxAge: 28, + } + lLogger := &logrus.Logger{ + Out: stdOutHandler, + Formatter: getFormatter(config.EnableJSONFormat), + Hooks: make(logrus.LevelHooks), + Level: level, + } + + if config.EnableConsole && config.EnableFile { + lLogger.SetOutput(io.MultiWriter(stdOutHandler, fileHandler)) + } else if config.EnableFile { + lLogger.SetOutput(fileHandler) + lLogger.SetFormatter(getFormatter(config.FileJSONFormat)) + } + + return &logrusLogger{ + logger: lLogger, + }, nil +} + +func (l *logrusLogger) Debugf(format string, args ...interface{}) { + l.logger.Debugf(format, args...) +} + +func (l *logrusLogger) Debug(msg string) { + l.logger.Debug(msg) +} + +func (l *logrusLogger) Infof(format string, args ...interface{}) { + l.logger.Infof(format, args...) +} + +func (l *logrusLogger) Info(msg string) { + l.logger.Info(msg) +} + +func (l *logrusLogger) Infoln(msg string) { + l.logger.Infoln(msg) +} + +func (l *logrusLogger) Warnf(format string, args ...interface{}) { + l.logger.Warnf(format, args...) +} + +func (l *logrusLogger) Warn(msg string) { + l.logger.Warn(msg) +} + +func (l *logrusLogger) Errorf(format string, args ...interface{}) { + l.logger.Errorf(format, args...) +} + +func (l *logrusLogger) Error(msg string) { + l.logger.Error(msg) +} + +func (l *logrusLogger) Fatalf(format string, args ...interface{}) { + l.logger.Fatalf(format, args...) +} + +func (l *logrusLogger) Fatal(msg string) { + l.logger.Fatal(msg) +} + +func (l *logrusLogger) Panicf(format string, args ...interface{}) { + l.logger.Panicf(format, args...) +} + +func (l *logrusLogger) Panic(msg string) { + l.logger.Panic(msg) +} + +func (l *logrusLogger) WithFields(fields Fields) Logger { + return &logrusLogEntry{ + entry: l.logger.WithFields(convertToLogrusFields(fields)), + } +} + +func (l *logrusLogger) GetDelegate() interface{} { + return l.logger +} + +func (l *logrusLogger) SetLogLevel(logLevel string) error { + logrusLogLevel, err := logrus.ParseLevel(logLevel) + if err != nil { + return err + } + l.logger.SetLevel(logrusLogLevel) + return nil +} + +func (l *logrusLogEntry) Debugf(format string, args ...interface{}) { + l.entry.Debugf(format, args...) +} + +func (l *logrusLogEntry) Debug(msg string) { + l.entry.Debug(msg) +} + +func (l *logrusLogEntry) Infof(format string, args ...interface{}) { + l.entry.Infof(format, args...) +} + +func (l *logrusLogEntry) Info(msg string) { + l.entry.Info(msg) +} + +func (l *logrusLogEntry) Infoln(msg string) { + l.entry.Infoln(msg) +} + +func (l *logrusLogEntry) Warnf(format string, args ...interface{}) { + l.entry.Warnf(format, args...) +} + +func (l *logrusLogEntry) Warn(msg string) { + l.entry.Warn(msg) +} + +func (l *logrusLogEntry) Errorf(format string, args ...interface{}) { + l.entry.Errorf(format, args...) +} + +func (l *logrusLogEntry) Error(msg string) { + l.entry.Error(msg) +} + +func (l *logrusLogEntry) Fatalf(format string, args ...interface{}) { + l.entry.Fatalf(format, args...) +} + +func (l *logrusLogEntry) Fatal(msg string) { + l.entry.Fatal(msg) +} + +func (l *logrusLogEntry) WithFields(fields Fields) Logger { + return &logrusLogEntry{ + entry: l.entry.WithFields(convertToLogrusFields(fields)), + } +} + +func (l *logrusLogEntry) SetLogLevel(logLevel string) error { + level, err := logrus.ParseLevel(logLevel) + if err != nil { + return err + } + l.entry.Logger.SetLevel(level) + return nil +} + +func (l *logrusLogEntry) GetDelegate() interface{} { + return l.entry +} + +func convertToLogrusFields(fields Fields) logrus.Fields { + logrusFields := logrus.Fields{} + for index, val := range fields { + logrusFields[index] = val + } + return logrusFields +} diff --git a/pkg/logger/zap.go b/pkg/logger/zap.go new file mode 100644 index 0000000..ebef964 --- /dev/null +++ b/pkg/logger/zap.go @@ -0,0 +1,156 @@ +package logger + +import ( + "fmt" + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +type zapLogger struct { + sugaredLogger *zap.SugaredLogger + atomicLevel *zap.AtomicLevel +} + +func getEncoder(isJSON bool) zapcore.Encoder { + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + if isJSON { + return zapcore.NewJSONEncoder(encoderConfig) + } + encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + return zapcore.NewConsoleEncoder(encoderConfig) +} + +func getZapLevel(level string) zapcore.Level { + switch level { + case infoLvl: + return zapcore.InfoLevel + case warnLvl: + return zapcore.WarnLevel + case debugLvl: + return zapcore.DebugLevel + case errorLvl: + return zapcore.ErrorLevel + case fatalLvl: + return zapcore.FatalLevel + default: + return zapcore.InfoLevel + } +} + +func newZapLogger(config Configuration) (*zapLogger, error) { + var cores []zapcore.Core + atom := zap.NewAtomicLevel() + if config.EnableConsole { + level := getZapLevel(config.ConsoleLevel) + atom.SetLevel(level) + writer := zapcore.Lock(os.Stdout) + core := zapcore.NewCore(getEncoder(config.EnableJSONFormat), writer, atom) + cores = append(cores, core) + } + + if config.EnableFile { + level := getZapLevel(config.FileLevel) + atom.SetLevel(level) + writer := zapcore.AddSync(&lumberjack.Logger{ + Filename: config.FileLocation, + MaxSize: 100, + Compress: true, + MaxAge: 28, + }) + core := zapcore.NewCore(getEncoder(config.FileJSONFormat), writer, atom) + cores = append(cores, core) + } + + combinedCore := zapcore.NewTee(cores...) + + // AddCallerSkip skips 2 number of callers, this is important else the file that gets + // logged will always be the wrapped file. In our case zap.go + logger := zap.New(combinedCore, + zap.AddCallerSkip(2), + zap.AddCaller(), + ).Sugar() + return &zapLogger{ + sugaredLogger: logger, + atomicLevel: &atom, + }, nil +} + +func (l *zapLogger) Debugf(format string, args ...interface{}) { + l.sugaredLogger.Debugf(format, args...) +} + +func (l *zapLogger) Debug(msg string) { + l.sugaredLogger.Debug(msg) +} + +func (l *zapLogger) Infof(format string, args ...interface{}) { + l.sugaredLogger.Infof(format, args...) +} + +func (l *zapLogger) Info(msg string) { + l.sugaredLogger.Info(msg) +} + +func (l *zapLogger) Infoln(msg string) { + l.sugaredLogger.Infoln(msg) +} + +func (l *zapLogger) Warnf(format string, args ...interface{}) { + l.sugaredLogger.Warnf(format, args...) +} + +func (l *zapLogger) Warn(msg string) { + l.sugaredLogger.Warn(msg) +} + +func (l *zapLogger) Errorf(format string, args ...interface{}) { + l.sugaredLogger.Errorf(format, args...) +} + +func (l *zapLogger) Error(msg string) { + l.sugaredLogger.Error(msg) +} + +func (l *zapLogger) Fatalf(format string, args ...interface{}) { + l.sugaredLogger.Fatalf(format, args...) +} + +func (l *zapLogger) Fatal(msg string) { + l.sugaredLogger.Fatal(msg) +} + +func (l *zapLogger) WithFields(fields Fields) Logger { + var fds = make([]interface{}, 0, len(fields)*2) + for k, v := range fields { + fds = append(fds, k) + fds = append(fds, v) + } + // AddCallerSkip(-1) is important here, else the file that gets logged will always be the wrapped file. + newLogger := l.sugaredLogger.WithOptions(zap.AddCallerSkip(-1)).With(fds...) + return &zapLogger{ + sugaredLogger: newLogger, + atomicLevel: l.atomicLevel, + } +} + +func (l *zapLogger) GetDelegate() interface{} { + return l.sugaredLogger +} + +func (l *zapLogger) SetLogLevel(level string) error { + l.atomicLevel.SetLevel(getZapLevel(level)) + return nil +} + +func GetDesugaredZapLoggerDelegate(instance Logger) (*zap.Logger, error) { + switch v := instance.GetDelegate().(type) { + case *zap.SugaredLogger: + return instance.GetDelegate().(*zap.SugaredLogger).Desugar(), nil + default: + return nil, fmt.Errorf("expected zap.SugaredLogger but got: %v", v) + } +} diff --git a/pkg/redis/client.go b/pkg/redis/client.go new file mode 100644 index 0000000..f605d4e --- /dev/null +++ b/pkg/redis/client.go @@ -0,0 +1,17 @@ +package redis + +import "github.com/redis/go-redis/v9" + +func New(cfg Config) redis.UniversalClient { + return redis.NewUniversalClient(&redis.UniversalOptions{ + Addrs: cfg.Addrs, + MasterName: cfg.MasterName, + DB: cfg.DBNumber, + Username: cfg.Username, + Password: cfg.Password, + SentinelUsername: cfg.SentinelUsername, + SentinelPassword: cfg.SentinelPassword, + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + }) +} diff --git a/pkg/redis/config.go b/pkg/redis/config.go new file mode 100644 index 0000000..2de733c --- /dev/null +++ b/pkg/redis/config.go @@ -0,0 +1,17 @@ +package redis + +import "time" + +type Config struct { + Addrs []string `mapstructure:"addrs" json:"addrs"` + MasterName string `mapstructure:"master_name" json:"masterName"` + DBNumber int `mapstructure:"db_number" json:"dbNumber"` + Username string `mapstructure:"username" json:"username"` + Password string `mapstructure:"password" json:"-"` + SentinelUsername string `mapstructure:"sentinel_username" json:"sentinelUsername"` + SentinelPassword string `mapstructure:"sentinel_password" json:"-"` + Prefix string `mapstructure:"prefix" json:"prefix"` + Separator string `mapstructure:"separator" json:"separator"` + ReadTimeout time.Duration `mapstructure:"read_timeout" json:"readTimeout"` + WriteTimeout time.Duration `mapstructure:"write_timeout" json:"writeTimeout"` +}