mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2024-11-22 05:06:04 +03:00
wip: structured logging
This commit is contained in:
parent
2f76dfd95e
commit
a2eb85eb25
@ -17,3 +17,10 @@ type Connection struct {
|
|||||||
ID int `gorm:"primary_key" json:"id"`
|
ID int `gorm:"primary_key" json:"id"`
|
||||||
Active bool `json:"active,omitempty"`
|
Active bool `json:"active,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Connection) Address() string {
|
||||||
|
if c.PublicURL != "" {
|
||||||
|
return c.PublicURL
|
||||||
|
}
|
||||||
|
return c.URL
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -13,7 +14,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/retailcrm/zabbix-metrics-collector"
|
"github.com/retailcrm/zabbix-metrics-collector"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
@ -65,7 +65,6 @@ type Engine struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
AppInfo AppInfo
|
AppInfo AppInfo
|
||||||
Sessions sessions.Store
|
Sessions sessions.Store
|
||||||
LogFormatter logging.Formatter
|
|
||||||
Config config.Configuration
|
Config config.Configuration
|
||||||
Zabbix metrics.Transport
|
Zabbix metrics.Transport
|
||||||
ginEngine *gin.Engine
|
ginEngine *gin.Engine
|
||||||
@ -133,9 +132,6 @@ func (e *Engine) Prepare() *Engine {
|
|||||||
if e.DefaultError == "" {
|
if e.DefaultError == "" {
|
||||||
e.DefaultError = "error"
|
e.DefaultError = "error"
|
||||||
}
|
}
|
||||||
if e.LogFormatter == nil {
|
|
||||||
e.LogFormatter = logger.DefaultLogFormatter()
|
|
||||||
}
|
|
||||||
if e.LocaleMatcher == nil {
|
if e.LocaleMatcher == nil {
|
||||||
e.LocaleMatcher = DefaultLocalizerMatcher()
|
e.LocaleMatcher = DefaultLocalizerMatcher()
|
||||||
}
|
}
|
||||||
@ -150,9 +146,13 @@ func (e *Engine) Prepare() *Engine {
|
|||||||
e.Localizer.Preload(e.PreloadLanguages)
|
e.Localizer.Preload(e.PreloadLanguages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !e.Config.IsDebug() {
|
||||||
|
logger.DefaultOpts.Level = slog.LevelInfo
|
||||||
|
}
|
||||||
|
|
||||||
e.CreateDB(e.Config.GetDBConfig())
|
e.CreateDB(e.Config.GetDBConfig())
|
||||||
e.ResetUtils(e.Config.GetAWSConfig(), e.Config.IsDebug(), 0)
|
e.ResetUtils(e.Config.GetAWSConfig(), e.Config.IsDebug(), 0)
|
||||||
e.SetLogger(logger.NewStandard(e.Config.GetTransportInfo().GetCode(), e.Config.GetLogLevel(), e.LogFormatter))
|
e.SetLogger(logger.NewDefaultText())
|
||||||
e.Sentry.Localizer = &e.Localizer
|
e.Sentry.Localizer = &e.Localizer
|
||||||
e.Utils.Logger = e.Logger()
|
e.Utils.Logger = e.Logger()
|
||||||
e.Sentry.Logger = e.Logger()
|
e.Sentry.Logger = e.Logger()
|
||||||
@ -175,7 +175,7 @@ func (e *Engine) UseZabbix(collectors []metrics.Collector) *Engine {
|
|||||||
}
|
}
|
||||||
cfg := e.Config.GetZabbixConfig()
|
cfg := e.Config.GetZabbixConfig()
|
||||||
sender := zabbix.NewSender(cfg.ServerHost, cfg.ServerPort)
|
sender := zabbix.NewSender(cfg.ServerHost, cfg.ServerPort)
|
||||||
e.Zabbix = metrics.NewZabbix(collectors, sender, cfg.Host, cfg.Interval, e.Logger())
|
e.Zabbix = metrics.NewZabbix(collectors, sender, cfg.Host, cfg.Interval, logger.ZabbixCollectorAdapter(e.Logger()))
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +123,6 @@ func (e *EngineTest) Test_Prepare() {
|
|||||||
assert.True(e.T(), e.engine.prepared)
|
assert.True(e.T(), e.engine.prepared)
|
||||||
assert.NotNil(e.T(), e.engine.Config)
|
assert.NotNil(e.T(), e.engine.Config)
|
||||||
assert.NotEmpty(e.T(), e.engine.DefaultError)
|
assert.NotEmpty(e.T(), e.engine.DefaultError)
|
||||||
assert.NotEmpty(e.T(), e.engine.LogFormatter)
|
|
||||||
assert.NotEmpty(e.T(), e.engine.LocaleMatcher)
|
assert.NotEmpty(e.T(), e.engine.LocaleMatcher)
|
||||||
assert.False(e.T(), e.engine.isUnd(e.engine.Localizer.LanguageTag))
|
assert.False(e.T(), e.engine.isUnd(e.engine.Localizer.LanguageTag))
|
||||||
assert.NotNil(e.T(), e.engine.DB)
|
assert.NotNil(e.T(), e.engine.DB)
|
||||||
@ -253,7 +252,7 @@ func (e *EngineTest) Test_SetLogger() {
|
|||||||
defer func() {
|
defer func() {
|
||||||
e.engine.logger = origLogger
|
e.engine.logger = origLogger
|
||||||
}()
|
}()
|
||||||
e.engine.logger = &logger.StandardLogger{}
|
e.engine.logger = logger.NewDefaultNil()
|
||||||
e.engine.SetLogger(nil)
|
e.engine.SetLogger(nil)
|
||||||
assert.NotNil(e.T(), e.engine.logger)
|
assert.NotNil(e.T(), e.engine.logger)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package healthcheck
|
package healthcheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"github.com/retailcrm/mg-transport-core/v2/core/logger"
|
"github.com/retailcrm/mg-transport-core/v2/core/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,19 +31,19 @@ type CounterProcessor struct {
|
|||||||
func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varnamelen
|
func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varnamelen
|
||||||
if counter.IsFailed() {
|
if counter.IsFailed() {
|
||||||
if counter.IsFailureProcessed() {
|
if counter.IsFailureProcessed() {
|
||||||
c.debugLog("skipping counter id=%d because its failure is already processed", id)
|
c.debugLog("skipping counter because its failure is already processed", slog.Int(logger.CounterIDAttr, id))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
apiURL, apiKey, _, exists := c.ConnectionDataProvider(id)
|
apiURL, apiKey, _, exists := c.ConnectionDataProvider(id)
|
||||||
if !exists {
|
if !exists {
|
||||||
c.debugLog("cannot find connection data for counter id=%d", id)
|
c.debugLog("cannot find connection data for counter", slog.Int(logger.CounterIDAttr, id))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
err := c.Notifier(apiURL, apiKey, counter.Message())
|
err := c.Notifier(apiURL, apiKey, counter.Message())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.debugLog("cannot send notification for counter id=%d: %s (message: %s)",
|
c.debugLog("cannot send notification for counter",
|
||||||
id, err, counter.Message())
|
slog.Int(logger.CounterIDAttr, id), logger.ErrAttr(err), slog.String(logger.FailureMessageAttr, counter.Message()))
|
||||||
}
|
}
|
||||||
counter.FailureProcessed()
|
counter.FailureProcessed()
|
||||||
return true
|
return true
|
||||||
@ -53,7 +55,8 @@ func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varn
|
|||||||
// Ignore this counter for now because total count of requests is less than minimal count.
|
// Ignore this counter for now because total count of requests is less than minimal count.
|
||||||
// The results may not be representative.
|
// The results may not be representative.
|
||||||
if (succeeded + failed) < c.MinRequests {
|
if (succeeded + failed) < c.MinRequests {
|
||||||
c.debugLog("skipping counter id=%d because it has fewer than %d requests", id, c.MinRequests)
|
c.debugLog("skipping counter because it has too few requests",
|
||||||
|
slog.Int(logger.CounterIDAttr, id), slog.Any("minRequests", c.MinRequests))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,13 +75,13 @@ func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varn
|
|||||||
|
|
||||||
apiURL, apiKey, lang, exists := c.ConnectionDataProvider(id)
|
apiURL, apiKey, lang, exists := c.ConnectionDataProvider(id)
|
||||||
if !exists {
|
if !exists {
|
||||||
c.debugLog("cannot find connection data for counter id=%d", id)
|
c.debugLog("cannot find connection data for counter", slog.Int(logger.CounterIDAttr, id))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
err := c.Notifier(apiURL, apiKey, c.getErrorText(counter.Name(), c.Error, lang))
|
err := c.Notifier(apiURL, apiKey, c.getErrorText(counter.Name(), c.Error, lang))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.debugLog("cannot send notification for counter id=%d: %s (message: %s)",
|
c.debugLog("cannot send notification for counter",
|
||||||
id, err, counter.Message())
|
slog.Int(logger.CounterIDAttr, id), logger.ErrAttr(err), slog.String(logger.FailureMessageAttr, counter.Message()))
|
||||||
}
|
}
|
||||||
counter.CountersProcessed()
|
counter.CountersProcessed()
|
||||||
return true
|
return true
|
||||||
@ -96,6 +99,6 @@ func (c CounterProcessor) getErrorText(name, msg, lang string) string {
|
|||||||
|
|
||||||
func (c CounterProcessor) debugLog(msg string, args ...interface{}) {
|
func (c CounterProcessor) debugLog(msg string, args ...interface{}) {
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
c.Logger.Debugf(msg, args...)
|
c.Logger.Debug(msg, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,8 @@ func (t *CounterProcessorTest) Test_FailureProcessed() {
|
|||||||
|
|
||||||
p.Process(1, c)
|
p.Process(1, c)
|
||||||
c.AssertExpectations(t.T())
|
c.AssertExpectations(t.T())
|
||||||
t.Assert().Contains(log.String(), "skipping counter id=1 because its failure is already processed")
|
t.Assert().Contains(log.String(), "skipping counter because its failure is already processed")
|
||||||
|
t.Assert().Contains(log.String(), "id=1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *CounterProcessorTest) Test_CounterFailed_CannotFindConnection() {
|
func (t *CounterProcessorTest) Test_CounterFailed_CannotFindConnection() {
|
||||||
@ -107,7 +108,8 @@ func (t *CounterProcessorTest) Test_CounterFailed_CannotFindConnection() {
|
|||||||
|
|
||||||
p.Process(1, c)
|
p.Process(1, c)
|
||||||
c.AssertExpectations(t.T())
|
c.AssertExpectations(t.T())
|
||||||
t.Assert().Contains(log.String(), "cannot find connection data for counter id=1")
|
t.Assert().Contains(log.String(), "cannot find connection data for counter")
|
||||||
|
t.Assert().Contains(log.String(), "id=1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *CounterProcessorTest) Test_CounterFailed_ErrWhileNotifying() {
|
func (t *CounterProcessorTest) Test_CounterFailed_ErrWhileNotifying() {
|
||||||
@ -121,7 +123,10 @@ func (t *CounterProcessorTest) Test_CounterFailed_ErrWhileNotifying() {
|
|||||||
|
|
||||||
p.Process(1, c)
|
p.Process(1, c)
|
||||||
c.AssertExpectations(t.T())
|
c.AssertExpectations(t.T())
|
||||||
t.Assert().Contains(log.String(), "cannot send notification for counter id=1: http status code: 500 (message: error message)")
|
t.Assert().Contains(log.String(), "cannot send notification for counter")
|
||||||
|
t.Assert().Contains(log.String(), "id=1")
|
||||||
|
t.Assert().Contains(log.String(), `error="http status code: 500"`)
|
||||||
|
t.Assert().Contains(log.String(), `message="error message"`)
|
||||||
t.Assert().Equal(t.apiURL, n.apiURL)
|
t.Assert().Equal(t.apiURL, n.apiURL)
|
||||||
t.Assert().Equal(t.apiKey, n.apiKey)
|
t.Assert().Equal(t.apiKey, n.apiKey)
|
||||||
t.Assert().Equal("error message", n.message)
|
t.Assert().Equal("error message", n.message)
|
||||||
@ -155,7 +160,7 @@ func (t *CounterProcessorTest) Test_TooFewRequests() {
|
|||||||
p.Process(1, c)
|
p.Process(1, c)
|
||||||
c.AssertExpectations(t.T())
|
c.AssertExpectations(t.T())
|
||||||
t.Assert().Contains(log.String(),
|
t.Assert().Contains(log.String(),
|
||||||
fmt.Sprintf("skipping counter id=%d because it has fewer than %d requests", 1, DefaultMinRequests))
|
fmt.Sprintf(`msg="skipping counter because it has too few requests" id=%d minRequests=%d`, 1, DefaultMinRequests))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *CounterProcessorTest) Test_ThresholdNotPassed() {
|
func (t *CounterProcessorTest) Test_ThresholdNotPassed() {
|
||||||
@ -200,7 +205,8 @@ func (t *CounterProcessorTest) Test_ThresholdPassed_NoConnectionFound() {
|
|||||||
|
|
||||||
p.Process(1, c)
|
p.Process(1, c)
|
||||||
c.AssertExpectations(t.T())
|
c.AssertExpectations(t.T())
|
||||||
t.Assert().Contains(log.String(), "cannot find connection data for counter id=1")
|
t.Assert().Contains(log.String(), "cannot find connection data for counter")
|
||||||
|
t.Assert().Contains(log.String(), "id=1")
|
||||||
t.Assert().Empty(n.message)
|
t.Assert().Empty(n.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +224,7 @@ func (t *CounterProcessorTest) Test_ThresholdPassed_NotifyingError() {
|
|||||||
|
|
||||||
p.Process(1, c)
|
p.Process(1, c)
|
||||||
c.AssertExpectations(t.T())
|
c.AssertExpectations(t.T())
|
||||||
t.Assert().Contains(log.String(), "cannot send notification for counter id=1: unknown error (message: )")
|
t.Assert().Contains(log.String(), `msg="cannot send notification for counter" id=1 error="unknown error" message=""`)
|
||||||
t.Assert().Equal(`default error [{"Name":"MockedCounter"}]`, n.message)
|
t.Assert().Equal(`default error [{"Name":"MockedCounter"}]`, n.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package core
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ type Job struct {
|
|||||||
// SetLogger(logger).
|
// SetLogger(logger).
|
||||||
// SetLogging(false)
|
// SetLogging(false)
|
||||||
// _ = manager.RegisterJob("updateTokens", &Job{
|
// _ = manager.RegisterJob("updateTokens", &Job{
|
||||||
// Command: func(log logger.Logger) error {
|
// Command: func(log logger.LoggerOld) error {
|
||||||
// // logic goes here...
|
// // logic goes here...
|
||||||
// logger.Info("All tokens were updated successfully")
|
// logger.Info("All tokens were updated successfully")
|
||||||
// return nil
|
// return nil
|
||||||
@ -146,14 +147,14 @@ func (j *Job) runOnceSync(name string, log logger.Logger) {
|
|||||||
|
|
||||||
// NewJobManager is a JobManager constructor.
|
// NewJobManager is a JobManager constructor.
|
||||||
func NewJobManager() *JobManager {
|
func NewJobManager() *JobManager {
|
||||||
return &JobManager{jobs: &sync.Map{}, nilLogger: logger.NewNil()}
|
return &JobManager{jobs: &sync.Map{}, nilLogger: logger.NewDefaultNil()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultJobErrorHandler returns default error handler for a job.
|
// DefaultJobErrorHandler returns default error handler for a job.
|
||||||
func DefaultJobErrorHandler() JobErrorHandler {
|
func DefaultJobErrorHandler() JobErrorHandler {
|
||||||
return func(name string, err error, log logger.Logger) {
|
return func(name string, err error, log logger.Logger) {
|
||||||
if err != nil && name != "" {
|
if err != nil && name != "" {
|
||||||
log.Errorf("Job `%s` errored with an error: `%s`", name, err.Error())
|
log.Error("job failed with an error", slog.String("job", name), logger.ErrAttr(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,7 +163,7 @@ func DefaultJobErrorHandler() JobErrorHandler {
|
|||||||
func DefaultJobPanicHandler() JobPanicHandler {
|
func DefaultJobPanicHandler() JobPanicHandler {
|
||||||
return func(name string, recoverValue interface{}, log logger.Logger) {
|
return func(name string, recoverValue interface{}, log logger.Logger) {
|
||||||
if recoverValue != nil && name != "" {
|
if recoverValue != nil && name != "" {
|
||||||
log.Errorf("Job `%s` panicked with value: `%#v`", name, recoverValue)
|
log.Error("job panicked with the value", slog.String("job", name), slog.Any("value", recoverValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -25,7 +26,7 @@ type JobTest struct {
|
|||||||
executeErr chan error
|
executeErr chan error
|
||||||
panicValue chan interface{}
|
panicValue chan interface{}
|
||||||
lastLog string
|
lastLog string
|
||||||
lastMsgLevel logging.Level
|
lastMsgLevel slog.Level
|
||||||
syncBool bool
|
syncBool bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,68 +37,69 @@ type JobManagerTest struct {
|
|||||||
syncRunnerFlag bool
|
syncRunnerFlag bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type callbackLoggerFunc func(level logging.Level, format string, args ...interface{})
|
type callbackLoggerFunc func(ctx context.Context, level slog.Level, msg string, args ...any)
|
||||||
|
|
||||||
type callbackLogger struct {
|
type callbackLogger struct {
|
||||||
fn callbackLoggerFunc
|
fn callbackLoggerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Fatal(args ...interface{}) {
|
func (n *callbackLogger) Handler() slog.Handler {
|
||||||
n.fn(logging.CRITICAL, "", args...)
|
return logger.NilHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Fatalf(format string, args ...interface{}) {
|
func (n *callbackLogger) With(args ...any) logger.Logger {
|
||||||
n.fn(logging.CRITICAL, format, args...)
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Panic(args ...interface{}) {
|
func (n *callbackLogger) WithGroup(name string) logger.Logger {
|
||||||
n.fn(logging.CRITICAL, "", args...)
|
return n
|
||||||
}
|
|
||||||
func (n *callbackLogger) Panicf(format string, args ...interface{}) {
|
|
||||||
n.fn(logging.CRITICAL, format, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Critical(args ...interface{}) {
|
func (n *callbackLogger) ForAccount(handler, conn, acc any) logger.Logger {
|
||||||
n.fn(logging.CRITICAL, "", args...)
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Criticalf(format string, args ...interface{}) {
|
func (n *callbackLogger) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
n.fn(logging.CRITICAL, format, args...)
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Error(args ...interface{}) {
|
func (n *callbackLogger) Log(ctx context.Context, level slog.Level, msg string, args ...any) {
|
||||||
n.fn(logging.ERROR, "", args...)
|
n.fn(ctx, level, msg, args...)
|
||||||
}
|
|
||||||
func (n *callbackLogger) Errorf(format string, args ...interface{}) {
|
|
||||||
n.fn(logging.ERROR, format, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Warning(args ...interface{}) {
|
func (n *callbackLogger) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
|
||||||
n.fn(logging.WARNING, "", args...)
|
|
||||||
}
|
|
||||||
func (n *callbackLogger) Warningf(format string, args ...interface{}) {
|
|
||||||
n.fn(logging.WARNING, format, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Notice(args ...interface{}) {
|
func (n *callbackLogger) Debug(msg string, args ...any) {
|
||||||
n.fn(logging.NOTICE, "", args...)
|
n.Log(nil, slog.LevelDebug, msg, args...)
|
||||||
}
|
|
||||||
func (n *callbackLogger) Noticef(format string, args ...interface{}) {
|
|
||||||
n.fn(logging.NOTICE, format, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Info(args ...interface{}) {
|
func (n *callbackLogger) DebugContext(ctx context.Context, msg string, args ...any) {
|
||||||
n.fn(logging.INFO, "", args...)
|
n.Log(ctx, slog.LevelDebug, msg, args...)
|
||||||
}
|
|
||||||
func (n *callbackLogger) Infof(format string, args ...interface{}) {
|
|
||||||
n.fn(logging.INFO, format, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *callbackLogger) Debug(args ...interface{}) {
|
func (n *callbackLogger) Info(msg string, args ...any) {
|
||||||
n.fn(logging.DEBUG, "", args...)
|
n.Log(nil, slog.LevelInfo, msg, args...)
|
||||||
}
|
}
|
||||||
func (n *callbackLogger) Debugf(format string, args ...interface{}) {
|
|
||||||
n.fn(logging.DEBUG, format, args...)
|
func (n *callbackLogger) InfoContext(ctx context.Context, msg string, args ...any) {
|
||||||
|
n.Log(ctx, slog.LevelInfo, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *callbackLogger) Warn(msg string, args ...any) {
|
||||||
|
n.Log(nil, slog.LevelWarn, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *callbackLogger) WarnContext(ctx context.Context, msg string, args ...any) {
|
||||||
|
n.Log(ctx, slog.LevelWarn, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *callbackLogger) Error(msg string, args ...any) {
|
||||||
|
n.Log(nil, slog.LevelError, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *callbackLogger) ErrorContext(ctx context.Context, msg string, args ...any) {
|
||||||
|
n.Log(ctx, slog.LevelError, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJob(t *testing.T) {
|
func TestJob(t *testing.T) {
|
||||||
@ -115,9 +117,9 @@ func TestDefaultJobErrorHandler(t *testing.T) {
|
|||||||
|
|
||||||
fn := DefaultJobErrorHandler()
|
fn := DefaultJobErrorHandler()
|
||||||
require.NotNil(t, fn)
|
require.NotNil(t, fn)
|
||||||
fn("job", errors.New("test"), &callbackLogger{fn: func(level logging.Level, s string, i ...interface{}) {
|
fn("job", errors.New("test"), &callbackLogger{fn: func(_ context.Context, level slog.Level, s string, i ...interface{}) {
|
||||||
require.Len(t, i, 2)
|
require.Len(t, i, 2)
|
||||||
assert.Equal(t, fmt.Sprintf("%s", i[1]), "test")
|
assert.Equal(t, "error=test", fmt.Sprintf("%s", i[1]))
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,9 +130,9 @@ func TestDefaultJobPanicHandler(t *testing.T) {
|
|||||||
|
|
||||||
fn := DefaultJobPanicHandler()
|
fn := DefaultJobPanicHandler()
|
||||||
require.NotNil(t, fn)
|
require.NotNil(t, fn)
|
||||||
fn("job", errors.New("test"), &callbackLogger{fn: func(level logging.Level, s string, i ...interface{}) {
|
fn("job", errors.New("test"), &callbackLogger{fn: func(_ context.Context, level slog.Level, s string, i ...interface{}) {
|
||||||
require.Len(t, i, 2)
|
require.Len(t, i, 2)
|
||||||
assert.Equal(t, fmt.Sprintf("%s", i[1]), "test")
|
assert.Equal(t, "value=test", fmt.Sprintf("%s", i[1]))
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +149,7 @@ func (t *JobTest) testPanicHandler() JobPanicHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *JobTest) testLogger() logger.Logger {
|
func (t *JobTest) testLogger() logger.Logger {
|
||||||
return &callbackLogger{fn: func(level logging.Level, format string, args ...interface{}) {
|
return &callbackLogger{fn: func(_ context.Context, level slog.Level, format string, args ...interface{}) {
|
||||||
if format == "" {
|
if format == "" {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.Grow(3 * len(args)) // nolint:gomnd
|
sb.Grow(3 * len(args)) // nolint:gomnd
|
||||||
@ -404,11 +406,11 @@ func (t *JobManagerTest) WaitForJob() bool {
|
|||||||
|
|
||||||
func (t *JobManagerTest) Test_SetLogger() {
|
func (t *JobManagerTest) Test_SetLogger() {
|
||||||
t.manager.logger = nil
|
t.manager.logger = nil
|
||||||
t.manager.SetLogger(logger.NewStandard("test", logging.ERROR, logger.DefaultLogFormatter()))
|
t.manager.SetLogger(logger.NewDefaultText())
|
||||||
assert.IsType(t.T(), &logger.StandardLogger{}, t.manager.logger)
|
assert.IsType(t.T(), &logger.Default{}, t.manager.logger)
|
||||||
|
|
||||||
t.manager.SetLogger(nil)
|
t.manager.SetLogger(nil)
|
||||||
assert.IsType(t.T(), &logger.StandardLogger{}, t.manager.logger)
|
assert.IsType(t.T(), &logger.Default{}, t.manager.logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *JobManagerTest) Test_SetLogging() {
|
func (t *JobManagerTest) Test_SetLogging() {
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultAccountLoggerFormat contains default prefix format for the AccountLoggerDecorator.
|
|
||||||
// Its messages will look like this (assuming you will provide the connection URL and account name):
|
|
||||||
// messageHandler (https://any.simla.com => @tg_account): sent message with id=1
|
|
||||||
const DefaultAccountLoggerFormat = "%s (%s => %s):"
|
|
||||||
|
|
||||||
type ComponentAware interface {
|
|
||||||
SetComponent(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionAware interface {
|
|
||||||
SetConnectionIdentifier(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountAware interface {
|
|
||||||
SetAccountIdentifier(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrefixFormatAware interface {
|
|
||||||
SetPrefixFormat(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountLogger interface {
|
|
||||||
PrefixedLogger
|
|
||||||
ComponentAware
|
|
||||||
ConnectionAware
|
|
||||||
AccountAware
|
|
||||||
PrefixFormatAware
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountLoggerDecorator struct {
|
|
||||||
format string
|
|
||||||
component string
|
|
||||||
connIdentifier string
|
|
||||||
accIdentifier string
|
|
||||||
PrefixDecorator
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecorateForAccount(base Logger, component, connIdentifier, accIdentifier string) AccountLogger {
|
|
||||||
return (&AccountLoggerDecorator{
|
|
||||||
PrefixDecorator: PrefixDecorator{
|
|
||||||
backend: base,
|
|
||||||
},
|
|
||||||
component: component,
|
|
||||||
connIdentifier: connIdentifier,
|
|
||||||
accIdentifier: accIdentifier,
|
|
||||||
}).updatePrefix()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewForAccount returns logger for account. It uses StandardLogger under the hood.
|
|
||||||
func NewForAccount(
|
|
||||||
transportCode, component, connIdentifier, accIdentifier string,
|
|
||||||
logLevel logging.Level,
|
|
||||||
logFormat logging.Formatter) AccountLogger {
|
|
||||||
return DecorateForAccount(NewStandard(transportCode, logLevel, logFormat),
|
|
||||||
component, connIdentifier, accIdentifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AccountLoggerDecorator) SetComponent(s string) {
|
|
||||||
a.component = s
|
|
||||||
a.updatePrefix()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AccountLoggerDecorator) SetConnectionIdentifier(s string) {
|
|
||||||
a.connIdentifier = s
|
|
||||||
a.updatePrefix()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AccountLoggerDecorator) SetAccountIdentifier(s string) {
|
|
||||||
a.accIdentifier = s
|
|
||||||
a.updatePrefix()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AccountLoggerDecorator) SetPrefixFormat(s string) {
|
|
||||||
a.format = s
|
|
||||||
a.updatePrefix()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AccountLoggerDecorator) updatePrefix() AccountLogger {
|
|
||||||
a.SetPrefix(fmt.Sprintf(a.prefixFormat(), a.component, a.connIdentifier, a.accIdentifier))
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AccountLoggerDecorator) prefixFormat() string {
|
|
||||||
if a.format == "" {
|
|
||||||
return DefaultAccountLoggerFormat
|
|
||||||
}
|
|
||||||
return a.format
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testComponent = "ComponentName"
|
|
||||||
testConnectionID = "https://test.retailcrm.pro"
|
|
||||||
testAccountID = "@account_name"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AccountLoggerDecoratorTest struct {
|
|
||||||
suite.Suite
|
|
||||||
buf *bytes.Buffer
|
|
||||||
logger AccountLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccountLoggerDecorator(t *testing.T) {
|
|
||||||
suite.Run(t, new(AccountLoggerDecoratorTest))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewForAccount(t *testing.T) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
logger := NewForAccount("code", "component", "conn", "acc", logging.DEBUG, DefaultLogFormatter())
|
|
||||||
logger.(*AccountLoggerDecorator).backend.(*StandardLogger).
|
|
||||||
SetBaseLogger(NewBase(buf, "code", logging.DEBUG, DefaultLogFormatter()))
|
|
||||||
logger.Debugf("message %s", "text")
|
|
||||||
|
|
||||||
assert.Contains(t, buf.String(), fmt.Sprintf(DefaultAccountLoggerFormat+" message", "component", "conn", "acc"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AccountLoggerDecoratorTest) SetupSuite() {
|
|
||||||
t.buf = &bytes.Buffer{}
|
|
||||||
t.logger = DecorateForAccount((&StandardLogger{}).
|
|
||||||
SetBaseLogger(NewBase(t.buf, "code", logging.DEBUG, DefaultLogFormatter())),
|
|
||||||
testComponent, testConnectionID, testAccountID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AccountLoggerDecoratorTest) SetupTest() {
|
|
||||||
t.buf.Reset()
|
|
||||||
t.logger.SetComponent(testComponent)
|
|
||||||
t.logger.SetConnectionIdentifier(testConnectionID)
|
|
||||||
t.logger.SetAccountIdentifier(testAccountID)
|
|
||||||
t.logger.SetPrefixFormat(DefaultAccountLoggerFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AccountLoggerDecoratorTest) Test_LogWithNewFormat() {
|
|
||||||
t.logger.SetPrefixFormat("[%s (%s: %s)] =>")
|
|
||||||
t.logger.Infof("test message")
|
|
||||||
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(),
|
|
||||||
fmt.Sprintf("[%s (%s: %s)] =>", testComponent, testConnectionID, testAccountID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AccountLoggerDecoratorTest) Test_Log() {
|
|
||||||
t.logger.Infof("test message")
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(),
|
|
||||||
fmt.Sprintf(DefaultAccountLoggerFormat, testComponent, testConnectionID, testAccountID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AccountLoggerDecoratorTest) Test_SetComponent() {
|
|
||||||
t.logger.SetComponent("NewComponent")
|
|
||||||
t.logger.Infof("test message")
|
|
||||||
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(),
|
|
||||||
fmt.Sprintf(DefaultAccountLoggerFormat, "NewComponent", testConnectionID, testAccountID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AccountLoggerDecoratorTest) Test_SetConnectionIdentifier() {
|
|
||||||
t.logger.SetComponent("NewComponent")
|
|
||||||
t.logger.SetConnectionIdentifier("https://test.simla.com")
|
|
||||||
t.logger.Infof("test message")
|
|
||||||
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(),
|
|
||||||
fmt.Sprintf(DefaultAccountLoggerFormat, "NewComponent", "https://test.simla.com", testAccountID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AccountLoggerDecoratorTest) Test_SetAccountIdentifier() {
|
|
||||||
t.logger.SetComponent("NewComponent")
|
|
||||||
t.logger.SetConnectionIdentifier("https://test.simla.com")
|
|
||||||
t.logger.SetAccountIdentifier("@new_account_name")
|
|
||||||
t.logger.Infof("test message")
|
|
||||||
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(),
|
|
||||||
fmt.Sprintf(DefaultAccountLoggerFormat, "NewComponent", "https://test.simla.com", "@new_account_name"))
|
|
||||||
}
|
|
21
core/logger/api_client_adapter.go
Normal file
21
core/logger/api_client_adapter.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
retailcrm "github.com/retailcrm/api-client-go/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type apiClientAdapter struct {
|
||||||
|
logger Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIClientAdapter returns BasicLogger that calls underlying logger.
|
||||||
|
func APIClientAdapter(logger Logger) retailcrm.BasicLogger {
|
||||||
|
return &apiClientAdapter{logger: logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf data in the log using Debug method.
|
||||||
|
func (l *apiClientAdapter) Printf(format string, v ...interface{}) {
|
||||||
|
l.logger.Debug(fmt.Sprintf(format, v...))
|
||||||
|
}
|
19
core/logger/attrs.go
Normal file
19
core/logger/attrs.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import "log/slog"
|
||||||
|
|
||||||
|
const (
|
||||||
|
HandlerAttr = "handler"
|
||||||
|
ConnectionAttr = "connection"
|
||||||
|
AccountAttr = "account"
|
||||||
|
CounterIDAttr = "counterId"
|
||||||
|
ErrorAttr = "error"
|
||||||
|
FailureMessageAttr = "failureMessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrAttr(err any) slog.Attr {
|
||||||
|
if err == nil {
|
||||||
|
return slog.String(ErrorAttr, "<nil>")
|
||||||
|
}
|
||||||
|
return slog.Any(ErrorAttr, err)
|
||||||
|
}
|
87
core/logger/default.go
Normal file
87
core/logger/default.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Default struct {
|
||||||
|
Logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefault(log *slog.Logger) Logger {
|
||||||
|
return &Default{Logger: log}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultText() Logger {
|
||||||
|
return NewDefault(slog.New(slog.NewTextHandler(os.Stdout, DefaultOpts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultJSON() Logger {
|
||||||
|
return NewDefault(slog.New(slog.NewJSONHandler(os.Stdout, DefaultOpts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultNil() Logger {
|
||||||
|
return NewDefault(slog.New(NilHandler))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) Handler() slog.Handler {
|
||||||
|
return d.Logger.Handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) ForAccount(handler, conn, acc any) Logger {
|
||||||
|
return d.With(slog.Any(HandlerAttr, handler), slog.Any(ConnectionAttr, conn), slog.Any(AccountAttr, acc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) With(args ...any) Logger {
|
||||||
|
return &Default{Logger: d.Logger.With(args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) WithGroup(name string) Logger {
|
||||||
|
return &Default{Logger: d.Logger.WithGroup(name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
return d.Logger.Enabled(ctx, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) Log(ctx context.Context, level slog.Level, msg string, args ...any) {
|
||||||
|
d.Logger.Log(ctx, level, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
|
||||||
|
d.Logger.LogAttrs(ctx, level, msg, attrs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) Debug(msg string, args ...any) {
|
||||||
|
d.Logger.Debug(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) DebugContext(ctx context.Context, msg string, args ...any) {
|
||||||
|
d.Logger.DebugContext(ctx, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) Info(msg string, args ...any) {
|
||||||
|
d.Logger.Info(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) InfoContext(ctx context.Context, msg string, args ...any) {
|
||||||
|
d.Logger.InfoContext(ctx, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) Warn(msg string, args ...any) {
|
||||||
|
d.Logger.Warn(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) WarnContext(ctx context.Context, msg string, args ...any) {
|
||||||
|
d.Logger.WarnContext(ctx, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) Error(msg string, args ...any) {
|
||||||
|
d.Logger.Error(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Default) ErrorContext(ctx context.Context, msg string, args ...any) {
|
||||||
|
d.Logger.ErrorContext(ctx, msg, args...)
|
||||||
|
}
|
8
core/logger/handler.go
Normal file
8
core/logger/handler.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import "log/slog"
|
||||||
|
|
||||||
|
var DefaultOpts = &slog.HandlerOptions{
|
||||||
|
AddSource: false,
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
}
|
@ -1,205 +1,44 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"context"
|
||||||
"os"
|
"log/slog"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger contains methods which should be present in logger implementation.
|
// LoggerOld contains methods which should be present in logger implementation.
|
||||||
|
type LoggerOld interface {
|
||||||
|
Fatal(args ...any)
|
||||||
|
Fatalf(format string, args ...any)
|
||||||
|
Panic(args ...any)
|
||||||
|
Panicf(format string, args ...any)
|
||||||
|
Critical(args ...any)
|
||||||
|
Criticalf(format string, args ...any)
|
||||||
|
Error(args ...any)
|
||||||
|
Errorf(format string, args ...any)
|
||||||
|
Warning(args ...any)
|
||||||
|
Warningf(format string, args ...any)
|
||||||
|
Notice(args ...any)
|
||||||
|
Noticef(format string, args ...any)
|
||||||
|
Info(args ...any)
|
||||||
|
Infof(format string, args ...any)
|
||||||
|
Debug(args ...any)
|
||||||
|
Debugf(format string, args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Fatal(args ...interface{})
|
Handler() slog.Handler
|
||||||
Fatalf(format string, args ...interface{})
|
With(args ...any) Logger
|
||||||
Panic(args ...interface{})
|
WithGroup(name string) Logger
|
||||||
Panicf(format string, args ...interface{})
|
ForAccount(handler, conn, acc any) Logger
|
||||||
Critical(args ...interface{})
|
Enabled(ctx context.Context, level slog.Level) bool
|
||||||
Criticalf(format string, args ...interface{})
|
Log(ctx context.Context, level slog.Level, msg string, args ...any)
|
||||||
Error(args ...interface{})
|
LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr)
|
||||||
Errorf(format string, args ...interface{})
|
Debug(msg string, args ...any)
|
||||||
Warning(args ...interface{})
|
DebugContext(ctx context.Context, msg string, args ...any)
|
||||||
Warningf(format string, args ...interface{})
|
Info(msg string, args ...any)
|
||||||
Notice(args ...interface{})
|
InfoContext(ctx context.Context, msg string, args ...any)
|
||||||
Noticef(format string, args ...interface{})
|
Warn(msg string, args ...any)
|
||||||
Info(args ...interface{})
|
WarnContext(ctx context.Context, msg string, args ...any)
|
||||||
Infof(format string, args ...interface{})
|
Error(msg string, args ...any)
|
||||||
Debug(args ...interface{})
|
ErrorContext(ctx context.Context, msg string, args ...any)
|
||||||
Debugf(format string, args ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// StandardLogger is a default implementation of Logger. Uses github.com/op/go-logging under the hood.
|
|
||||||
// This logger can prevent any write operations (disabled by default, use .Exclusive() method to enable).
|
|
||||||
type StandardLogger struct {
|
|
||||||
logger *logging.Logger
|
|
||||||
mutex *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStandard will create new StandardLogger with specified formatter.
|
|
||||||
// Usage:
|
|
||||||
// logger := NewLogger("telegram", logging.ERROR, DefaultLogFormatter())
|
|
||||||
func NewStandard(transportCode string, logLevel logging.Level, logFormat logging.Formatter) *StandardLogger {
|
|
||||||
return &StandardLogger{
|
|
||||||
logger: NewBase(os.Stdout, transportCode, logLevel, logFormat),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBase is a constructor for underlying logger in the StandardLogger struct.
|
|
||||||
func NewBase(out io.Writer, transportCode string, logLevel logging.Level, logFormat logging.Formatter) *logging.Logger {
|
|
||||||
logger := logging.MustGetLogger(transportCode)
|
|
||||||
logBackend := logging.NewLogBackend(out, "", 0)
|
|
||||||
formatBackend := logging.NewBackendFormatter(logBackend, logFormat)
|
|
||||||
backend1Leveled := logging.AddModuleLevel(formatBackend)
|
|
||||||
backend1Leveled.SetLevel(logLevel, "")
|
|
||||||
logger.SetBackend(backend1Leveled)
|
|
||||||
|
|
||||||
return logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultLogFormatter will return default formatter for logs.
|
|
||||||
func DefaultLogFormatter() logging.Formatter {
|
|
||||||
return logging.MustStringFormatter(
|
|
||||||
`%{time:2006-01-02 15:04:05.000} %{level:.4s} => %{message}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclusive makes logger goroutine-safe.
|
|
||||||
func (l *StandardLogger) Exclusive() *StandardLogger {
|
|
||||||
if l.mutex == nil {
|
|
||||||
l.mutex = &sync.RWMutex{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBaseLogger replaces base logger with the provided instance.
|
|
||||||
func (l *StandardLogger) SetBaseLogger(logger *logging.Logger) *StandardLogger {
|
|
||||||
l.logger = logger
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// lock locks logger.
|
|
||||||
func (l *StandardLogger) lock() {
|
|
||||||
if l.mutex != nil {
|
|
||||||
l.mutex.Lock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unlock unlocks logger.
|
|
||||||
func (l *StandardLogger) unlock() {
|
|
||||||
if l.mutex != nil {
|
|
||||||
l.mutex.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1).
|
|
||||||
func (l *StandardLogger) Fatal(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Fatal(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalf is equivalent to l.Critical followed by a call to os.Exit(1).
|
|
||||||
func (l *StandardLogger) Fatalf(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic().
|
|
||||||
func (l *StandardLogger) Panic(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicf is equivalent to l.Critical followed by a call to panic().
|
|
||||||
func (l *StandardLogger) Panicf(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Panicf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Critical logs a message using CRITICAL as log level.
|
|
||||||
func (l *StandardLogger) Critical(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Critical(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Criticalf logs a message using CRITICAL as log level.
|
|
||||||
func (l *StandardLogger) Criticalf(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Criticalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs a message using ERROR as log level.
|
|
||||||
func (l *StandardLogger) Error(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf logs a message using ERROR as log level.
|
|
||||||
func (l *StandardLogger) Errorf(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning logs a message using WARNING as log level.
|
|
||||||
func (l *StandardLogger) Warning(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Warning(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningf logs a message using WARNING as log level.
|
|
||||||
func (l *StandardLogger) Warningf(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notice logs a message using NOTICE as log level.
|
|
||||||
func (l *StandardLogger) Notice(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Notice(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Noticef logs a message using NOTICE as log level.
|
|
||||||
func (l *StandardLogger) Noticef(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Noticef(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs a message using INFO as log level.
|
|
||||||
func (l *StandardLogger) Info(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof logs a message using INFO as log level.
|
|
||||||
func (l *StandardLogger) Infof(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logs a message using DEBUG as log level.
|
|
||||||
func (l *StandardLogger) Debug(args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf logs a message using DEBUG as log level.
|
|
||||||
func (l *StandardLogger) Debugf(format string, args ...interface{}) {
|
|
||||||
l.lock()
|
|
||||||
defer l.unlock()
|
|
||||||
l.logger.Debugf(format, args...)
|
|
||||||
}
|
}
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StandardLoggerTest struct {
|
|
||||||
suite.Suite
|
|
||||||
logger *StandardLogger
|
|
||||||
buf *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogger_NewLogger(t *testing.T) {
|
|
||||||
logger := NewStandard("code", logging.DEBUG, DefaultLogFormatter())
|
|
||||||
assert.NotNil(t, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogger_DefaultLogFormatter(t *testing.T) {
|
|
||||||
formatter := DefaultLogFormatter()
|
|
||||||
|
|
||||||
assert.NotNil(t, formatter)
|
|
||||||
assert.IsType(t, logging.MustStringFormatter(`%{message}`), formatter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Logger(t *testing.T) {
|
|
||||||
suite.Run(t, new(StandardLoggerTest))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) SetupSuite() {
|
|
||||||
t.buf = &bytes.Buffer{}
|
|
||||||
t.logger = (&StandardLogger{}).
|
|
||||||
Exclusive().
|
|
||||||
SetBaseLogger(NewBase(t.buf, "code", logging.DEBUG, DefaultLogFormatter()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) SetupTest() {
|
|
||||||
t.buf.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Cover Fatal and Fatalf (implementation below is no-op)
|
|
||||||
// func (t *StandardLoggerTest) Test_Fatal() {
|
|
||||||
// if os.Getenv("FLAG") == "1" {
|
|
||||||
// t.logger.Fatal("test", "fatal")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// cmd := exec.Command(os.Args[0], "-test.run=TestGetConfig")
|
|
||||||
// cmd.Env = append(os.Environ(), "FLAG=1")
|
|
||||||
// err := cmd.Run()
|
|
||||||
|
|
||||||
// e, ok := err.(*exec.ExitError)
|
|
||||||
// expectedErrorString := "test fatal"
|
|
||||||
// t.Assert().Equal(true, ok)
|
|
||||||
// t.Assert().Equal(expectedErrorString, e.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Panic() {
|
|
||||||
defer func() {
|
|
||||||
t.Assert().NotNil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), "panic")
|
|
||||||
}()
|
|
||||||
t.logger.Panic("panic")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Panicf() {
|
|
||||||
defer func() {
|
|
||||||
t.Assert().NotNil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), "panicf")
|
|
||||||
}()
|
|
||||||
t.logger.Panicf("panicf")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Critical() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), "critical")
|
|
||||||
}()
|
|
||||||
t.logger.Critical("critical")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Criticalf() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), "critical")
|
|
||||||
}()
|
|
||||||
t.logger.Criticalf("critical")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Warning() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "WARN")
|
|
||||||
t.Assert().Contains(t.buf.String(), "warning")
|
|
||||||
}()
|
|
||||||
t.logger.Warning("warning")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Notice() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "NOTI")
|
|
||||||
t.Assert().Contains(t.buf.String(), "notice")
|
|
||||||
}()
|
|
||||||
t.logger.Notice("notice")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Info() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(), "info")
|
|
||||||
}()
|
|
||||||
t.logger.Info("info")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Debug() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "DEBU")
|
|
||||||
t.Assert().Contains(t.buf.String(), "debug")
|
|
||||||
}()
|
|
||||||
t.logger.Debug("debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Warningf() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "WARN")
|
|
||||||
t.Assert().Contains(t.buf.String(), "warning")
|
|
||||||
}()
|
|
||||||
t.logger.Warningf("%s", "warning")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Noticef() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "NOTI")
|
|
||||||
t.Assert().Contains(t.buf.String(), "notice")
|
|
||||||
}()
|
|
||||||
t.logger.Noticef("%s", "notice")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Infof() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(), "info")
|
|
||||||
}()
|
|
||||||
t.logger.Infof("%s", "info")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StandardLoggerTest) Test_Debugf() {
|
|
||||||
defer func() {
|
|
||||||
t.Require().Nil(recover())
|
|
||||||
t.Assert().Contains(t.buf.String(), "DEBU")
|
|
||||||
t.Assert().Contains(t.buf.String(), "debug")
|
|
||||||
}()
|
|
||||||
t.logger.Debugf("%s", "debug")
|
|
||||||
}
|
|
26
core/logger/nil_handler.go
Normal file
26
core/logger/nil_handler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NilHandler slog.Handler = &nilHandler{}
|
||||||
|
|
||||||
|
type nilHandler struct{}
|
||||||
|
|
||||||
|
func (n *nilHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nilHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nilHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nilHandler) WithGroup(name string) slog.Handler {
|
||||||
|
return n
|
||||||
|
}
|
@ -1,45 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Nil provides Logger implementation that does almost nothing when called.
|
|
||||||
// Panic, Panicf, Fatal and Fatalf methods still cause panic and immediate program termination respectively.
|
|
||||||
// All other methods won't do anything at all.
|
|
||||||
type Nil struct{}
|
|
||||||
|
|
||||||
func (n Nil) Fatal(args ...interface{}) {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Nil) Fatalf(format string, args ...interface{}) {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Nil) Panic(args ...interface{}) {
|
|
||||||
panic(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Nil) Panicf(format string, args ...interface{}) {
|
|
||||||
panic(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Nil) Critical(args ...interface{}) {}
|
|
||||||
func (n Nil) Criticalf(format string, args ...interface{}) {}
|
|
||||||
func (n Nil) Error(args ...interface{}) {}
|
|
||||||
func (n Nil) Errorf(format string, args ...interface{}) {}
|
|
||||||
func (n Nil) Warning(args ...interface{}) {}
|
|
||||||
func (n Nil) Warningf(format string, args ...interface{}) {}
|
|
||||||
func (n Nil) Notice(args ...interface{}) {}
|
|
||||||
func (n Nil) Noticef(format string, args ...interface{}) {}
|
|
||||||
func (n Nil) Info(args ...interface{}) {}
|
|
||||||
func (n Nil) Infof(format string, args ...interface{}) {}
|
|
||||||
func (n Nil) Debug(args ...interface{}) {}
|
|
||||||
func (n Nil) Debugf(format string, args ...interface{}) {}
|
|
||||||
|
|
||||||
// NewNil is a Nil logger constructor.
|
|
||||||
func NewNil() Logger {
|
|
||||||
return &Nil{}
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NilTest struct {
|
|
||||||
suite.Suite
|
|
||||||
logger Logger
|
|
||||||
realStdout *os.File
|
|
||||||
r *os.File
|
|
||||||
w *os.File
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNilLogger(t *testing.T) {
|
|
||||||
suite.Run(t, new(NilTest))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *NilTest) SetupSuite() {
|
|
||||||
t.logger = NewNil()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *NilTest) SetupTest() {
|
|
||||||
t.realStdout = os.Stdout
|
|
||||||
t.r, t.w, _ = os.Pipe()
|
|
||||||
os.Stdout = t.w
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *NilTest) TearDownTest() {
|
|
||||||
if t.realStdout != nil {
|
|
||||||
t.Require().NoError(t.w.Close())
|
|
||||||
os.Stdout = t.realStdout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *NilTest) readStdout() string {
|
|
||||||
outC := make(chan string)
|
|
||||||
go func() {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err := io.Copy(&buf, t.r)
|
|
||||||
t.Require().NoError(err)
|
|
||||||
outC <- buf.String()
|
|
||||||
close(outC)
|
|
||||||
}()
|
|
||||||
|
|
||||||
t.Require().NoError(t.w.Close())
|
|
||||||
os.Stdout = t.realStdout
|
|
||||||
t.realStdout = nil
|
|
||||||
|
|
||||||
select {
|
|
||||||
case c := <-outC:
|
|
||||||
return c
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *NilTest) Test_Noop() {
|
|
||||||
t.logger.Critical("message")
|
|
||||||
t.logger.Criticalf("message")
|
|
||||||
t.logger.Error("message")
|
|
||||||
t.logger.Errorf("message")
|
|
||||||
t.logger.Warning("message")
|
|
||||||
t.logger.Warningf("message")
|
|
||||||
t.logger.Notice("message")
|
|
||||||
t.logger.Noticef("message")
|
|
||||||
t.logger.Info("message")
|
|
||||||
t.logger.Infof("message")
|
|
||||||
t.logger.Debug("message")
|
|
||||||
t.logger.Debugf("message")
|
|
||||||
|
|
||||||
t.Assert().Empty(t.readStdout())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *NilTest) Test_Panic() {
|
|
||||||
t.Assert().Panics(func() {
|
|
||||||
t.logger.Panic("")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *NilTest) Test_Panicf() {
|
|
||||||
t.Assert().Panics(func() {
|
|
||||||
t.logger.Panicf("")
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import "github.com/op/go-logging"
|
|
||||||
|
|
||||||
// PrefixAware is implemented if the logger allows you to change the prefix.
|
|
||||||
type PrefixAware interface {
|
|
||||||
SetPrefix(string)
|
|
||||||
Prefix() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixedLogger is a base interface for the logger with prefix.
|
|
||||||
type PrefixedLogger interface {
|
|
||||||
Logger
|
|
||||||
PrefixAware
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixDecorator is an implementation of the PrefixedLogger. It will allow you to decorate any Logger with
|
|
||||||
// the provided predefined prefix.
|
|
||||||
type PrefixDecorator struct {
|
|
||||||
backend Logger
|
|
||||||
prefix []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecorateWithPrefix using provided base logger and provided prefix.
|
|
||||||
// No internal state of the base logger will be touched.
|
|
||||||
func DecorateWithPrefix(backend Logger, prefix string) PrefixedLogger {
|
|
||||||
return &PrefixDecorator{backend: backend, prefix: []interface{}{prefix}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWithPrefix returns logger with prefix. It uses StandardLogger under the hood.
|
|
||||||
func NewWithPrefix(transportCode, prefix string, logLevel logging.Level, logFormat logging.Formatter) PrefixedLogger {
|
|
||||||
return DecorateWithPrefix(NewStandard(transportCode, logLevel, logFormat), prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPrefix will replace existing prefix with the provided value.
|
|
||||||
// Use this format for prefixes: "prefix here:" - omit space at the end (it will be inserted automatically).
|
|
||||||
func (p *PrefixDecorator) SetPrefix(prefix string) {
|
|
||||||
p.prefix = []interface{}{prefix}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) getFormat(fmt string) string {
|
|
||||||
return p.prefix[0].(string) + " " + fmt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Prefix() string {
|
|
||||||
return p.prefix[0].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Fatal(args ...interface{}) {
|
|
||||||
p.backend.Fatal(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Fatalf(format string, args ...interface{}) {
|
|
||||||
p.backend.Fatalf(p.getFormat(format), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Panic(args ...interface{}) {
|
|
||||||
p.backend.Panic(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Panicf(format string, args ...interface{}) {
|
|
||||||
p.backend.Panicf(p.getFormat(format), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Critical(args ...interface{}) {
|
|
||||||
p.backend.Critical(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Criticalf(format string, args ...interface{}) {
|
|
||||||
p.backend.Criticalf(p.getFormat(format), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Error(args ...interface{}) {
|
|
||||||
p.backend.Error(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Errorf(format string, args ...interface{}) {
|
|
||||||
p.backend.Errorf(p.getFormat(format), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Warning(args ...interface{}) {
|
|
||||||
p.backend.Warning(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Warningf(format string, args ...interface{}) {
|
|
||||||
p.backend.Warningf(p.getFormat(format), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Notice(args ...interface{}) {
|
|
||||||
p.backend.Notice(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Noticef(format string, args ...interface{}) {
|
|
||||||
p.backend.Noticef(p.getFormat(format), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Info(args ...interface{}) {
|
|
||||||
p.backend.Info(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Infof(format string, args ...interface{}) {
|
|
||||||
p.backend.Infof(p.getFormat(format), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Debug(args ...interface{}) {
|
|
||||||
p.backend.Debug(append(p.prefix, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixDecorator) Debugf(format string, args ...interface{}) {
|
|
||||||
p.backend.Debugf(p.getFormat(format), args...)
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testPrefix = "TestPrefix:"
|
|
||||||
|
|
||||||
type PrefixDecoratorTest struct {
|
|
||||||
suite.Suite
|
|
||||||
buf *bytes.Buffer
|
|
||||||
logger PrefixedLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrefixDecorator(t *testing.T) {
|
|
||||||
suite.Run(t, new(PrefixDecoratorTest))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewWithPrefix(t *testing.T) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
logger := NewWithPrefix("code", "Prefix:", logging.DEBUG, DefaultLogFormatter())
|
|
||||||
logger.(*PrefixDecorator).backend.(*StandardLogger).
|
|
||||||
SetBaseLogger(NewBase(buf, "code", logging.DEBUG, DefaultLogFormatter()))
|
|
||||||
logger.Debugf("message %s", "text")
|
|
||||||
|
|
||||||
assert.Contains(t, buf.String(), "Prefix: message text")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) SetupSuite() {
|
|
||||||
t.buf = &bytes.Buffer{}
|
|
||||||
t.logger = DecorateWithPrefix((&StandardLogger{}).
|
|
||||||
SetBaseLogger(NewBase(t.buf, "code", logging.DEBUG, DefaultLogFormatter())), testPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) SetupTest() {
|
|
||||||
t.buf.Reset()
|
|
||||||
t.logger.SetPrefix(testPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_SetPrefix() {
|
|
||||||
t.logger.Info("message")
|
|
||||||
t.Assert().Equal(testPrefix, t.logger.Prefix())
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
|
|
||||||
t.logger.SetPrefix(testPrefix + testPrefix)
|
|
||||||
t.logger.Info("message")
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Panic() {
|
|
||||||
t.Require().Panics(func() {
|
|
||||||
t.logger.Panic("message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Panicf() {
|
|
||||||
t.Require().Panics(func() {
|
|
||||||
t.logger.Panicf("%s", "message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Critical() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Critical("message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Criticalf() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Criticalf("%s", "message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "CRIT")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Error() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Error("message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "ERRO")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Errorf() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Errorf("%s", "message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "ERRO")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Warning() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Warning("message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "WARN")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Warningf() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Warningf("%s", "message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "WARN")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Notice() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Notice("message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "NOTI")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Noticef() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Noticef("%s", "message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "NOTI")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Info() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Info("message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Infof() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Infof("%s", "message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "INFO")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Debug() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Debug("message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "DEBU")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *PrefixDecoratorTest) Test_Debugf() {
|
|
||||||
t.Require().NotPanics(func() {
|
|
||||||
t.logger.Debugf("%s", "message")
|
|
||||||
})
|
|
||||||
t.Assert().Contains(t.buf.String(), "DEBU")
|
|
||||||
t.Assert().Contains(t.buf.String(), testPrefix+" message")
|
|
||||||
}
|
|
19
core/logger/zabbix_collector_adapter.go
Normal file
19
core/logger/zabbix_collector_adapter.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metrics "github.com/retailcrm/zabbix-metrics-collector"
|
||||||
|
)
|
||||||
|
|
||||||
|
type zabbixCollectorAdapter struct {
|
||||||
|
log Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *zabbixCollectorAdapter) Errorf(format string, args ...interface{}) {
|
||||||
|
a.log.Error(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ZabbixCollectorAdapter(log Logger) metrics.ErrorLogger {
|
||||||
|
return &zabbixCollectorAdapter{log: log}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
@ -64,14 +65,13 @@ func NewModuleFeaturesUploader(
|
|||||||
awsConfig.WithCredentialsProvider(customProvider),
|
awsConfig.WithCredentialsProvider(customProvider),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Error("cannot load S3 configuration", logger.ErrAttr(err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client := manager.NewUploader(s3.NewFromConfig(cfg))
|
client := manager.NewUploader(s3.NewFromConfig(cfg))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Error("cannot load S3 configuration", logger.ErrAttr(err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,18 +87,18 @@ func NewModuleFeaturesUploader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ModuleFeaturesUploader) Upload() {
|
func (s *ModuleFeaturesUploader) Upload() {
|
||||||
s.log.Debugf("upload module features started...")
|
s.log.Debug("upload module features started...")
|
||||||
|
|
||||||
content, err := os.ReadFile(s.featuresFilename)
|
content, err := os.ReadFile(s.featuresFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Errorf("cannot read markdown file %s %s", s.featuresFilename, err.Error())
|
s.log.Error("cannot read markdown file %s %s", slog.String("fileName", s.featuresFilename), logger.ErrAttr(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, lang := range languages {
|
for _, lang := range languages {
|
||||||
translated, err := s.translate(content, lang)
|
translated, err := s.translate(content, lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Errorf("cannot translate module features file to %s: %s", lang.String(), err.Error())
|
s.log.Error("cannot translate module features file", slog.String("lang", lang.String()), logger.ErrAttr(err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ func (s *ModuleFeaturesUploader) Upload() {
|
|||||||
resp, err := s.uploadFile(html, lang.String())
|
resp, err := s.uploadFile(html, lang.String())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Errorf("cannot upload file %s: %s", lang.String(), err.Error())
|
s.log.Error("cannot upload file", slog.String("lang", lang.String()), logger.ErrAttr(err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ func (s *ModuleFeaturesUploader) Upload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
s.log.Debugf("upload module features finished")
|
s.log.Debug("upload module features finished")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ModuleFeaturesUploader) translate(content []byte, lang language.Tag) ([]byte, error) {
|
func (s *ModuleFeaturesUploader) translate(content []byte, lang language.Tag) ([]byte, error) {
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
||||||
|
"github.com/retailcrm/mg-transport-core/v2/core/util/testutil"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/retailcrm/mg-transport-core/v2/core/config"
|
"github.com/retailcrm/mg-transport-core/v2/core/config"
|
||||||
"github.com/retailcrm/mg-transport-core/v2/core/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ModuleFeaturesUploaderTest struct {
|
type ModuleFeaturesUploaderTest struct {
|
||||||
@ -36,8 +35,7 @@ func (t *ModuleFeaturesUploaderTest) TearDownSuite() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_NewModuleFeaturesUploader() {
|
func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_NewModuleFeaturesUploader() {
|
||||||
logs := &bytes.Buffer{}
|
log := testutil.NewBufferedLogger()
|
||||||
log := logger.NewBase(logs, "code", logging.DEBUG, logger.DefaultLogFormatter())
|
|
||||||
conf := config.AWS{Bucket: "bucketName", FolderName: "folder/name"}
|
conf := config.AWS{Bucket: "bucketName", FolderName: "folder/name"}
|
||||||
|
|
||||||
uploader := NewModuleFeaturesUploader(log, conf, t.localizer, "filename.txt")
|
uploader := NewModuleFeaturesUploader(log, conf, t.localizer, "filename.txt")
|
||||||
@ -50,8 +48,7 @@ func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_NewModuleFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_translate() {
|
func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_translate() {
|
||||||
logs := &bytes.Buffer{}
|
log := testutil.NewBufferedLogger()
|
||||||
log := logger.NewBase(logs, "code", logging.DEBUG, logger.DefaultLogFormatter())
|
|
||||||
conf := config.AWS{Bucket: "bucketName", FolderName: "folder/name"}
|
conf := config.AWS{Bucket: "bucketName", FolderName: "folder/name"}
|
||||||
uploader := NewModuleFeaturesUploader(log, conf, t.localizer, "filename.txt")
|
uploader := NewModuleFeaturesUploader(log, conf, t.localizer, "filename.txt")
|
||||||
content := "test content " + t.localizer.GetLocalizedMessage("message")
|
content := "test content " + t.localizer.GetLocalizedMessage("message")
|
||||||
@ -62,8 +59,7 @@ func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_translate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_uploadFile() {
|
func (t *ModuleFeaturesUploaderTest) TestModuleFeaturesUploader_uploadFile() {
|
||||||
logs := &bytes.Buffer{}
|
log := testutil.NewBufferedLogger()
|
||||||
log := logger.NewBase(logs, "code", logging.DEBUG, logger.DefaultLogFormatter())
|
|
||||||
conf := config.AWS{Bucket: "bucketName", FolderName: "folder/name"}
|
conf := config.AWS{Bucket: "bucketName", FolderName: "folder/name"}
|
||||||
uploader := NewModuleFeaturesUploader(log, conf, t.localizer, "source.md")
|
uploader := NewModuleFeaturesUploader(log, conf, t.localizer, "source.md")
|
||||||
content := "test content"
|
content := "test content"
|
||||||
|
@ -2,6 +2,7 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
@ -22,9 +23,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// reset is borrowed directly from the gin.
|
|
||||||
const reset = "\033[0m"
|
|
||||||
|
|
||||||
// ErrorHandlerFunc will handle errors.
|
// ErrorHandlerFunc will handle errors.
|
||||||
type ErrorHandlerFunc func(recovery interface{}, c *gin.Context)
|
type ErrorHandlerFunc func(recovery interface{}, c *gin.Context)
|
||||||
|
|
||||||
@ -142,17 +140,17 @@ func (s *Sentry) SentryMiddlewares() []gin.HandlerFunc {
|
|||||||
|
|
||||||
// obtainErrorLogger extracts logger from the context or builds it right here from tags used in Sentry events
|
// obtainErrorLogger extracts logger from the context or builds it right here from tags used in Sentry events
|
||||||
// Those tags can be configured with SentryLoggerConfig field.
|
// Those tags can be configured with SentryLoggerConfig field.
|
||||||
func (s *Sentry) obtainErrorLogger(c *gin.Context) logger.AccountLogger {
|
func (s *Sentry) obtainErrorLogger(c *gin.Context) logger.Logger {
|
||||||
if item, ok := c.Get("logger"); ok {
|
if item, ok := c.Get("logger"); ok {
|
||||||
if accountLogger, ok := item.(logger.AccountLogger); ok {
|
if ctxLogger, ok := item.(logger.Logger); ok {
|
||||||
return accountLogger
|
return ctxLogger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionID := "{no connection ID}"
|
connectionID := "{no connection ID}"
|
||||||
accountID := "{no account ID}"
|
accountID := "{no account ID}"
|
||||||
if s.SentryLoggerConfig.TagForConnection == "" && s.SentryLoggerConfig.TagForAccount == "" {
|
if s.SentryLoggerConfig.TagForConnection == "" && s.SentryLoggerConfig.TagForAccount == "" {
|
||||||
return logger.DecorateForAccount(s.Logger, "Sentry", connectionID, accountID)
|
return s.Logger.ForAccount("Sentry", connectionID, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
for tag := range s.tagsFromContext(c) {
|
for tag := range s.tagsFromContext(c) {
|
||||||
@ -164,7 +162,7 @@ func (s *Sentry) obtainErrorLogger(c *gin.Context) logger.AccountLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return logger.DecorateForAccount(s.Logger, "Sentry", connectionID, accountID)
|
return s.Logger.ForAccount("Sentry", connectionID, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tagsSetterMiddleware sets event tags into Sentry events.
|
// tagsSetterMiddleware sets event tags into Sentry events.
|
||||||
@ -201,13 +199,13 @@ func (s *Sentry) exceptionCaptureMiddleware() gin.HandlerFunc { // nolint:gocogn
|
|||||||
for _, err := range publicErrors {
|
for _, err := range publicErrors {
|
||||||
messages[index] = err.Error()
|
messages[index] = err.Error()
|
||||||
s.CaptureException(c, err)
|
s.CaptureException(c, err)
|
||||||
l.Error(err)
|
l.Error(err.Error())
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, err := range privateErrors {
|
for _, err := range privateErrors {
|
||||||
s.CaptureException(c, err)
|
s.CaptureException(c, err)
|
||||||
l.Error(err)
|
l.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if privateLen > 0 || recovery != nil {
|
if privateLen > 0 || recovery != nil {
|
||||||
@ -250,8 +248,9 @@ func (s *Sentry) recoveryMiddleware() gin.HandlerFunc { // nolint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if l != nil {
|
if l != nil {
|
||||||
stack := stacktrace.FormattedStack(3, l.Prefix()+" ")
|
// TODO: Check if we can output stacktraces with prefix data like before if we really need it.
|
||||||
formattedErr := fmt.Sprintf("%s %s", l.Prefix(), err)
|
stack := stacktrace.FormattedStack(3, "trace: ")
|
||||||
|
formattedErr := logger.ErrAttr(err)
|
||||||
httpRequest, _ := httputil.DumpRequest(c.Request, false)
|
httpRequest, _ := httputil.DumpRequest(c.Request, false)
|
||||||
headers := strings.Split(string(httpRequest), "\r\n")
|
headers := strings.Split(string(httpRequest), "\r\n")
|
||||||
for idx, header := range headers {
|
for idx, header := range headers {
|
||||||
@ -259,18 +258,17 @@ func (s *Sentry) recoveryMiddleware() gin.HandlerFunc { // nolint
|
|||||||
if current[0] == "Authorization" {
|
if current[0] == "Authorization" {
|
||||||
headers[idx] = current[0] + ": *"
|
headers[idx] = current[0] + ": *"
|
||||||
}
|
}
|
||||||
headers[idx] = l.Prefix() + " " + headers[idx]
|
headers[idx] = "header: " + headers[idx]
|
||||||
}
|
}
|
||||||
headersToStr := strings.Join(headers, "\r\n")
|
headersToStr := slog.String("headers", strings.Join(headers, "\r\n"))
|
||||||
|
formattedStack := slog.String("stacktrace", string(stack))
|
||||||
switch {
|
switch {
|
||||||
case brokenPipe:
|
case brokenPipe:
|
||||||
l.Errorf("%s\n%s%s", formattedErr, headersToStr, reset)
|
l.Error("error", formattedErr, headersToStr)
|
||||||
case gin.IsDebugging():
|
case gin.IsDebugging():
|
||||||
l.Errorf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
l.Error("[Recovery] panic recovered", headersToStr, formattedErr, formattedStack)
|
||||||
timeFormat(time.Now()), headersToStr, formattedErr, stack, reset)
|
|
||||||
default:
|
default:
|
||||||
l.Errorf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
l.Error("[Recovery] panic recovered", formattedErr, formattedStack)
|
||||||
timeFormat(time.Now()), formattedErr, stack, reset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if brokenPipe {
|
if brokenPipe {
|
||||||
|
@ -2,7 +2,6 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sync"
|
"sync"
|
||||||
@ -278,7 +277,7 @@ func (s *SentryTest) TestSentry_CaptureException() {
|
|||||||
|
|
||||||
func (s *SentryTest) TestSentry_obtainErrorLogger_Existing() {
|
func (s *SentryTest) TestSentry_obtainErrorLogger_Existing() {
|
||||||
ctx, _ := s.ginCtxMock()
|
ctx, _ := s.ginCtxMock()
|
||||||
log := logger.DecorateForAccount(testutil.NewBufferedLogger(), "component", "conn", "acc")
|
log := testutil.NewBufferedLogger().ForAccount("component", "conn", "acc")
|
||||||
ctx.Set("logger", log)
|
ctx.Set("logger", log)
|
||||||
|
|
||||||
s.Assert().Equal(log, s.sentry.obtainErrorLogger(ctx))
|
s.Assert().Equal(log, s.sentry.obtainErrorLogger(ctx))
|
||||||
@ -299,12 +298,8 @@ func (s *SentryTest) TestSentry_obtainErrorLogger_Constructed() {
|
|||||||
|
|
||||||
s.Assert().NotNil(log)
|
s.Assert().NotNil(log)
|
||||||
s.Assert().NotNil(logNoConfig)
|
s.Assert().NotNil(logNoConfig)
|
||||||
s.Assert().Implements((*logger.AccountLogger)(nil), log)
|
s.Assert().Implements((*logger.Logger)(nil), log)
|
||||||
s.Assert().Implements((*logger.AccountLogger)(nil), logNoConfig)
|
s.Assert().Implements((*logger.Logger)(nil), logNoConfig)
|
||||||
s.Assert().Equal(
|
|
||||||
fmt.Sprintf(logger.DefaultAccountLoggerFormat, "Sentry", "{no connection ID}", "{no account ID}"),
|
|
||||||
logNoConfig.Prefix())
|
|
||||||
s.Assert().Equal(fmt.Sprintf(logger.DefaultAccountLoggerFormat, "Sentry", "conn_url", "acc_name"), log.Prefix())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SentryTest) TestSentry_MiddlewaresError() {
|
func (s *SentryTest) TestSentry_MiddlewaresError() {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -231,11 +232,11 @@ func (b *HTTPClientBuilder) buildMocks() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.mockHost != "" && b.mockPort != "" && len(b.mockedDomains) > 0 {
|
if b.mockHost != "" && b.mockPort != "" && len(b.mockedDomains) > 0 {
|
||||||
b.logf("Mock address is \"%s\"\n", net.JoinHostPort(b.mockHost, b.mockPort))
|
b.log("Mock address has been set", slog.String("address", net.JoinHostPort(b.mockHost, b.mockPort)))
|
||||||
b.logf("Mocked domains: ")
|
b.log("Mocked domains: ")
|
||||||
|
|
||||||
for _, domain := range b.mockedDomains {
|
for _, domain := range b.mockedDomains {
|
||||||
b.logf(" - %s\n", domain)
|
b.log(fmt.Sprintf(" - %s\n", domain))
|
||||||
}
|
}
|
||||||
|
|
||||||
b.httpTransport.Proxy = nil
|
b.httpTransport.Proxy = nil
|
||||||
@ -259,7 +260,7 @@ func (b *HTTPClientBuilder) buildMocks() error {
|
|||||||
addr = net.JoinHostPort(b.mockHost, b.mockPort)
|
addr = net.JoinHostPort(b.mockHost, b.mockPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logf("Mocking \"%s\" with \"%s\"\n", oldAddr, addr)
|
b.log(fmt.Sprintf("Mocking \"%s\" with \"%s\"\n", oldAddr, addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,13 +271,13 @@ func (b *HTTPClientBuilder) buildMocks() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// logf prints logs via Engine or via fmt.Printf.
|
// log prints logs via Engine or via fmt.Println.
|
||||||
func (b *HTTPClientBuilder) logf(format string, args ...interface{}) {
|
func (b *HTTPClientBuilder) log(msg string, args ...interface{}) {
|
||||||
if b.logging {
|
if b.logging {
|
||||||
if b.logger != nil {
|
if b.logger != nil {
|
||||||
b.logger.Infof(format, args...)
|
b.logger.Info(msg, args...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(format, args...)
|
fmt.Println(append([]any{msg}, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -137,14 +136,14 @@ func (t *HTTPClientBuilderTest) Test_buildMocks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPClientBuilderTest) Test_WithLogger() {
|
func (t *HTTPClientBuilderTest) Test_WithLogger() {
|
||||||
logger := logger.NewStandard("telegram", logging.ERROR, logger.DefaultLogFormatter())
|
|
||||||
builder := NewHTTPClientBuilder()
|
builder := NewHTTPClientBuilder()
|
||||||
require.Nil(t.T(), builder.logger)
|
require.Nil(t.T(), builder.logger)
|
||||||
|
|
||||||
builder.WithLogger(nil)
|
builder.WithLogger(nil)
|
||||||
assert.Nil(t.T(), builder.logger)
|
assert.Nil(t.T(), builder.logger)
|
||||||
|
|
||||||
builder.WithLogger(logger)
|
log := logger.NewDefaultText()
|
||||||
|
builder.WithLogger(log)
|
||||||
assert.NotNil(t.T(), builder.logger)
|
assert.NotNil(t.T(), builder.logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +152,7 @@ func (t *HTTPClientBuilderTest) Test_logf() {
|
|||||||
assert.Nil(t.T(), recover())
|
assert.Nil(t.T(), recover())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t.builder.logf("test %s", "string")
|
t.builder.log(fmt.Sprintf("test %s", "string"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPClientBuilderTest) Test_Build() {
|
func (t *HTTPClientBuilderTest) Test_Build() {
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"log/slog"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
|
|
||||||
"github.com/retailcrm/mg-transport-core/v2/core/logger"
|
"github.com/retailcrm/mg-transport-core/v2/core/logger"
|
||||||
)
|
)
|
||||||
@ -29,119 +25,46 @@ type BufferedLogger interface {
|
|||||||
|
|
||||||
// BufferLogger is an implementation of the BufferedLogger.
|
// BufferLogger is an implementation of the BufferedLogger.
|
||||||
type BufferLogger struct {
|
type BufferLogger struct {
|
||||||
buf bytes.Buffer
|
logger.Default
|
||||||
rw sync.RWMutex
|
buf LockableBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBufferedLogger returns new BufferedLogger instance.
|
// NewBufferedLogger returns new BufferedLogger instance.
|
||||||
func NewBufferedLogger() BufferedLogger {
|
func NewBufferedLogger() BufferedLogger {
|
||||||
return &BufferLogger{}
|
bl := &BufferLogger{}
|
||||||
|
bl.Logger = slog.New(slog.NewTextHandler(&bl.buf, logger.DefaultOpts))
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// With doesn't do anything here and only added for backwards compatibility with the interface.
|
||||||
|
func (l *BufferLogger) With(args ...any) logger.Logger {
|
||||||
|
return &BufferLogger{
|
||||||
|
Default: logger.Default{
|
||||||
|
Logger: l.Logger.With(args...),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BufferLogger) ForAccount(handler, conn, acc any) logger.Logger {
|
||||||
|
return l.With(slog.Any(logger.HandlerAttr, handler), slog.Any(logger.ConnectionAttr, conn), slog.Any(logger.AccountAttr, acc))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read bytes from the logger buffer. io.Reader implementation.
|
// Read bytes from the logger buffer. io.Reader implementation.
|
||||||
func (l *BufferLogger) Read(p []byte) (n int, err error) {
|
func (l *BufferLogger) Read(p []byte) (n int, err error) {
|
||||||
defer l.rw.RUnlock()
|
|
||||||
l.rw.RLock()
|
|
||||||
return l.buf.Read(p)
|
return l.buf.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String contents of the logger buffer. fmt.Stringer implementation.
|
// String contents of the logger buffer. fmt.Stringer implementation.
|
||||||
func (l *BufferLogger) String() string {
|
func (l *BufferLogger) String() string {
|
||||||
defer l.rw.RUnlock()
|
|
||||||
l.rw.RLock()
|
|
||||||
return l.buf.String()
|
return l.buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes is a shorthand for the underlying bytes.Buffer method. Returns byte slice with the buffer contents.
|
// Bytes is a shorthand for the underlying bytes.Buffer method. Returns byte slice with the buffer contents.
|
||||||
func (l *BufferLogger) Bytes() []byte {
|
func (l *BufferLogger) Bytes() []byte {
|
||||||
defer l.rw.RUnlock()
|
|
||||||
l.rw.RLock()
|
|
||||||
return l.buf.Bytes()
|
return l.buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset is a shorthand for the underlying bytes.Buffer method. It will reset buffer contents.
|
// Reset is a shorthand for the underlying bytes.Buffer method. It will reset buffer contents.
|
||||||
func (l *BufferLogger) Reset() {
|
func (l *BufferLogger) Reset() {
|
||||||
defer l.rw.Unlock()
|
|
||||||
l.rw.Lock()
|
|
||||||
l.buf.Reset()
|
l.buf.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *BufferLogger) write(level logging.Level, args ...interface{}) {
|
|
||||||
defer l.rw.Unlock()
|
|
||||||
l.rw.Lock()
|
|
||||||
l.buf.WriteString(fmt.Sprintln(append([]interface{}{level.String(), "=>"}, args...)...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) writef(level logging.Level, format string, args ...interface{}) {
|
|
||||||
defer l.rw.Unlock()
|
|
||||||
l.rw.Lock()
|
|
||||||
l.buf.WriteString(fmt.Sprintf(level.String()+" => "+format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Fatal(args ...interface{}) {
|
|
||||||
l.write(logging.CRITICAL, args...)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Fatalf(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.CRITICAL, format, args...)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Panic(args ...interface{}) {
|
|
||||||
l.write(logging.CRITICAL, args...)
|
|
||||||
panic(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Panicf(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.CRITICAL, format, args...)
|
|
||||||
panic(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Critical(args ...interface{}) {
|
|
||||||
l.write(logging.CRITICAL, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Criticalf(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.CRITICAL, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Error(args ...interface{}) {
|
|
||||||
l.write(logging.ERROR, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Errorf(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.ERROR, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Warning(args ...interface{}) {
|
|
||||||
l.write(logging.WARNING, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Warningf(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.WARNING, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Notice(args ...interface{}) {
|
|
||||||
l.write(logging.NOTICE, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Noticef(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.NOTICE, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Info(args ...interface{}) {
|
|
||||||
l.write(logging.INFO, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Infof(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.INFO, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Debug(args ...interface{}) {
|
|
||||||
l.write(logging.DEBUG, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *BufferLogger) Debugf(format string, args ...interface{}) {
|
|
||||||
l.writef(logging.DEBUG, format, args...)
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,17 +29,17 @@ func (t *BufferLoggerTest) Test_Read() {
|
|||||||
|
|
||||||
data, err := io.ReadAll(t.logger)
|
data, err := io.ReadAll(t.logger)
|
||||||
t.Require().NoError(err)
|
t.Require().NoError(err)
|
||||||
t.Assert().Equal([]byte(logging.DEBUG.String()+" => test\n"), data)
|
t.Assert().Contains(string(data), "level=DEBUG msg=test")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BufferLoggerTest) Test_Bytes() {
|
func (t *BufferLoggerTest) Test_Bytes() {
|
||||||
t.logger.Debug("test")
|
t.logger.Debug("test")
|
||||||
t.Assert().Equal([]byte(logging.DEBUG.String()+" => test\n"), t.logger.Bytes())
|
t.Assert().Contains(string(t.logger.Bytes()), "level=DEBUG msg=test")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BufferLoggerTest) Test_String() {
|
func (t *BufferLoggerTest) Test_String() {
|
||||||
t.logger.Debug("test")
|
t.logger.Debug("test")
|
||||||
t.Assert().Equal(logging.DEBUG.String()+" => test\n", t.logger.String())
|
t.Assert().Contains(t.logger.String(), "level=DEBUG msg=test")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BufferLoggerTest) TestRace() {
|
func (t *BufferLoggerTest) TestRace() {
|
||||||
|
150
core/util/testutil/lockable_buffer.go
Normal file
150
core/util/testutil/lockable_buffer.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LockableBuffer struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
rw sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Bytes() []byte {
|
||||||
|
defer b.rw.RUnlock()
|
||||||
|
b.rw.RLock()
|
||||||
|
return b.buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) AvailableBuffer() []byte {
|
||||||
|
defer b.rw.RUnlock()
|
||||||
|
b.rw.RLock()
|
||||||
|
return b.buf.AvailableBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) String() string {
|
||||||
|
defer b.rw.RUnlock()
|
||||||
|
b.rw.RLock()
|
||||||
|
return b.buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Len() int {
|
||||||
|
defer b.rw.RUnlock()
|
||||||
|
b.rw.RLock()
|
||||||
|
return b.buf.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Cap() int {
|
||||||
|
defer b.rw.RUnlock()
|
||||||
|
b.rw.RLock()
|
||||||
|
return b.buf.Cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Available() int {
|
||||||
|
defer b.rw.RUnlock()
|
||||||
|
b.rw.RLock()
|
||||||
|
return b.buf.Available()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Truncate(n int) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
b.buf.Truncate(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Reset() {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
b.buf.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Grow(n int) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
b.buf.Grow(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Write(p []byte) (n int, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) WriteString(s string) (n int, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.WriteString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.ReadFrom(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) WriteByte(c byte) error {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.WriteByte(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) WriteRune(r rune) (n int, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.WriteRune(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Read(p []byte) (n int, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) Next(n int) []byte {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.Next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) ReadByte() (byte, error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.ReadByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) ReadRune() (r rune, size int, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.ReadRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) UnreadRune() error {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.UnreadRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) UnreadByte() error {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.UnreadByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) ReadBytes(delim byte) (line []byte, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.ReadBytes(delim)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LockableBuffer) ReadString(delim byte) (line string, err error) {
|
||||||
|
defer b.rw.Unlock()
|
||||||
|
b.rw.Lock()
|
||||||
|
return b.buf.ReadString(delim)
|
||||||
|
}
|
@ -132,7 +132,7 @@ func (u *Utils) GenerateToken() string {
|
|||||||
func (u *Utils) GetAPIClient(
|
func (u *Utils) GetAPIClient(
|
||||||
url, key string, scopes []string, credentials ...[]string) (*retailcrm.Client, int, error) {
|
url, key string, scopes []string, credentials ...[]string) (*retailcrm.Client, int, error) {
|
||||||
client := retailcrm.New(url, key).
|
client := retailcrm.New(url, key).
|
||||||
WithLogger(retailcrm.DebugLoggerAdapter(u.Logger))
|
WithLogger(logger.APIClientAdapter(u.Logger))
|
||||||
client.Debug = u.IsDebug
|
client.Debug = u.IsDebug
|
||||||
|
|
||||||
cr, status, err := client.APICredentials()
|
cr, status, err := client.APICredentials()
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/h2non/gock"
|
"github.com/h2non/gock"
|
||||||
"github.com/op/go-logging"
|
|
||||||
retailcrm "github.com/retailcrm/api-client-go/v2"
|
retailcrm "github.com/retailcrm/api-client-go/v2"
|
||||||
v1 "github.com/retailcrm/mg-transport-api-client-go/v1"
|
v1 "github.com/retailcrm/mg-transport-api-client-go/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -38,7 +38,7 @@ func mgClient() *v1.MgClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UtilsTest) SetupSuite() {
|
func (u *UtilsTest) SetupSuite() {
|
||||||
logger := logger.NewStandard("code", logging.DEBUG, logger.DefaultLogFormatter())
|
logger := logger.NewDefaultText()
|
||||||
awsConfig := config.AWS{
|
awsConfig := config.AWS{
|
||||||
AccessKeyID: "access key id (will be removed)",
|
AccessKeyID: "access key id (will be removed)",
|
||||||
SecretAccessKey: "secret access key",
|
SecretAccessKey: "secret access key",
|
||||||
|
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/retailcrm/mg-transport-core/v2
|
module github.com/retailcrm/mg-transport-core/v2
|
||||||
|
|
||||||
go 1.18
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.3.3
|
github.com/DATA-DOG/go-sqlmock v1.3.3
|
||||||
|
1
go.sum
1
go.sum
@ -157,6 +157,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
Loading…
Reference in New Issue
Block a user