- Remove logger as globals to use dedicated pointer (#104)

# Refactor Logger :
- Remove logger as globals to use dedicated pointer
- Add compat file to allow retro-compatibility (function are deprecated)
- Add field management to allow add custom information
- Add option struct to manage options
- Add entry struct to manage all log entry
- Add multithreading capabilities (with race prevention)
- Allow to log to file + stdout/stderr + syslog
- Add io.WriteCloser compatibility interface
- Add logrus hook to manage option by hook instance
- Add capabilities to custom each logfile/syslog/standard independently
- Add small test suite

# Update Packages for logger new options : 
- archive
- cluster
- context
- httpcli
- httpserver
- ldap
- tests

# Other : 
- bump dependancies
- update minio server for aws test
This commit is contained in:
Nicolas JUHEL
2021-05-21 17:32:47 +02:00
committed by GitHub
parent 9ac6920e16
commit c127359ffb
30 changed files with 2776 additions and 850 deletions

View File

@@ -200,7 +200,6 @@ func ExtractAll(src libiot.FileProgress, originalName, outputPath string, defaul
}
func CreateArchive(archiveType ArchiveType, archive libiot.FileProgress, stripPath string, pathContent ...string) (created bool, err liberr.Error) {
//@TODO: make function
if len(pathContent) < 1 {
//nolint #goerr113
return false, ErrorParamsEmpty.ErrorParent(fmt.Errorf("pathContent is empty"))
@@ -213,6 +212,8 @@ func CreateArchive(archiveType ArchiveType, archive libiot.FileProgress, stripPa
return libtar.Create(archive, stripPath, pathContent...)
case TypeTarGzip:
return libtar.CreateGzip(archive, stripPath, pathContent...)
//@TODO: add zip mode
}
return false, nil

View File

@@ -40,37 +40,71 @@ func init() {
pkg: pkgName,
}
})
}
type FuncLogger func() liblog.Logger
func SetLoggerFactory(log FuncLogger) {
if log == nil {
log = liblog.GetDefault
}
dgblog.SetLoggerFactory(func(pkgName string) dgblog.ILogger {
return &logDragonBoart{
pkg: pkgName,
log: log,
}
})
}
type logDragonBoart struct {
pkg string
log FuncLogger
}
func (l *logDragonBoart) SetLevel(level dgblog.LogLevel) {
if l.log == nil {
return
}
switch level {
case dgblog.CRITICAL:
l.log().SetLevel(liblog.FatalLevel)
case dgblog.ERROR:
l.log().SetLevel(liblog.ErrorLevel)
case dgblog.WARNING:
l.log().SetLevel(liblog.WarnLevel)
case dgblog.INFO:
l.log().SetLevel(liblog.InfoLevel)
case dgblog.DEBUG:
l.log().SetLevel(liblog.DebugLevel)
}
}
func (l *logDragonBoart) logMsg(lvl liblog.Level, message string, args ...interface{}) {
if l.log == nil {
l.log = liblog.GetDefault
}
l.log().Entry(lvl, message, args...).FieldAdd("dragonboat.package", l.pkg).Log()
}
func (l *logDragonBoart) Debugf(format string, args ...interface{}) {
var newArg = append(make([]interface{}, 0), l.pkg)
liblog.DebugLevel.Logf("[DragonBoat: %s] "+format, append(newArg, args...)...)
l.logMsg(liblog.DebugLevel, format, args...)
}
func (l *logDragonBoart) Infof(format string, args ...interface{}) {
var newArg = append(make([]interface{}, 0), l.pkg)
liblog.InfoLevel.Logf("[DragonBoat: %s] "+format, append(newArg, args...)...)
l.logMsg(liblog.InfoLevel, format, args...)
}
func (l *logDragonBoart) Warningf(format string, args ...interface{}) {
var newArg = append(make([]interface{}, 0), l.pkg)
liblog.WarnLevel.Logf("[DragonBoat: %s] "+format, append(newArg, args...)...)
l.logMsg(liblog.WarnLevel, format, args...)
}
func (l *logDragonBoart) Errorf(format string, args ...interface{}) {
var newArg = append(make([]interface{}, 0), l.pkg)
liblog.ErrorLevel.Logf("[DragonBoat: %s] "+format, append(newArg, args...)...)
l.logMsg(liblog.ErrorLevel, format, args...)
}
func (l *logDragonBoart) Panicf(format string, args ...interface{}) {
var newArg = append(make([]interface{}, 0), l.pkg)
liblog.FatalLevel.Logf("[DragonBoat: %s] "+format, append(newArg, args...)...)
l.logMsg(liblog.FatalLevel, format, args...)
}

View File

@@ -56,9 +56,14 @@ type GinTonic interface {
GetStringMap(key string) (sm map[string]interface{})
GetStringMapString(key string) (sms map[string]string)
GetStringMapStringSlice(key string) (smss map[string][]string)
SetLogger(log FuncLogger)
}
type FuncLogger func() liblog.Logger
type ctxGinTonic struct {
l FuncLogger
g *gin.Context
x context.Context
c context.CancelFunc
@@ -88,9 +93,22 @@ func NewGinTonic(c *gin.Context) GinTonic {
}
return &ctxGinTonic{
c,
x,
l,
l: liblog.GetDefault,
g: c,
x: x,
c: l,
}
}
func (c *ctxGinTonic) SetLogger(fct FuncLogger) {
c.l = fct
}
func (c *ctxGinTonic) log(lvl liblog.Level, msg string, args ...interface{}) {
if c.l != nil {
c.l().Entry(lvl, msg, args...).Log()
} else {
liblog.GetDefault().Entry(lvl, msg, args...).Log()
}
}
@@ -101,11 +119,11 @@ func (c *ctxGinTonic) CancelOnSignal(s ...os.Signal) {
select {
case <-sc:
liblog.InfoLevel.Logf("Os Signal received, calling context cancel !")
c.log(liblog.InfoLevel, "OS Signal received, calling context cancel !")
c.c()
return
case <-c.Done():
liblog.InfoLevel.Logf("Context has been closed...")
c.log(liblog.InfoLevel, "Context has been closed !")
return
}
}()

22
go.mod
View File

@@ -14,14 +14,14 @@ require (
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/aokoli/goutils v1.1.1 // indirect
github.com/armon/go-metrics v0.3.8 // indirect
github.com/aws/aws-sdk-go-v2 v1.5.0
github.com/aws/aws-sdk-go-v2/config v1.2.0
github.com/aws/aws-sdk-go-v2/credentials v1.2.0
github.com/aws/aws-sdk-go-v2/service/iam v1.4.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.7.0
github.com/aws/aws-sdk-go-v2 v1.6.0
github.com/aws/aws-sdk-go-v2/config v1.3.0
github.com/aws/aws-sdk-go-v2/credentials v1.2.1
github.com/aws/aws-sdk-go-v2/service/iam v1.5.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.8.0
github.com/c-bata/go-prompt v0.2.6
github.com/cockroachdb/errors v1.8.4 // indirect
github.com/cockroachdb/pebble v0.0.0-20210515132633-39fe91db49c0 // indirect
github.com/cockroachdb/pebble v0.0.0-20210520205706-edcfca5432fb // indirect
github.com/cockroachdb/redact v1.0.9 // indirect
github.com/fatih/color v1.11.0
github.com/fxamacker/cbor/v2 v2.2.0
@@ -40,6 +40,7 @@ require (
github.com/google/uuid v1.2.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v0.16.1
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
github.com/hashicorp/go-msgpack v1.1.5 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -59,13 +60,14 @@ require (
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lni/dragonboat/v3 v3.3.4
github.com/matcornic/hermes/v2 v2.1.0
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.12 // indirect
github.com/miekg/dns v1.1.42 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/nats-io/jwt/v2 v2.0.2
github.com/nats-io/nats-server/v2 v2.2.4
github.com/nats-io/nats-server/v2 v2.2.5
github.com/nats-io/nats.go v1.11.0
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.2
@@ -76,6 +78,7 @@ require (
github.com/shirou/gopsutil v3.21.4+incompatible
github.com/sirupsen/logrus v1.8.1
github.com/spf13/jwalterweatherman v1.1.0
github.com/stretchr/testify v1.7.0 // indirect
github.com/ugorji/go v1.2.6 // indirect
github.com/vanng822/go-premailer v1.20.1 // indirect
github.com/vbauerster/mpb/v5 v5.4.0
@@ -85,11 +88,12 @@ require (
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
golang.org/x/exp v0.0.0-20210514180818-737f94c0881e // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
golang.org/x/sys v0.0.0-20210521090106-6ca3eb03dfc2 // indirect
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

View File

@@ -33,8 +33,8 @@ import (
"net/url"
"strings"
"github.com/nabbar/golib/errors"
"github.com/nabbar/golib/logger"
liberr "github.com/nabbar/golib/errors"
liblog "github.com/nabbar/golib/logger"
)
type httpClient struct {
@@ -45,11 +45,11 @@ type httpClient struct {
type HTTP interface {
SetContext(ctx context.Context)
Check() errors.Error
Call(file *bytes.Buffer) (bool, *bytes.Buffer, errors.Error)
Check() liberr.Error
Call(file *bytes.Buffer) (bool, *bytes.Buffer, liberr.Error)
}
func NewClient(uri string) (HTTP, errors.Error) {
func NewClient(uri string) (HTTP, liberr.Error) {
var (
pUri *url.URL
err error
@@ -88,7 +88,7 @@ func (obj *httpClient) SetContext(ctx context.Context) {
}
}
func (obj *httpClient) Check() errors.Error {
func (obj *httpClient) Check() liberr.Error {
req, e := obj.newRequest(http.MethodHead, nil)
if e != nil {
@@ -106,7 +106,7 @@ func (obj *httpClient) Check() errors.Error {
return e
}
func (obj *httpClient) Call(body *bytes.Buffer) (bool, *bytes.Buffer, errors.Error) {
func (obj *httpClient) Call(body *bytes.Buffer) (bool, *bytes.Buffer, liberr.Error) {
req, e := obj.newRequest(http.MethodPost, body)
if e != nil {
@@ -122,7 +122,7 @@ func (obj *httpClient) Call(body *bytes.Buffer) (bool, *bytes.Buffer, errors.Err
return obj.checkResponse(res)
}
func (obj *httpClient) newRequest(method string, body *bytes.Buffer) (*http.Request, errors.Error) {
func (obj *httpClient) newRequest(method string, body *bytes.Buffer) (*http.Request, liberr.Error) {
var reader *bytes.Reader
if body != nil && body.Len() > 0 {
@@ -137,7 +137,7 @@ func (obj *httpClient) newRequest(method string, body *bytes.Buffer) (*http.Requ
return req, nil
}
func (obj *httpClient) doRequest(req *http.Request) (*http.Response, errors.Error) {
func (obj *httpClient) doRequest(req *http.Request) (*http.Response, liberr.Error) {
res, e := obj.cli.Do(req)
if e != nil {
@@ -147,7 +147,7 @@ func (obj *httpClient) doRequest(req *http.Request) (*http.Response, errors.Erro
return res, nil
}
func (obj *httpClient) checkResponse(res *http.Response) (bool, *bytes.Buffer, errors.Error) {
func (obj *httpClient) checkResponse(res *http.Response) (bool, *bytes.Buffer, liberr.Error) {
var buf *bytes.Buffer
if res.Body != nil {
@@ -163,7 +163,7 @@ func (obj *httpClient) checkResponse(res *http.Response) (bool, *bytes.Buffer, e
return false, nil, BUFFER_WRITE.ErrorParent(err)
}
logger.DebugLevel.LogError(err)
liblog.GetDefault().Entry(liblog.DebugLevel, "").ErrorAdd(err).FieldAdd("remote.uri", res.Request.URL.String()).FieldAdd("remote.method", res.Request.Method).Log()
}
return strings.HasPrefix(res.Status, "2"), buf, nil

View File

@@ -62,6 +62,7 @@ type PoolServer interface {
Del(bindAddress string) PoolServer
Has(bindAddress string) bool
Len() int
SetLogger(log FuncGetLogger)
MapRun(f MapRunPoolServer)
MapUpd(f MapUpdPoolServer)
@@ -190,6 +191,13 @@ func (p pool) Len() int {
return len(p)
}
func (p pool) SetLogger(log FuncGetLogger) {
p.MapUpd(func(srv Server) Server {
srv.SetLogger(log)
return srv
})
}
func (p pool) List(fieldFilter, fieldReturn FieldType, pattern, regex string) []string {
var (
r = make([]string, 0)

View File

@@ -48,6 +48,7 @@ import (
const _TimeoutWaitingPortFreeing = 500 * time.Microsecond
type srvRun struct {
log func() liblog.Logger
err *atomic.Value
run *atomic.Value
snm string
@@ -65,8 +66,9 @@ type run interface {
Shutdown()
}
func newRun() run {
func newRun(log FuncGetLogger) run {
return &srvRun{
log: log,
err: new(atomic.Value),
run: new(atomic.Value),
srv: nil,
@@ -158,9 +160,21 @@ func (s *srvRun) Listen(cfg *ServerConfig, handler http.Handler) liberr.Error {
name = bind
}
var _log liblog.Logger
if s.log == nil {
_log = liblog.GetDefault()
} else if l := s.log(); l == nil {
_log = liblog.GetDefault()
} else {
_log = l
}
_log.SetFields(_log.GetFields().Add("http server '%s'", name))
srv := &http.Server{
Addr: cfg.GetListen().Host,
ErrorLog: liblog.GetLogger(liblog.ErrorLevel, log.LstdFlags|log.Lmicroseconds, "[http/http2 server '%s']", name),
ErrorLog: _log.GetStdLogger(liblog.ErrorLevel, log.LstdFlags|log.Lmicroseconds),
}
if cfg.ReadTimeout > 0 {

View File

@@ -34,6 +34,8 @@ import (
"sync/atomic"
"time"
liblog "github.com/nabbar/golib/logger"
liberr "github.com/nabbar/golib/errors"
libsts "github.com/nabbar/golib/status"
)
@@ -42,12 +44,17 @@ const (
timeoutShutdown = 10 * time.Second
)
type FuncGetLogger func() liblog.Logger
type server struct {
log *atomic.Value
run *atomic.Value
cfg *atomic.Value
}
type Server interface {
SetLogger(log FuncGetLogger)
GetConfig() *ServerConfig
SetConfig(cfg *ServerConfig) bool
@@ -75,11 +82,26 @@ func NewServer(cfg *ServerConfig) Server {
c.Store(cfg.Clone())
return &server{
log: new(atomic.Value),
cfg: c,
run: new(atomic.Value),
}
}
func (s *server) SetLogger(log FuncGetLogger) {
s.log.Store(log)
}
func (s *server) GetLogger() FuncGetLogger {
if i := s.log.Load(); i == nil {
return nil
} else if f, ok := i.(FuncGetLogger); ok {
return f
}
return nil
}
func (s *server) GetConfig() *ServerConfig {
if s.cfg == nil {
return nil
@@ -121,11 +143,11 @@ func (s *server) SetConfig(cfg *ServerConfig) bool {
func (s *server) getRun() run {
if s.run == nil {
return newRun()
return newRun(s.GetLogger())
} else if i := s.run.Load(); i == nil {
return newRun()
return newRun(s.GetLogger())
} else if r, ok := i.(run); !ok {
return newRun()
return newRun(s.GetLogger())
} else {
return r
}

View File

@@ -39,6 +39,8 @@ import (
liblog "github.com/nabbar/golib/logger"
)
type FuncLogger func() liblog.Logger
//HelperLDAP struct use to manage connection to server and request it.
type HelperLDAP struct {
Attributes []string
@@ -49,6 +51,7 @@ type HelperLDAP struct {
bindDN string
bindPass string
ctx context.Context
log FuncLogger
}
//NewLDAP build a new LDAP helper based on config struct given.
@@ -67,6 +70,37 @@ func NewLDAP(ctx context.Context, cnf *Config, attributes []string) (*HelperLDAP
}, nil
}
//SetLogger is used to specify the logger to be used for debug messgae
func (lc *HelperLDAP) SetLogger(fct FuncLogger) {
lc.log = fct
}
func (lc HelperLDAP) getLogEntry(lvl liblog.Level, msg string, args ...interface{}) *liblog.Entry {
var log liblog.Logger
if lc.log == nil {
log = liblog.GetDefault()
} else if l := lc.log(); l == nil {
log = liblog.GetDefault()
} else {
log = l
}
return log.Entry(lvl, msg, args...).FieldAdd("ldap.host", lc.config.ServerAddr(lc.tlsMode == TLSModeTLS)).FieldAdd("ldap.tlsMode", lc.tlsMode.String())
}
func (lc HelperLDAP) getLogEntryErr(lvlKO liblog.Level, err error, msg string, args ...interface{}) *liblog.Entry {
var log liblog.Logger
if lc.log == nil {
log = liblog.GetDefault()
} else if l := lc.log(); l == nil {
log = liblog.GetDefault()
} else {
log = l
}
return log.Entry(lvlKO, msg, args...).FieldAdd("ldap.host", lc.config.ServerAddr(lc.tlsMode == TLSModeTLS)).ErrorAdd(err)
}
//SetCredentials used to defined the BindDN and password for connection.
func (lc *HelperLDAP) SetCredentials(user, pass string) {
lc.bindDN = user
@@ -193,7 +227,7 @@ func (lc *HelperLDAP) tryConnect() (TLSMode, liberr.Error) {
if lc.config.Portldaps != 0 {
l, err = lc.dialTLS()
liblog.DebugLevel.LogErrorCtxf(liblog.DebugLevel, "connecting ldap with tls mode '%s'", err, TLSModeTLS.String())
lc.getLogEntryErr(liblog.DebugLevel, err, "connecting ldap with tls mode '%s'", TLSModeTLS.String()).Check(liblog.DebugLevel)
if err == nil {
return TLSModeTLS, nil
@@ -205,14 +239,14 @@ func (lc *HelperLDAP) tryConnect() (TLSMode, liberr.Error) {
}
l, err = lc.dial()
liblog.DebugLevel.LogErrorCtxf(liblog.DebugLevel, "connecting ldap with tls mode '%s'", err, TLSModeNone.String())
lc.getLogEntryErr(liblog.DebugLevel, err, "connecting ldap with tls mode '%s'", TLSModeNone.String()).Check(liblog.DebugLevel)
if err != nil {
return _TLSModeInit, err
}
err = lc.starttls(l)
liblog.DebugLevel.LogErrorCtxf(liblog.DebugLevel, "connecting ldap with tls mode '%s'", err, TLSModeStarttls.String())
lc.getLogEntryErr(liblog.DebugLevel, err, "connecting ldap with tls mode '%s'", TLSModeStarttls.String()).Check(liblog.DebugLevel)
if err == nil {
return TLSModeStarttls, nil
@@ -276,7 +310,7 @@ func (lc *HelperLDAP) connect() liberr.Error {
}
}
liblog.DebugLevel.Logf("ldap connected with tls mode '%s'", lc.tlsMode.String())
lc.getLogEntry(liblog.DebugLevel, "ldap connected").Log()
lc.conn = l
}
@@ -347,7 +381,7 @@ func (lc *HelperLDAP) Connect() liberr.Error {
return err
}
liblog.DebugLevel.Logf("Bind success on LDAP server %s with tls mode '%s'", lc.config.ServerAddr(lc.tlsMode == TLSModeTLS), lc.tlsMode.String())
lc.getLogEntry(liblog.DebugLevel, "ldap bind success").FieldAdd("bind.dn", lc.bindDN).Log()
return nil
}
@@ -377,7 +411,7 @@ func (lc *HelperLDAP) runSearch(filter string, attributes []string) (*ldap.Searc
return nil, ErrorLDAPSearch.ErrorParent(err)
}
liblog.DebugLevel.Logf("Search success on server '%s' with tls mode '%s', with filter [%s] and attribute %v", lc.config.ServerAddr(lc.tlsMode == TLSModeTLS), lc.tlsMode.String(), filter, attributes)
lc.getLogEntry(liblog.DebugLevel, "ldap search success").FieldAdd("ldap.filter", filter).FieldAdd("ldap.attributes", attributes).Log()
return src, nil
}
@@ -446,7 +480,7 @@ func (lc *HelperLDAP) UserInfoByField(username string, fieldOfUnicValue string)
userRes["DN"] = src.Entries[0].DN
}
liblog.DebugLevel.Logf("Map info retrieve in ldap server '%s' with tls mode '%s' about user [%s] : %v", lc.config.ServerAddr(lc.tlsMode == TLSModeTLS), lc.tlsMode.String(), username, userRes)
lc.getLogEntry(liblog.DebugLevel, "ldap user find success").FieldAdd("ldap.user", username).FieldAdd("ldap.map", userRes).Log()
return userRes, nil
}
@@ -479,7 +513,7 @@ func (lc *HelperLDAP) GroupInfoByField(groupname string, fieldForUnicValue strin
}
}
liblog.DebugLevel.Logf("Info for group [%s] find on server '%s' with tls mode '%s' : %v", groupname, lc.config.ServerAddr(lc.tlsMode == TLSModeTLS), lc.tlsMode.String(), grpInfo)
lc.getLogEntry(liblog.DebugLevel, "ldap group find success").FieldAdd("ldap.group", groupname).FieldAdd("ldap.map", grpInfo).Log()
return grpInfo, nil
}
@@ -504,13 +538,13 @@ func (lc *HelperLDAP) UserMemberOf(username string) ([]string, liberr.Error) {
for _, entry := range src.Entries {
for _, mmb := range entry.GetAttributeValues("memberOf") {
liblog.DebugLevel.Logf("Group find for uid '%s' on server '%s' with tls mode '%s' : %v", username, lc.config.ServerAddr(lc.tlsMode == TLSModeTLS), lc.tlsMode.String(), mmb)
lc.getLogEntry(liblog.DebugLevel, "ldap find user group list building").FieldAdd("ldap.user", username).FieldAdd("ldap.raw.groups", mmb).Log()
mmo := lc.ParseEntries(mmb)
grp = append(grp, mmo["cn"]...)
}
}
liblog.DebugLevel.Logf("Groups find for uid '%s' on server '%s' with tls mode '%s' : %v", username, lc.config.ServerAddr(lc.tlsMode == TLSModeTLS), lc.tlsMode.String(), grp)
lc.getLogEntry(liblog.DebugLevel, "ldap user group list success").FieldAdd("ldap.user", username).FieldAdd("ldap.grouplist", grp).Log()
return grp, nil
}
@@ -560,7 +594,7 @@ func (lc *HelperLDAP) UsersOfGroup(groupname string) ([]string, liberr.Error) {
}
}
liblog.DebugLevel.Logf("Member of groups [%s] find on server '%s' with tls mode '%s' : %v", groupname, lc.config.ServerAddr(lc.tlsMode == TLSModeTLS), lc.tlsMode.String(), grp)
lc.getLogEntry(liblog.DebugLevel, "ldap group user list success").FieldAdd("ldap.group", groupname).FieldAdd("ldap.userlist", grp).Log()
return grp, nil
}

281
logger/compat.go Normal file
View File

@@ -0,0 +1,281 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
)
//@deprecated: only for retro compatibility
var defaultLogger Logger
func init() {
defaultLogger = New()
defaultLogger.SetLevel(InfoLevel)
}
//GetDefault return the default logger
//@deprecated: create a logger and call GetLevel() like New().GetLevel()
func GetDefault() Logger {
return defaultLogger
}
//GetCurrentLevel return the current loglevel setting in the logger. All log entry matching this level or below will be logged.
//@deprecated: create a logger and call GetLevel() like New().GetLevel()
func GetCurrentLevel() Level {
return defaultLogger.GetLevel()
}
// SetLevel Change the Level of all log entry with the Level type given in parameter. The change is apply for next log entry only.
// If the given Level type is not matching a correct Level type, no change will be apply.
//@deprecated: create a logger and call GetLevel() like New().GetLevel...
func SetLevel(level Level) {
defaultLogger.SetLevel(level)
}
// AddGID Reconfigure the current logger to add or not the thread GID before each message.
//@deprecated: create a logger and update the options like New().SetOptions...
func AddGID(enable bool) {
opt := defaultLogger.GetOptions()
opt.DisableStack = !enable
_ = defaultLogger.SetOptions(context.TODO(), opt)
}
// Timestamp Reconfigure the current logger to add or not the timestamp before each message.
//@deprecated: create a logger and update the options like New().SetOptions...
func Timestamp(enable bool) {
opt := defaultLogger.GetOptions()
opt.DisableTimestamp = !enable
_ = defaultLogger.SetOptions(context.TODO(), opt)
}
// IsTimeStamp will return true if timestamp is added or not on log message
//@deprecated: create a logger and get the options like New().GetOptions...
func IsTimeStamp() bool {
return !defaultLogger.GetOptions().DisableTimestamp
}
// FileTrace Reconfigure the current logger to add or not the origin file/line of each message.
// This option is apply for all message except info message.
//@deprecated: create a logger and update the options like New().SetOptions...
func FileTrace(enable bool) {
opt := defaultLogger.GetOptions()
opt.EnableTrace = enable
_ = defaultLogger.SetOptions(context.TODO(), opt)
}
// IsFileTrace will return true if trace is added or not on log message
//@deprecated: create a logger and get the options like New().GetOptions...
func IsFileTrace() bool {
return defaultLogger.GetOptions().EnableTrace
}
// ModeColor will reconfigure the current logger to use or not color in messages format.
// This apply only for next message and only for TextFormat.
//@deprecated: create a logger and update the options like New().SetOptions...
func ModeColor(enable bool) {
opt := defaultLogger.GetOptions()
opt.DisableColor = !enable
_ = defaultLogger.SetOptions(context.TODO(), opt)
}
// IsModeColor will return true if color is configured on log message
//@deprecated: create a logger and get the options like New().GetOptions...
func IsModeColor() bool {
return !defaultLogger.GetOptions().DisableColor
}
// EnableColor Reconfigure the current logger to use color in messages format.
// This apply only for next message and only for TextFormat.
//@deprecated: create a logger and update the options like New().SetOptions...
func EnableColor() {
ModeColor(true)
}
// DisableColor Reconfigure the current logger to not use color in messages format.
// This apply only for next message and only for TextFormat.
//@deprecated: create a logger and update the options like New().SetOptions...
func DisableColor() {
ModeColor(false)
}
// EnableViperLog enable or not the Gin Logger configuration.
//@deprecated: create a logger and call function SetSPF13Level like New().SetSPF13Level...
func EnableViperLog(enable bool) {
defaultLogger.SetSPF13Level(defaultLogger.GetLevel(), nil)
}
// SetTracePathFilter customize the filter apply to filepath on trace.
//@deprecated: create a logger and update the options like New().SetOptions...
func SetTracePathFilter(path string) {
opt := defaultLogger.GetOptions()
opt.TraceFilter = path
_ = defaultLogger.SetOptions(context.TODO(), opt)
}
// Log Simple function to log directly the given message with the attached log Level.
/*
message a string message to be logged with the attached log Level
*/
//@deprecated: create a logger and call one of this function : New().Debug, New().Info, New().Warning, New().Error, New().Fatal, New().Panic, New().LogDetails or New().Entry
func (l Level) Log(message string) {
defaultLogger.LogDetails(l, message, nil, nil, nil)
}
// Logf Simple function to log (to the attached log Level) with a fmt function a given pattern and arguments in parameters.
/*
format a string pattern for fmt function
args a list of interface to match the references in the pattern
*/
//@deprecated: create a logger and call one of this function : New().Debug, New().Info, New().Warning, New().Error, New().Fatal, New().Panic, New().LogDetails or New().Entry
func (l Level) Logf(format string, args ...interface{}) {
defaultLogger.LogDetails(l, fmt.Sprintf(format, args...), nil, nil, nil)
}
// LogData Simple function to log directly the given message with given data with the attached log Level.
/*
message a string message to be logged with the attached log Level
data an interface of data to be logged with the message. (In Text format, the data will be json marshaled)
*/
//@deprecated: create a logger and call one of this function : New().Debug, New().Info, New().Warning, New().Error, New().Fatal, New().Panic, New().LogDetails or New().Entry
func (l Level) LogData(message string, data interface{}) {
defaultLogger.LogDetails(l, message, data, nil, nil)
}
// WithFields Simple function to log directly the given message with given fields with the attached log Level.
/*
message a string message to be logged with the attached log Level
fields a map of string key and interfaces value for a complete list of field ("field name" => value interface)
*/
//@deprecated: create a logger and call one of this function : New().Debug, New().Info, New().Warning, New().Error, New().Fatal, New().Panic, New().LogDetails or New().Entry
func (l Level) WithFields(message string, fields Fields) {
defaultLogger.LogDetails(l, message, nil, nil, fields)
}
// LogError Simple function to log directly the given error with the attached log Level.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- return true
// + when the err is nil, this function will :
// +--- return false
/*
err an error object message to be logged with the attached log Level
*/
//@deprecated: create a logger and call one of this function : New().CheckError or New().Entry.Check
func (l Level) LogError(err error) bool {
return defaultLogger.CheckError(l, NilLevel, "", nil, []error{err}, nil)
}
// LogErrorCtx Function to test, log and inform about the given error object.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
context a string for the context of the current test of the error
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
*/
//@deprecated: create a logger and call one of this function : New().CheckError or New().Entry.Check
func (l Level) LogErrorCtx(levelElse Level, context string, err error) bool {
return defaultLogger.Entry(l, context).ErrorAdd(err).Check(levelElse)
}
// LogErrorCtxf Function to test, log and inform about the given error object, but with a context based on a pattern and matching args.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
contextPattern a pattern string for the context of the current test of the error. This string will be used in a fmt function as pattern string
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
args a list of interface for the context of the current test of the error. This list of interface will be used in a fmt function as the matching args for the pattern string
*/
//@deprecated: create a logger and call one of this function : New().CheckError or New().Entry.Check
func (l Level) LogErrorCtxf(levelElse Level, contextPattern string, err error, args ...interface{}) bool {
return defaultLogger.Entry(l, contextPattern, args...).ErrorAdd(err).Check(levelElse)
}
// LogGinErrorCtxf Function to test, log and inform about the given error object, but with a context based on a couple of pattern and matching args.
// This function will also add an Gin Tonic Error if the c parameters is a valid GinTonic Context reference.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- if the Context Gin Tonic is valid, add the Error into this context
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
contextPattern a pattern string for the context of the current test of the error. This string will be used in a fmt function as pattern string
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
c a valid Go GinTonic Context reference to add current error to the Gin Tonic Error Context
args a list of interface for the context of the current test of the error. This list of interface will be used in a fmt function as the matching args for the pattern string
*/
//@deprecated: create a logger and call one of this function : New().CheckError or New().Entry.SetGinContext.Check
func (l Level) LogGinErrorCtxf(levelElse Level, contextPattern string, err error, c *gin.Context, args ...interface{}) bool {
return defaultLogger.Entry(l, contextPattern, args...).SetGinContext(c).ErrorAdd(err).Check(levelElse)
}
// LogGinErrorCtx Function to test, log and inform about the given error object.
// This function will also add an Gin Tonic Error if the c parameters is a valid GinTonic Context reference.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- if the Context Gin Tonic is valid, add the Error into this context
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
context a string for the context of the current test of the error
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
c a valid Go GinTonic Context reference to add current error to the Gin Tonic Error Context.
*/
//@deprecated: create a logger and call one of this function : New().CheckError or New().Entry.SetGinContext.Check
func (l Level) LogGinErrorCtx(levelElse Level, context string, err error, c *gin.Context) bool {
return defaultLogger.Entry(l, context).SetGinContext(c).ErrorAdd(err).Check(levelElse)
}

243
logger/entry.go Normal file
View File

@@ -0,0 +1,243 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"time"
"github.com/gin-gonic/gin"
liberr "github.com/nabbar/golib/errors"
"github.com/sirupsen/logrus"
)
const (
FieldTime = "time"
FieldLevel = "level"
FieldStack = "stack"
FieldCaller = "caller"
FieldFile = "file"
FieldLine = "line"
FieldMessage = "message"
FieldError = "error"
FieldData = "data"
)
type Entry struct {
log func() *logrus.Logger
gin *gin.Context
//Time is the time of the event (can be empty time if disabled timestamp)
Time time.Time `json:"time"`
//Level define the level of the entry (cannot be empty or nil)
Level Level `json:"level"`
//Stack define the process goroutine number (can be 0 if disabled)
Stack uint64 `json:"stack"`
//Caller define the function caller of the entry (can be empty if trace disabled, not found or anonymous function)
Caller string `json:"caller"`
//File define the file function caller of the entry (can be empty if trace disabled, not found or anonymous function)
File string `json:"file"`
//Caller define the line in file caller of the entry (can be 0 if trace disabled, not found or anonymous function)
Line uint32 `json:"line"`
//Message define the main message of the entry (can be empty)
Message string `json:"message"`
//Error define a slice of error interface (can be nil, or a silce with one or more nil values)
Error []error `json:"error"`
//Data is a unknown type data to add to logger (can be nil)
Data interface{} `json:"data"`
//Fields are a list of custom information to add to log entry (can be nil or can overwrite Entry values)
Fields Fields `json:"fields"`
}
//SetGinContext allow to register a gin context pointer to register the errors of the current entry intro gin Context Error Slice.
func (e *Entry) SetGinContext(ctx *gin.Context) *Entry {
e.gin = ctx
return e
}
//FieldAdd allow to add one couple key/val as type string/interface into the custom field of the entry.
func (e *Entry) FieldAdd(key string, val interface{}) *Entry {
e.Fields.Add(key, val)
return e
}
//FieldMerge allow to merge a Field pointer into the custom field of the entry.
func (e *Entry) FieldMerge(fields Fields) *Entry {
e.Fields.Merge(fields)
return e
}
//FieldSet allow to change the custom field of the entry with the given Fields in parameter.
func (e *Entry) FieldSet(fields Fields) *Entry {
e.Fields = fields
return e
}
func (e *Entry) FieldClean(keys ...string) *Entry {
e.Fields.Clean(keys...)
return e
}
func (e *Entry) DataSet(data interface{}) *Entry {
e.Data = data
return e
}
func (e *Entry) ErrorClean() *Entry {
e.Error = make([]error, 0)
return e
}
func (e *Entry) ErrorSet(err []error) *Entry {
e.Error = err
return e
}
func (e *Entry) ErrorAdd(err ...error) *Entry {
for _, er := range err {
e.Error = append(e.Error, er)
}
return e
}
func (e *Entry) ErrorAddLib(err ...liberr.Error) *Entry {
for _, er := range err {
e.ErrorAdd(er.GetErrorSlice()...)
}
return e
}
func (e *Entry) Check(lvlNoErr Level) bool {
var found = false
if len(e.Error) > 0 {
for _, er := range e.Error {
if er == nil {
continue
}
found = true
break
}
}
if !found {
e.Level = lvlNoErr
}
e.Log()
return found
}
func (e *Entry) Log() {
var (
ent *logrus.Entry
tag = NewFields().Add(FieldLevel, e.Level.String())
log *logrus.Logger
)
if !e.Time.IsZero() {
tag.Add(FieldTime, e.Time.Format(time.RFC3339Nano))
}
if e.Stack > 0 {
tag.Add(FieldStack, e.Stack)
}
if e.Caller != "" {
tag.Add(FieldCaller, e.Caller)
} else if e.File != "" {
tag.Add(FieldFile, e.File)
}
if e.Line > 0 {
tag.Add(FieldLine, e.Line)
}
if e.Message != "" {
tag.Add(FieldMessage, e.Message)
}
if len(e.Error) > 0 {
tag.Add(FieldError, e.Error)
}
if e.Data != nil {
tag.Add(FieldData, e.Data)
}
if len(e.Fields) > 0 {
tag.Merge(e.Fields)
}
if e.log == nil {
return
} else if log = e.log(); log == nil {
return
} else {
ent = log.WithFields(tag.Logrus())
}
//nolint exhaustive
switch e.Level {
case NilLevel:
return
case DebugLevel:
ent.Debugln()
case InfoLevel:
ent.Infoln()
case WarnLevel:
ent.Warnln()
case ErrorLevel:
ent.Errorln()
case FatalLevel:
ent.Fatalln()
case PanicLevel:
ent.Panicln()
}
if e.gin != nil && len(e.Error) > 0 {
for _, err := range e.Error {
_ = e.gin.Error(err)
}
}
}

107
logger/fields.go Normal file
View File

@@ -0,0 +1,107 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import "github.com/sirupsen/logrus"
type Fields map[string]interface{}
func NewFields() Fields {
return make(Fields)
}
func (f Fields) new() map[string]interface{} {
return make(map[string]interface{}, 0)
}
func (f Fields) clone() map[string]interface{} {
if len(f) > 0 {
return f
}
return f.new()
}
func (f Fields) Add(key string, val interface{}) Fields {
res := f.clone()
res[key] = val
return res
}
func (f Fields) Map(fct func(key string, val interface{}) interface{}) Fields {
res := f.clone()
for k, v := range res {
if v = fct(k, v); v != nil {
res[k] = v
}
}
return res
}
func (f Fields) Merge(other Fields) Fields {
if len(other) < 1 {
return f
} else if len(f) < 1 {
return other
}
res := f.clone()
other.Map(func(key string, val interface{}) interface{} {
res[key] = val
return nil
})
return res
}
func (f Fields) Clean(keys ...string) Fields {
res := f.new()
if len(keys) > 0 {
f.Map(func(key string, val interface{}) interface{} {
for _, kk := range keys {
if kk == key {
return nil
}
}
res[key] = val
return nil
})
}
return res
}
func (f Fields) Logrus() logrus.Fields {
return f.clone()
}

View File

@@ -1,140 +0,0 @@
/*
MIT License
Copyright (c) 2019 Nicolas JUHEL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package logger
import (
"io"
"strings"
"github.com/sirupsen/logrus"
)
// Format a uint8 type customized with function to manage the result logger format.
type Format uint8
const (
nilFormat Format = iota
// TextFormat a text format for logger entry.
TextFormat
// JsonFormat a json format for logger entry.
JsonFormat
)
var (
curFormat = nilFormat
)
func init() {
updateFormatter(TextFormat)
}
func SetOutput(out io.WriteCloser) {
logrus.SetOutput(out)
}
func updateFormatter(newFormat Format) {
switch newFormat {
case curFormat:
return
case TextFormat:
curFormat = TextFormat
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: modeColor,
DisableColors: !modeColor,
DisableLevelTruncation: !modeColor,
DisableTimestamp: true,
DisableSorting: true,
})
case JsonFormat:
curFormat = JsonFormat
logrus.SetFormatter(&logrus.JSONFormatter{
DisableTimestamp: true,
})
case nilFormat:
return
}
}
// GetFormatListString return the full list (slice of string) of all available formats.
func GetFormatListString() []string {
return []string{
strings.ToLower(TextFormat.String()),
strings.ToLower(JsonFormat.String()),
}
}
// SetFormat Change the format of all log entry with the Format type given in parameter. The change is apply for next entry only.
// If the given Format type is not matching a correct Format type, no change will be apply.
/*
fmt a Format type for the format to use
*/
func SetFormat(fmt Format) {
switch fmt {
case TextFormat, JsonFormat:
updateFormatter(fmt)
case curFormat, nilFormat:
return
}
}
// GetCurrentFormat Return the current Format Type used for all log entry.
func GetCurrentFormat() Format {
return curFormat
}
// GetFormatString return a valid Format Type matching the given string parameter.
/*
format the string representation of a Format type
*/
func GetFormatString(format string) Format {
switch strings.ToLower(format) {
case strings.ToLower(TextFormat.String()):
return TextFormat
case strings.ToLower(JsonFormat.String()):
return JsonFormat
}
return GetFormatString(TextFormat.String())
}
// String Return the string name of the Format Type.
func (f Format) String() string {
switch f {
case JsonFormat:
return "Json"
case TextFormat:
return "Text"
case nilFormat, curFormat:
return ""
}
return TextFormat.String()
}

181
logger/hclog.go Normal file
View File

@@ -0,0 +1,181 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"io"
"log"
"github.com/hashicorp/go-hclog"
)
const (
HCLogArgs = "hclog.args"
HCLogName = "hclog.name"
)
type _hclog struct {
l Logger
}
func (l *_hclog) Log(level hclog.Level, msg string, args ...interface{}) {
switch level {
case hclog.Off, hclog.NoLevel:
return
case hclog.Trace:
l.l.Debug(msg, nil, args...)
case hclog.Debug:
l.l.Debug(msg, nil, args...)
case hclog.Info:
l.l.Info(msg, nil, args...)
case hclog.Warn:
l.l.Warning(msg, nil, args...)
case hclog.Error:
l.l.Error(msg, nil, args...)
}
}
func (l *_hclog) Trace(msg string, args ...interface{}) {
l.l.Debug(msg, nil, args...)
}
func (l *_hclog) Debug(msg string, args ...interface{}) {
l.l.Debug(msg, nil, args...)
}
func (l *_hclog) Info(msg string, args ...interface{}) {
l.l.Info(msg, nil, args...)
}
func (l *_hclog) Warn(msg string, args ...interface{}) {
l.l.Warning(msg, nil, args...)
}
func (l *_hclog) Error(msg string, args ...interface{}) {
l.l.Error(msg, nil, args...)
}
func (l *_hclog) IsTrace() bool {
return l.l.GetOptions().EnableTrace
}
func (l *_hclog) IsDebug() bool {
return l.l.GetLevel() >= DebugLevel
}
func (l *_hclog) IsInfo() bool {
return l.l.GetLevel() >= InfoLevel
}
func (l *_hclog) IsWarn() bool {
return l.l.GetLevel() >= WarnLevel
}
func (l *_hclog) IsError() bool {
return l.l.GetLevel() >= ErrorLevel
}
func (l *_hclog) ImpliedArgs() []interface{} {
fields := l.l.GetFields()
if a, ok := fields[HCLogArgs]; !ok {
return make([]interface{}, 0)
} else if s, ok := a.([]interface{}); ok {
return s
}
return make([]interface{}, 0)
}
func (l *_hclog) With(args ...interface{}) hclog.Logger {
l.l.SetFields(l.l.GetFields().Add(HCLogArgs, args))
return l
}
func (l *_hclog) Name() string {
fields := l.l.GetFields()
if a, ok := fields[HCLogName]; !ok {
return ""
} else if s, ok := a.(string); ok {
return s
}
return ""
}
func (l *_hclog) Named(name string) hclog.Logger {
l.l.SetFields(l.l.GetFields().Add(HCLogName, name))
return l
}
func (l *_hclog) ResetNamed(name string) hclog.Logger {
l.l.SetFields(l.l.GetFields().Add(HCLogName, name))
return l
}
func (l *_hclog) SetLevel(level hclog.Level) {
switch level {
case hclog.Off, hclog.NoLevel:
l.l.SetLevel(NilLevel)
case hclog.Trace:
l.l.SetLevel(DebugLevel)
case hclog.Debug:
l.l.SetLevel(DebugLevel)
case hclog.Info:
l.l.SetLevel(InfoLevel)
case hclog.Warn:
l.l.SetLevel(WarnLevel)
case hclog.Error:
l.l.SetLevel(ErrorLevel)
}
}
func (l *_hclog) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
var lvl Level
switch opts.ForceLevel {
case hclog.Off, hclog.NoLevel:
lvl = NilLevel
case hclog.Trace:
lvl = DebugLevel
case hclog.Debug:
lvl = DebugLevel
case hclog.Info:
lvl = InfoLevel
case hclog.Warn:
lvl = WarnLevel
case hclog.Error:
lvl = ErrorLevel
}
return l.l.GetStdLogger(lvl, 0)
}
func (l *_hclog) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
return l.l
}

165
logger/hookfile.go Normal file
View File

@@ -0,0 +1,165 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/ioutils"
"github.com/sirupsen/logrus"
)
type HookFile interface {
logrus.Hook
io.WriteCloser
RegisterHook(log *logrus.Logger)
}
type _HookFile struct {
f *os.File
l []logrus.Level
s bool
d bool
t bool
}
func NewHookFile(opt OptionsFile) (HookFile, error) {
if opt.Filepath == "" {
return nil, fmt.Errorf("missing file path")
}
var (
flags = os.O_WRONLY | os.O_APPEND
LVLs = make([]logrus.Level, 0)
hdl *os.File
err error
)
if opt.FileMode == 0 {
opt.FileMode = 0644
}
if opt.PathMode == 0 {
opt.PathMode = 0755
}
if opt.CreatePath {
if err = ioutils.PathCheckCreate(true, opt.Filepath, opt.FileMode, opt.PathMode); err != nil {
return nil, err
}
} else if opt.Create {
flags = os.O_CREATE | flags
}
if hdl, err = os.OpenFile(opt.Filepath, flags, opt.FileMode); err != nil {
return nil, err
}
if len(opt.LogLevel) > 0 {
for _, ls := range opt.LogLevel {
LVLs = append(LVLs, GetLevelString(ls).Logrus())
}
} else {
LVLs = logrus.AllLevels
}
return &_HookFile{
f: hdl,
l: LVLs,
s: opt.DisableStack,
d: opt.DisableTimestamp,
t: opt.EnableTrace,
}, nil
}
func (o *_HookFile) RegisterHook(log *logrus.Logger) {
log.AddHook(o)
}
func (o *_HookFile) Levels() []logrus.Level {
return o.l
}
func (o *_HookFile) Fire(entry *logrus.Entry) error {
ent := entry.Dup()
if o.s {
ent.Data = o.filterKey(ent.Data, FieldStack)
}
if o.d {
ent.Data = o.filterKey(ent.Data, FieldTime)
}
if !o.t {
ent.Data = o.filterKey(ent.Data, FieldCaller)
ent.Data = o.filterKey(ent.Data, FieldFile)
ent.Data = o.filterKey(ent.Data, FieldLine)
}
if p, err := ent.Bytes(); err != nil {
return err
} else if _, err = o.Write(p); err != nil {
return err
}
return nil
}
func (o *_HookFile) Write(p []byte) (n int, err error) {
if o.f == nil {
return 0, fmt.Errorf("logrus.hookfile: file not setup")
}
return o.f.Write(p)
}
func (o *_HookFile) Close() error {
err := o.f.Close()
o.f = nil
return err
}
func (o *_HookFile) filterKey(f logrus.Fields, key string) logrus.Fields {
if len(f) < 1 {
return f
}
var res = make(map[string]interface{}, 0)
for k, v := range f {
if k == key {
continue
}
res[k] = v
}
return res
}

127
logger/hookstandard.go Normal file
View File

@@ -0,0 +1,127 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"fmt"
"io"
"github.com/sirupsen/logrus"
)
type HookStandard interface {
logrus.Hook
io.WriteCloser
RegisterHook(log *logrus.Logger)
}
type _HookStd struct {
w io.Writer
l []logrus.Level
s bool
d bool
t bool
c bool
}
func NewHookStandard(opt Options, w io.Writer, lvls []logrus.Level) HookFile {
if len(lvls) < 1 {
lvls = logrus.AllLevels
}
return &_HookStd{
w: w,
l: lvls,
s: opt.DisableStack,
d: opt.DisableTimestamp,
t: opt.EnableTrace,
}
}
func (o *_HookStd) RegisterHook(log *logrus.Logger) {
log.AddHook(o)
}
func (o *_HookStd) Levels() []logrus.Level {
return o.l
}
func (o *_HookStd) Fire(entry *logrus.Entry) error {
ent := entry.Dup()
if o.s {
ent.Data = o.filterKey(ent.Data, FieldStack)
}
if o.d {
ent.Data = o.filterKey(ent.Data, FieldTime)
}
if !o.t {
ent.Data = o.filterKey(ent.Data, FieldCaller)
ent.Data = o.filterKey(ent.Data, FieldFile)
ent.Data = o.filterKey(ent.Data, FieldLine)
}
if p, err := ent.Bytes(); err != nil {
return err
} else if _, err = o.Write(p); err != nil {
return err
}
return nil
}
func (o *_HookStd) Write(p []byte) (n int, err error) {
if o.w == nil {
return 0, fmt.Errorf("logrus.hookstd: writer not setup")
}
return o.w.Write(p)
}
func (o *_HookStd) Close() error {
return nil
}
func (o *_HookStd) filterKey(f logrus.Fields, key string) logrus.Fields {
if len(f) < 1 {
return f
}
var res = make(map[string]interface{}, 0)
for k, v := range f {
if k == key {
continue
}
res[k] = v
}
return res
}

143
logger/hooksyslog.go Normal file
View File

@@ -0,0 +1,143 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"fmt"
"io"
"log/syslog"
"github.com/sirupsen/logrus"
)
type HookSyslog interface {
logrus.Hook
io.WriteCloser
RegisterHook(log *logrus.Logger)
}
type _HookSyslog struct {
w *syslog.Writer
l []logrus.Level
s bool
d bool
t bool
}
func NewHookSyslog(opt OptionsSyslog) (HookSyslog, error) {
var (
LVLs = make([]logrus.Level, 0)
sys *syslog.Writer
err error
)
if len(opt.LogLevel) > 0 {
for _, ls := range opt.LogLevel {
LVLs = append(LVLs, GetLevelString(ls).Logrus())
}
} else {
LVLs = logrus.AllLevels
}
if sys, err = syslog.Dial(opt.Network.String(), opt.Host, opt.Priority, opt.Tag); err != nil {
return nil, err
}
return &_HookSyslog{
w: sys,
l: LVLs,
s: opt.DisableStack,
d: opt.DisableTimestamp,
t: opt.EnableTrace,
}, nil
}
func (o *_HookSyslog) RegisterHook(log *logrus.Logger) {
log.AddHook(o)
}
func (o *_HookSyslog) Levels() []logrus.Level {
return o.l
}
func (o *_HookSyslog) Fire(entry *logrus.Entry) error {
ent := entry.Dup()
if o.s {
ent.Data = o.filterKey(ent.Data, FieldStack)
}
if o.d {
ent.Data = o.filterKey(ent.Data, FieldTime)
}
if !o.t {
ent.Data = o.filterKey(ent.Data, FieldCaller)
ent.Data = o.filterKey(ent.Data, FieldFile)
ent.Data = o.filterKey(ent.Data, FieldLine)
}
if p, err := ent.Bytes(); err != nil {
return err
} else if _, err = o.Write(p); err != nil {
return err
}
return nil
}
func (o *_HookSyslog) Write(p []byte) (n int, err error) {
if o.w == nil {
return 0, fmt.Errorf("logrus.hooksyslog: connection not setup")
}
return o.w.Write(p)
}
func (o *_HookSyslog) Close() error {
err := o.w.Close()
o.w = nil
return err
}
func (o *_HookSyslog) filterKey(f logrus.Fields, key string) logrus.Fields {
if len(f) < 1 {
return f
}
var res = make(map[string]interface{}, 0)
for k, v := range f {
if k == key {
continue
}
res[k] = v
}
return res
}

134
logger/interface.go Normal file
View File

@@ -0,0 +1,134 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"context"
"io"
"log"
"sync"
"sync/atomic"
"github.com/hashicorp/go-hclog"
jww "github.com/spf13/jwalterweatherman"
)
type Logger interface {
io.WriteCloser
//SetLevel allow to change the minimal level of log message
SetLevel(lvl Level)
//GetLevel return the minimal level of log message
GetLevel() Level
//SetIOWriterLevel allow to change the minimal level of log message for io.WriterCloser interface
SetIOWriterLevel(lvl Level)
//GetIOWriterLevel return the minimal level of log message for io.WriterCloser interface
GetIOWriterLevel() Level
//SetOptions allow to set or update the options for the logger
SetOptions(ctx context.Context, opt *Options) error
//GetOptions return the options for the logger
GetOptions() *Options
//SetFields allow to set or update the default fields for all logger entry
// Fields are custom information added into log message
SetFields(field Fields)
//GetFields return the default fields for all logger entry
// Fields are custom information added into log message
GetFields() Fields
//Clone allow to duplicate the logger with a copy of the logger
Clone(ctx context.Context) (Logger, error)
//SetSPF13Level allow to plus spf13 logger (jww) to this logger
SetSPF13Level(lvl Level, log *jww.Notepad)
//GetStdLogger return a golang log.logger instance linked with this main logger.
GetStdLogger(lvl Level, logFlags int) *log.Logger
//SetStdLogger force the default golang log.logger instance linked with this main logger.
SetStdLogger(lvl Level, logFlags int)
//SetHashicorpHCLog force mapping default Hshicorp logger hclog to current logger
SetHashicorpHCLog()
//NewHashicorpHCLog return a new Hshicorp logger hclog mapped current logger
NewHashicorpHCLog() hclog.Logger
//Debug add an entry with DebugLevel to the logger
Debug(message string, data interface{}, args ...interface{})
//Info add an entry with InfoLevel to the logger
Info(message string, data interface{}, args ...interface{})
//Warning add an entry with WarnLevel to the logger
Warning(message string, data interface{}, args ...interface{})
//Error add an entry with ErrorLevel level to the logger
Error(message string, data interface{}, args ...interface{})
//Fatal add an entry with FatalLevel to the logger
//The function will break the process (os.exit) after log entry.
Fatal(message string, data interface{}, args ...interface{})
//Panic add an entry with PanicLevel level to the logger
//The function will break the process (os.exit) after log entry.
Panic(message string, data interface{}, args ...interface{})
//LogDetails add an entry to the logger
LogDetails(lvl Level, message string, data interface{}, err []error, fields Fields, args ...interface{})
//CheckError will check if a not nil error is given add if yes, will add an entry to the logger.
// Othwise if the lvlOK is given (and not NilLevel) the function will add entry and said ok
CheckError(lvlKO, lvlOK Level, message string, data interface{}, err []error, fields Fields, args ...interface{}) bool
//Entry will return an entry struct to manage it (set gin context, add fields, log the entry...)
Entry(lvl Level, message string, args ...interface{}) *Entry
}
//New return a new logger interface pointer
func New() Logger {
lvl := new(atomic.Value)
lvl.Store(InfoLevel)
return &logger{
m: sync.Mutex{},
l: lvl,
o: new(atomic.Value),
s: new(atomic.Value),
f: new(atomic.Value),
w: new(atomic.Value),
c: new(atomic.Value),
}
}

160
logger/interface_test.go Normal file
View File

@@ -0,0 +1,160 @@
/*
* MIT License
*
* Copyright (c) 2020 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package logger_test
import (
"github.com/nabbar/golib/logger"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Logger", func() {
Context("Create New Logger with Default Config", func() {
It("Must succeed", func() {
log := logger.New()
log.SetLevel(logger.DebugLevel)
err := log.SetOptions(GetContext(), &logger.Options{})
Expect(err).ToNot(HaveOccurred())
log.LogDetails(logger.InfoLevel, "test logger", nil, nil, nil)
})
})
Context("Create New Logger with Default Config and trace", func() {
It("Must succeed", func() {
log := logger.New()
log.SetLevel(logger.DebugLevel)
err := log.SetOptions(GetContext(), &logger.Options{
EnableTrace: true,
})
Expect(err).ToNot(HaveOccurred())
log.LogDetails(logger.InfoLevel, "test logger with trace", nil, nil, nil)
})
})
Context("Create New Logger with field", func() {
It("Must succeed", func() {
log := logger.New()
log.SetLevel(logger.DebugLevel)
err := log.SetOptions(GetContext(), &logger.Options{
EnableTrace: true,
})
log.SetFields(logger.NewFields().Add("test-field", "ok"))
Expect(err).ToNot(HaveOccurred())
log.LogDetails(logger.InfoLevel, "test logger with field", nil, nil, nil)
})
})
Context("Create New Logger with file", func() {
It("Must succeed", func() {
log := logger.New()
log.SetLevel(logger.DebugLevel)
fsp, err := GetTempFile()
defer func() {
err = DelTempFile(fsp)
Expect(err).ToNot(HaveOccurred())
}()
Expect(err).ToNot(HaveOccurred())
err = log.SetOptions(GetContext(), &logger.Options{
EnableTrace: true,
LogFile: []logger.OptionsFile{
{
LogLevel: nil,
Filepath: fsp,
Create: true,
CreatePath: true,
FileMode: 0644,
PathMode: 0755,
DisableStack: false,
DisableTimestamp: false,
EnableTrace: true,
},
},
})
Expect(err).ToNot(HaveOccurred())
log.SetFields(logger.NewFields().Add("test-field", "ok"))
log.LogDetails(logger.InfoLevel, "test logger with field", nil, nil, nil)
})
})
Context("Create New Logger with file in multithread mode", func() {
It("Must succeed", func() {
log := logger.New()
log.SetLevel(logger.DebugLevel)
fsp, err := GetTempFile()
defer func() {
err = DelTempFile(fsp)
Expect(err).ToNot(HaveOccurred())
}()
Expect(err).ToNot(HaveOccurred())
err = log.SetOptions(GetContext(), &logger.Options{
EnableTrace: true,
LogFile: []logger.OptionsFile{
{
LogLevel: nil,
Filepath: fsp,
Create: true,
CreatePath: true,
FileMode: 0644,
PathMode: 0755,
DisableStack: false,
DisableTimestamp: false,
EnableTrace: true,
},
},
})
Expect(err).ToNot(HaveOccurred())
log.SetFields(logger.NewFields().Add("test-field", "ok"))
log.LogDetails(logger.InfoLevel, "test logger with field", nil, nil, nil)
var sub logger.Logger
sub, err = log.Clone(GetContext())
Expect(err).ToNot(HaveOccurred())
go func(log logger.Logger) {
defer func() {
se := log.Close()
Expect(se).ToNot(HaveOccurred())
}()
log.SetFields(logger.NewFields().Add("logger", "sub"))
for i := 0; i < 10; i++ {
log.Entry(logger.InfoLevel, "test multithreading logger").FieldAdd("id", i).Log()
}
}(sub)
log.SetFields(logger.NewFields().Add("logger", "main"))
for i := 0; i < 10; i++ {
log.Entry(logger.InfoLevel, "test multithreading logger").FieldAdd("id", i).Log()
}
})
})
})

88
logger/iowritecloser.go Normal file
View File

@@ -0,0 +1,88 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import "sync/atomic"
func (l *logger) Close() error {
for _, c := range l.closeGetMutex() {
if c != nil {
_ = c.Close()
}
}
if l.n != nil {
l.n()
}
l.closeClean()
return nil
}
func (l *logger) Write(p []byte) (n int, err error) {
l.newEntry(l.GetIOWriterLevel(), string(p), nil, l.GetFields(), nil).Log()
return len(p), nil
}
func (l *logger) SetIOWriterLevel(lvl Level) {
if l == nil {
return
}
l.m.Lock()
defer l.m.Unlock()
if l.w == nil {
l.w = new(atomic.Value)
}
l.w.Store(lvl)
}
func (l *logger) GetIOWriterLevel() Level {
if l == nil {
return NilLevel
}
l.m.Lock()
defer l.m.Unlock()
if l.w == nil {
l.w = new(atomic.Value)
}
if i := l.w.Load(); i == nil {
return NilLevel
} else if o, ok := i.(Level); ok {
return o
}
return NilLevel
}

View File

@@ -1,61 +0,0 @@
/*
MIT License
Copyright (c) 2019 Nicolas JUHEL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package logger
import (
"fmt"
"io"
)
// IOWriter is struct redirected all entry to the current logger.
type IOWriter struct {
lvl Level
prf string
}
// GetIOWriter return a io.Writer instance to Write on logger with a specified log level.
/*
level specify the log level to use to redirect all entry to current logger
msgPrefixPattern is a string pattern to prefix all entry
msgPrefixArgs is a list of args to apply on the msgPrefixPattern pattern to prefix all entry
*/
func GetIOWriter(level Level, msgPrefixPattern string, msgPrefixArgs ...interface{}) io.Writer {
return &IOWriter{
lvl: level,
prf: fmt.Sprintf(msgPrefixPattern, msgPrefixArgs...),
}
}
// Write implement the Write function of the io.Writer interface and redirect all entry to current logger.
// The return n will always return the len on the p parameter and err will always be nil.
/*
p the entry to be redirect to current logger
*/
func (iow IOWriter) Write(p []byte) (n int, err error) {
n = len(p)
err = nil
iow.lvl.Log(iow.prf + " " + string(p))
return
}

View File

@@ -25,15 +25,10 @@ SOFTWARE.
package logger
import (
"encoding/json"
"fmt"
"math"
"strings"
"sync/atomic"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/spf13/jwalterweatherman"
)
//Level a uint8 type customized with function to log message with the current log level.
@@ -56,14 +51,6 @@ const (
NilLevel
)
var (
curLevel *atomic.Value
)
func init() {
SetLevel(InfoLevel)
}
// GetLevelListString return a list ([]string) of all string loglevel available.
func GetLevelListString() []string {
return []string{
@@ -76,114 +63,12 @@ func GetLevelListString() []string {
}
}
//GetCurrentLevel return the current loglevel setting in the logger. All log entry matching this level or below will be logged.
func GetCurrentLevel() Level {
if curLevel == nil {
curLevel = new(atomic.Value)
}
if i := curLevel.Load(); i == nil {
return NilLevel
} else if l, ok := i.(Level); !ok {
return NilLevel
} else {
return l
}
}
func setCurLevel(lvl Level) {
if curLevel == nil {
curLevel = new(atomic.Value)
}
curLevel.Store(lvl)
}
// SetLevel Change the Level of all log entry with the Level type given in parameter. The change is apply for next log entry only.
// If the given Level type is not matching a correct Level type, no change will be apply.
/*
level a Level type to use to specify the new level of logger message
*/
func SetLevel(level Level) {
//nolint exhaustive
switch level {
case PanicLevel:
setCurLevel(PanicLevel)
logrus.SetLevel(logrus.PanicLevel)
case FatalLevel:
setCurLevel(FatalLevel)
logrus.SetLevel(logrus.FatalLevel)
case ErrorLevel:
setCurLevel(ErrorLevel)
logrus.SetLevel(logrus.ErrorLevel)
case WarnLevel:
setCurLevel(WarnLevel)
logrus.SetLevel(logrus.WarnLevel)
case InfoLevel:
setCurLevel(InfoLevel)
logrus.SetLevel(logrus.InfoLevel)
case DebugLevel:
setCurLevel(DebugLevel)
logrus.SetLevel(logrus.DebugLevel)
case NilLevel:
setCurLevel(NilLevel)
return
}
DebugLevel.Logf("Change Log Level to %s", logrus.GetLevel().String())
setViperLogTrace()
}
func setViperLogTrace() {
if !enableVPR {
return
}
jwalterweatherman.SetLogOutput(GetIOWriter(GetCurrentLevel(), "[Log Config Viper]"))
jwalterweatherman.SetStdoutOutput(GetIOWriter(GetCurrentLevel(), "[Std Config Viper]"))
if filetrace {
jwalterweatherman.SetStdoutThreshold(jwalterweatherman.LevelTrace)
return
}
//nolint exhaustive
switch GetCurrentLevel() {
case PanicLevel:
jwalterweatherman.SetStdoutThreshold(jwalterweatherman.LevelCritical)
case FatalLevel:
jwalterweatherman.SetStdoutThreshold(jwalterweatherman.LevelFatal)
case ErrorLevel:
jwalterweatherman.SetStdoutThreshold(jwalterweatherman.LevelError)
case WarnLevel:
jwalterweatherman.SetStdoutThreshold(jwalterweatherman.LevelWarn)
case InfoLevel:
jwalterweatherman.SetStdoutThreshold(jwalterweatherman.LevelInfo)
case DebugLevel:
jwalterweatherman.SetStdoutThreshold(jwalterweatherman.LevelDebug)
case NilLevel:
return
}
}
// GetLevelString return a valid Level Type matching the given string parameter. If the given parameter don't represent a valid level, the InfoLevel will be return.
/*
level the string representation of a Level type
*/
func GetLevelString(level string) Level {
switch strings.ToLower(level) {
func GetLevelString(l string) Level {
switch strings.ToLower(l) {
case strings.ToLower(PanicLevel.String()):
return PanicLevel
@@ -207,14 +92,14 @@ func GetLevelString(level string) Level {
}
// Uint8 Convert the current Level type to a uint8 value. E.g. FatalLevel becomes 1.
func (level Level) Uint8() uint8 {
return uint8(level)
func (l Level) Uint8() uint8 {
return uint8(l)
}
// String Convert the current Level type to a string. E.g. PanicLevel becomes "Critical Error".
func (level Level) String() string {
func (l Level) String() string {
//nolint exhaustive
switch level {
switch l {
case DebugLevel:
return "Debug"
case InfoLevel:
@@ -234,244 +119,21 @@ func (level Level) String() string {
return "unknown"
}
// Log Simple function to log directly the given message with the attached log Level.
/*
message a string message to be logged with the attached log Level
*/
func (level Level) Log(message string) {
level.logDetails(message, nil, nil, nil)
}
// Logf Simple function to log (to the attached log Level) with a fmt function a given pattern and arguments in parameters.
/*
format a string pattern for fmt function
args a list of interface to match the references in the pattern
*/
func (level Level) Logf(format string, args ...interface{}) {
level.logDetails(fmt.Sprintf(format, args...), nil, nil, nil)
}
// LogData Simple function to log directly the given message with given data with the attached log Level.
/*
message a string message to be logged with the attached log Level
data an interface of data to be logged with the message. (In Text format, the data will be json marshaled)
*/
func (level Level) LogData(message string, data interface{}) {
level.logDetails(message, data, nil, nil)
}
// WithFields Simple function to log directly the given message with given fields with the attached log Level.
/*
message a string message to be logged with the attached log Level
fields a map of string key and interfaces value for a complete list of field ("field name" => value interface)
*/
func (level Level) WithFields(message string, fields map[string]interface{}) {
level.logDetails(message, nil, nil, fields)
}
// LogError Simple function to log directly the given error with the attached log Level.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- return true
// + when the err is nil, this function will :
// +--- return false
/*
err an error object message to be logged with the attached log Level
*/
func (level Level) LogError(err error) bool {
return level.LogGinErrorCtx(NilLevel, "", err, nil)
}
// LogErrorCtx Function to test, log and inform about the given error object.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
context a string for the context of the current test of the error
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
*/
func (level Level) LogErrorCtx(levelElse Level, context string, err error) bool {
return level.LogGinErrorCtx(levelElse, context, err, nil)
}
// LogErrorCtxf Function to test, log and inform about the given error object, but with a context based on a pattern and matching args.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
contextPattern a pattern string for the context of the current test of the error. This string will be used in a fmt function as pattern string
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
args a list of interface for the context of the current test of the error. This list of interface will be used in a fmt function as the matching args for the pattern string
*/
func (level Level) LogErrorCtxf(levelElse Level, contextPattern string, err error, args ...interface{}) bool {
return level.LogGinErrorCtx(levelElse, fmt.Sprintf(contextPattern, args...), err, nil)
}
// LogGinErrorCtxf Function to test, log and inform about the given error object, but with a context based on a couple of pattern and matching args.
// This function will also add an Gin Tonic Error if the c parameters is a valid GinTonic Context reference.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- if the Context Gin Tonic is valid, add the Error into this context
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
contextPattern a pattern string for the context of the current test of the error. This string will be used in a fmt function as pattern string
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
c a valid Go GinTonic Context reference to add current error to the Gin Tonic Error Context
args a list of interface for the context of the current test of the error. This list of interface will be used in a fmt function as the matching args for the pattern string
*/
func (level Level) LogGinErrorCtxf(levelElse Level, contextPattern string, err error, c *gin.Context, args ...interface{}) bool {
return level.LogGinErrorCtx(levelElse, fmt.Sprintf(contextPattern, args...), err, c)
}
// LogGinErrorCtx Function to test, log and inform about the given error object.
// This function will also add an Gin Tonic Error if the c parameters is a valid GinTonic Context reference.
//
// How iot works :
// + when the err is a valid error, this function will :
// +--- log the Error with the attached log Level
// +--- if the Context Gin Tonic is valid, add the Error into this context
// +--- return true
// + when the err is nil, this function will :
// +--- use the levelElse if valid to inform with context there is no error found
// +--- return false
/*
levelElse level used if the err is nil before returning a False result
context a string for the context of the current test of the error
err a error object to be log with the attached log level before return true, if the err is nil, the levelElse is used to log there are no error and return false
c a valid Go GinTonic Context reference to add current error to the Gin Tonic Error Context.
*/
func (level Level) LogGinErrorCtx(levelElse Level, context string, err error, c *gin.Context) bool {
if err != nil {
level.logDetails(fmt.Sprintf("KO : %s", context), nil, err, nil)
ginTonicAddError(c, err)
return true
} else if proceed(levelElse) {
levelElse.logDetails(fmt.Sprintf("OK : %s", context), nil, err, nil)
}
return false
}
func (level Level) logDetails(message string, data interface{}, err error, fields logrus.Fields) {
if !proceed(level) {
return
}
var tags = make(map[string]interface{})
if enableGID {
tags[tagStack] = getGID()
}
if timestamp {
tags[tagTime] = time.Now().Format(time.RFC3339Nano)
}
tags[tagTime] = level.String()
if filetrace && GetCurrentLevel() == DebugLevel {
frame := getFrame()
tags[tagCaller] = frame.Function
tags[tagFile] = filterPath(frame.File)
tags[tagLine] = frame.Line
}
tags[tagMsg] = message
tags[tagErr] = err
tags[tagData] = data
var (
ent = logrus.NewEntry(logrus.StandardLogger())
msg string
)
if fields != nil && len(fields) > 0 {
ent.WithFields(fields)
}
//nolint exhaustive
switch curFormat {
case TextFormat:
if _, ok := tags[tagStack]; ok {
msg += fmt.Sprintf("[%d] ", tags[tagStack])
}
if _, ok := tags[tagCaller]; ok {
msg += fmt.Sprintf("[%s] ", tags[tagCaller])
}
var line string
if _, ok := tags[tagLine]; ok {
line = fmt.Sprintf("(%d) ", tags[tagLine])
}
if _, ok := tags[tagFile]; ok || len(line) > 0 {
msg += fmt.Sprintf("[%s%s] ", line, tags[tagFile])
}
msg += fmt.Sprintf("%s", tags[tagMsg])
if tags[tagErr] != nil {
msg += fmt.Sprintf(" -- err : %v", err)
}
if tags[tagData] != nil {
if str, err := json.MarshalIndent(data, "", " "); err == nil {
msg += fmt.Sprintf(" -- data : \n%s", string(str))
} else {
msg += fmt.Sprintf(" -- data : %v", err)
}
}
case JsonFormat:
ent.WithFields(tags)
msg = tags[tagMsg].(string)
case nilFormat:
return
}
//nolint exhaustive
switch level {
case NilLevel:
return
func (l Level) Logrus() logrus.Level {
switch l {
case DebugLevel:
ent.Debugln(msg)
return logrus.DebugLevel
case InfoLevel:
ent.Infoln(msg)
return logrus.InfoLevel
case WarnLevel:
ent.Warnln(msg)
return logrus.WarnLevel
case ErrorLevel:
ent.Errorln(msg)
return logrus.ErrorLevel
case FatalLevel:
ent.Fatalln(msg)
return logrus.FatalLevel
case PanicLevel:
ent.Panicln(msg)
return logrus.PanicLevel
default:
return math.MaxInt32
}
}

119
logger/log.go Normal file
View File

@@ -0,0 +1,119 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"fmt"
"time"
)
func (l logger) Debug(message string, data interface{}, args ...interface{}) {
l.newEntry(DebugLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
func (l logger) Info(message string, data interface{}, args ...interface{}) {
l.newEntry(InfoLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
func (l logger) Warning(message string, data interface{}, args ...interface{}) {
l.newEntry(WarnLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
func (l logger) Error(message string, data interface{}, args ...interface{}) {
l.newEntry(ErrorLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
func (l logger) Fatal(message string, data interface{}, args ...interface{}) {
l.newEntry(FatalLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
func (l logger) Panic(message string, data interface{}, args ...interface{}) {
l.newEntry(PanicLevel, fmt.Sprintf(message, args...), nil, nil, data).Log()
}
func (l logger) LogDetails(lvl Level, message string, data interface{}, err []error, fields Fields, args ...interface{}) {
l.newEntry(lvl, fmt.Sprintf(message, args...), err, fields, data).Log()
}
func (l logger) CheckError(lvlKO, lvlOK Level, message string, data interface{}, err []error, fields Fields, args ...interface{}) bool {
ent := l.newEntry(lvlKO, fmt.Sprintf(message, args...), err, fields, data)
return ent.Check(lvlOK)
}
func (l logger) Entry(lvl Level, message string, args ...interface{}) *Entry {
return l.newEntry(lvl, fmt.Sprintf(message, args...), nil, nil, nil)
}
func (l logger) newEntry(lvl Level, message string, err []error, fields Fields, data interface{}) *Entry {
opt := l.GetOptions()
cLv := l.GetLevel()
if cLv == NilLevel || lvl > cLv {
return &Entry{}
}
var ent = &Entry{
log: l.getLog,
Time: time.Time{},
Level: lvl,
Stack: 0,
Caller: "",
File: "",
Line: 0,
Message: message,
Error: err,
Data: data,
Fields: NewFields().Merge(l.GetFields()).Merge(fields),
}
if !opt.DisableTimestamp {
ent.Time = time.Now()
}
if !opt.DisableStack {
ent.Stack = l.getStack()
}
if opt.EnableTrace {
frm := l.getCaller()
if frm.Function != "" {
ent.Caller = frm.Function
}
if frm.File != "" {
ent.File = l.filterPath(frm.File)
}
if frm.Line > 0 {
ent.Line = uint32(frm.Line)
}
}
return ent
}

View File

@@ -1,223 +1,106 @@
/*
MIT License
Copyright (c) 2019 Nicolas JUHEL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"bytes"
"io"
"log"
"path"
"reflect"
"runtime"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/hashicorp/go-hclog"
jww "github.com/spf13/jwalterweatherman"
)
const (
tagStack = "stack"
tagTime = "time"
tagCaller = "func"
tagFile = "file"
tagLine = "line"
tagMsg = "message"
tagErr = "error"
tagData = "data"
)
var (
currPkgs = path.Base(reflect.TypeOf(IOWriter{}).PkgPath())
filterPkg = path.Clean(reflect.TypeOf(IOWriter{}).PkgPath())
modeColor = true
timestamp = true
filetrace = false
enableGID = false
enableVPR = true
)
func init() {
if i := strings.LastIndex(filterPkg, "/vendor/"); i != -1 {
filterPkg = filterPkg[:i+1]
}
func (l *logger) GetStdLogger(lvl Level, logFlags int) *log.Logger {
l.SetIOWriterLevel(lvl)
return log.New(l, "", logFlags)
}
// GetLogger return a golang log.logger instance linked with this main logger.
// This function is useful to keep the format, mode, color, output... same as current config.
// - msgPrefixPattern a pattern prefix to identify or comment all message passed throw this log.logger instance.
// - msgPrefixArgs a list of interface to apply on pattern with a fmt function.
func GetLogger(lvl Level, logFlags int, msgPrefixPattern string, msgPrefixArgs ...interface{}) *log.Logger {
return log.New(GetIOWriter(lvl, msgPrefixPattern, msgPrefixArgs...), "", logFlags)
}
// SetStdLogger force the default golang log.logger instance linked with this main logger.
// This function is useful to keep the format, mode, color, output... same as current config.
// - msgPrefixPattern a pattern prefix to identify or comment all message passed throw this log.logger instance.
// - msgPrefixArgs a list of interface to apply on pattern with a fmt function.
func SetStdLogger(lvl Level, logFlags int, msgPrefixPattern string, msgPrefixArgs ...interface{}) {
log.SetOutput(GetIOWriter(lvl, msgPrefixPattern, msgPrefixArgs...))
func (l *logger) SetStdLogger(lvl Level, logFlags int) {
l.SetIOWriterLevel(lvl)
log.SetOutput(l)
log.SetPrefix("")
log.SetFlags(logFlags)
}
// AddGID Reconfigure the current logger to add or not the thread GID before each message.
func AddGID(enable bool) {
enableGID = enable
}
// Timestamp Reconfigure the current logger to add or not the timestamp before each message.
func Timestamp(enable bool) {
timestamp = enable
}
// IsTimeStamp will return true if timestamp is added or not on log message
func IsTimeStamp() bool {
return timestamp
}
// FileTrace Reconfigure the current logger to add or not the origin file/line of each message.
// This option is apply for all message except info message.
func FileTrace(enable bool) {
filetrace = enable
setViperLogTrace()
}
// IsFileTrace will return true if trace is added or not on log message
func IsFileTrace() bool {
return filetrace
}
// ModeColor will reconfigure the current logger to use or not color in messages format.
// This apply only for next message and only for TextFormat.
func ModeColor(enable bool) {
modeColor = enable
updateFormatter(nilFormat)
}
// IsModeColor will return true if color is configured on log message
func IsModeColor() bool {
return modeColor
}
// EnableColor Reconfigure the current logger to use color in messages format.
// This apply only for next message and only for TextFormat.
// @deprecated use ModeColor(true)
func EnableColor() {
ModeColor(true)
}
// DisableColor Reconfigure the current logger to not use color in messages format.
// This apply only for next message and only for TextFormat.
// @deprecated use Color(false)
func DisableColor() {
ModeColor(false)
}
// EnableViperLog enable or not the Gin Logger configuration.
func EnableViperLog(enable bool) {
enableVPR = enable
setViperLogTrace()
}
// SetTracePathFilter customize the filter apply to filepath on trace.
func SetTracePathFilter(path string) {
filterPkg = path
}
func getFrame() runtime.Frame {
// Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need.
programCounters := make([]uintptr, 10, 255)
n := runtime.Callers(1, programCounters)
if n > 0 {
frames := runtime.CallersFrames(programCounters[:n])
more := true
for more {
func (l *logger) SetSPF13Level(lvl Level, log *jww.Notepad) {
var (
frame runtime.Frame
fOutLog func(handle io.Writer)
fLvl func(threshold jww.Threshold)
)
frame, more = frames.Next()
if strings.Contains(frame.Function, currPkgs) {
continue
if log == nil {
jww.SetStdoutOutput(io.Discard)
fOutLog = jww.SetLogOutput
fLvl = jww.SetLogThreshold
} else {
fOutLog = log.SetLogOutput
fLvl = log.SetLogThreshold
}
return frame
}
switch lvl {
case NilLevel:
fOutLog(io.Discard)
fLvl(jww.LevelCritical)
case DebugLevel:
fOutLog(l)
if opt := l.GetOptions(); opt.EnableTrace {
fLvl(jww.LevelTrace)
} else {
fLvl(jww.LevelDebug)
}
return runtime.Frame{Function: "unknown", File: "unknown", Line: 0}
}
func getGID() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
//nolint #nosec
/* #nosec */
n, _ := strconv.ParseUint(string(b), 10, 64)
return n
}
func ginTonicAddError(c *gin.Context, err error) {
if c != nil && err != nil {
_ = c.Error(err)
case InfoLevel:
fOutLog(l)
fLvl(jww.LevelInfo)
case WarnLevel:
fOutLog(l)
fLvl(jww.LevelWarn)
case ErrorLevel:
fOutLog(l)
fLvl(jww.LevelError)
case FatalLevel:
fOutLog(l)
fLvl(jww.LevelFatal)
case PanicLevel:
fOutLog(l)
fLvl(jww.LevelCritical)
}
}
func proceed(lvl Level) bool {
return lvl != NilLevel && lvl <= GetCurrentLevel()
func (l *logger) SetHashicorpHCLog() {
hclog.SetDefault(&_hclog{
l: l,
})
}
func filterPath(pathname string) string {
var (
filterMod = "/pkg/mod/"
filterVendor = "/vendor/"
)
if i := strings.LastIndex(pathname, filterMod); i != -1 {
i = i + len(filterMod)
pathname = pathname[i:]
func (l *logger) NewHashicorpHCLog() hclog.Logger {
return &_hclog{
l: l,
}
if i := strings.LastIndex(pathname, filterPkg); i != -1 {
i = i + len(filterPkg)
pathname = pathname[i:]
}
if i := strings.LastIndex(pathname, filterVendor); i != -1 {
i = i + len(filterVendor)
pathname = pathname[i:]
}
pathname = path.Clean(pathname)
return strings.Trim(pathname, "/")
}

View File

@@ -0,0 +1,84 @@
/*
* MIT License
*
* Copyright (c) 2020 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package logger_test
import (
"context"
"os"
"testing"
"github.com/nabbar/golib/ioutils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var (
ctx context.Context
cnl context.CancelFunc
)
/*
Using https://onsi.github.io/ginkgo/
Running with $> ginkgo -cover .
*/
func TestGolibAwsHelper(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Logger Helper Suite")
}
var _ = BeforeSuite(func() {
ctx, cnl = context.WithCancel(context.Background())
})
var _ = AfterSuite(func() {
cnl()
})
func GetContext() context.Context {
return ctx
}
func GetTempFile() (string, error) {
hdf, err := ioutils.NewFileProgressTemp()
if err != nil {
return "", err
}
defer func() {
_ = hdf.Close()
}()
return hdf.FilePath(), nil
}
func DelTempFile(filepath string) error {
if _, err := os.Stat(filepath); err != nil {
return err
}
return os.RemoveAll(filepath)
}

440
logger/model.go Normal file
View File

@@ -0,0 +1,440 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"bytes"
"context"
"io"
"io/ioutil"
"os"
"path"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/sirupsen/logrus"
)
const (
_TraceFilterMod = "/pkg/mod/"
_TraceFilterVendor = "/vendor/"
)
var _selfPackage = path.Base(reflect.TypeOf(logger{}).PkgPath())
type logger struct {
x context.Context
n context.CancelFunc
m sync.Mutex
l *atomic.Value //current level set for this logger
o *atomic.Value //options
s *atomic.Value //logrus logger
f *atomic.Value //defaults fields
w *atomic.Value //io writer level
c *atomic.Value
}
func (l *logger) defaultFormatter(opt *Options) *logrus.TextFormatter {
if opt == nil {
opt = &Options{}
}
return &logrus.TextFormatter{
ForceColors: false,
DisableColors: opt.DisableColor,
ForceQuote: false,
DisableQuote: false,
EnvironmentOverrideColors: false,
DisableTimestamp: true,
FullTimestamp: false,
TimestampFormat: time.RFC3339,
DisableSorting: false,
SortingFunc: nil,
DisableLevelTruncation: false,
PadLevelText: true,
QuoteEmptyFields: true,
FieldMap: nil,
CallerPrettyfier: nil,
}
}
func (l *logger) closeAdd(clo io.Closer) {
lst := append(l.closeGet(), clo)
if l.c == nil {
l.c = new(atomic.Value)
}
l.c.Store(lst)
}
func (l *logger) closeClean() {
if l == nil {
return
}
l.m.Lock()
defer l.m.Unlock()
if l.c == nil {
l.c = new(atomic.Value)
}
l.c.Store(make([]io.Closer, 0))
}
func (l *logger) closeGet() []io.Closer {
res := make([]io.Closer, 0)
if l == nil {
return res
}
if l.c == nil {
l.c = new(atomic.Value)
}
if i := l.c.Load(); i == nil {
return res
} else if o, ok := i.([]io.Closer); ok {
return o
}
return res
}
func (l *logger) closeGetMutex() []io.Closer {
l.m.Lock()
defer l.m.Unlock()
return l.closeGet()
}
func (l *logger) Clone(ctx context.Context) (Logger, error) {
c := &logger{
x: nil,
n: nil,
m: sync.Mutex{},
l: new(atomic.Value),
o: new(atomic.Value),
s: new(atomic.Value),
f: new(atomic.Value),
w: new(atomic.Value),
c: new(atomic.Value),
}
c.SetLevel(l.GetLevel())
if err := c.SetOptions(ctx, l.GetOptions()); err != nil {
return nil, err
}
return c, nil
}
func (l *logger) setLoggerMutex(lvl Level) {
if l == nil {
return
}
l.m.Lock()
defer l.m.Unlock()
if l.l == nil {
l.l = new(atomic.Value)
}
l.l.Store(lvl)
}
func (l *logger) SetLevel(lvl Level) {
l.setLoggerMutex(lvl)
if opt := l.GetOptions(); opt.change != nil {
opt.change(l)
}
}
func (l *logger) GetLevel() Level {
if l == nil {
return NilLevel
}
l.m.Lock()
defer l.m.Unlock()
if l.l == nil {
l.l = new(atomic.Value)
}
if i := l.l.Load(); i == nil {
return NilLevel
} else if o, ok := i.(Level); ok {
return o
}
return NilLevel
}
func (l *logger) SetFields(field Fields) {
if l == nil {
return
}
l.m.Lock()
defer l.m.Unlock()
if l.f == nil {
l.f = new(atomic.Value)
}
l.f.Store(field)
}
func (l *logger) GetFields() Fields {
if l == nil {
return NewFields()
}
l.m.Lock()
defer l.m.Unlock()
if l.f == nil {
l.f = new(atomic.Value)
}
if i := l.f.Load(); i == nil {
return NewFields()
} else if o, ok := i.(Fields); ok {
return o
}
return NewFields()
}
func (l *logger) setOptionsMutex(ctx context.Context, opt *Options) error {
l.setOptions(opt)
opt = l.GetOptions()
_ = l.Close()
l.x, l.n = context.WithCancel(ctx)
l.m.Lock()
defer l.m.Unlock()
obj := logrus.New()
obj.SetFormatter(l.defaultFormatter(opt))
obj.SetOutput(ioutil.Discard) // Send all logs to nowhere by default
if !opt.DisableStandard {
obj.AddHook(NewHookStandard(*opt, os.Stdout, []logrus.Level{
logrus.InfoLevel,
logrus.DebugLevel,
logrus.TraceLevel,
}))
obj.AddHook(NewHookStandard(*opt, os.Stderr, []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
}))
}
if len(opt.LogFile) > 0 {
for _, fopt := range opt.LogFile {
if hook, err := NewHookFile(fopt); err != nil {
return err
} else {
l.closeAdd(hook)
hook.RegisterHook(obj)
}
}
}
if len(opt.LogSyslog) > 0 {
for _, lopt := range opt.LogSyslog {
if hook, err := NewHookSyslog(lopt); err != nil {
return err
} else {
l.closeAdd(hook)
hook.RegisterHook(obj)
}
}
}
l.s = new(atomic.Value)
l.s.Store(obj)
go func() {
select {
case <-l.x.Done():
_ = l.Close()
return
}
}()
return nil
}
func (l *logger) SetOptions(ctx context.Context, opt *Options) error {
if err := l.setOptionsMutex(ctx, opt); err != nil {
return err
}
if opt.init != nil {
opt.init(l)
}
return nil
}
func (l *logger) GetOptions() *Options {
if l == nil {
return &Options{}
}
l.m.Lock()
defer l.m.Unlock()
if l.o == nil {
l.o = new(atomic.Value)
}
if i := l.o.Load(); i == nil {
return &Options{}
} else if o, ok := i.(*Options); ok {
return o
}
return &Options{}
}
func (l *logger) setOptions(opt *Options) {
if l == nil {
return
}
l.m.Lock()
defer l.m.Unlock()
if l.o == nil {
l.o = new(atomic.Value)
}
l.o.Store(opt)
}
func (l *logger) getLog() *logrus.Logger {
if l == nil {
return nil
}
l.m.Lock()
defer l.m.Unlock()
if l.s == nil {
l.s = new(atomic.Value)
}
if i := l.s.Load(); i == nil {
return nil
} else if o, ok := i.(*logrus.Logger); !ok {
return nil
} else {
return o
}
}
func (l *logger) getStack() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
//nolint #nosec
/* #nosec */
n, _ := strconv.ParseUint(string(b), 10, 64)
return n
}
func (l *logger) getCaller() runtime.Frame {
// Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need.
programCounters := make([]uintptr, 10, 255)
n := runtime.Callers(1, programCounters)
if n > 0 {
frames := runtime.CallersFrames(programCounters[:n])
more := true
for more {
var frame runtime.Frame
frame, more = frames.Next()
if strings.Contains(frame.Function, _selfPackage) {
continue
}
return frame
}
}
return runtime.Frame{Function: "unknown", File: "unknown", Line: 0}
}
func (l *logger) filterPath(pathname string) string {
var ()
if i := strings.LastIndex(pathname, _TraceFilterMod); i != -1 {
i = i + len(_TraceFilterMod)
pathname = pathname[i:]
}
if i := strings.LastIndex(pathname, _TraceFilterVendor); i != -1 {
i = i + len(_TraceFilterVendor)
pathname = pathname[i:]
}
opt := l.GetOptions()
if opt.TraceFilter != "" {
if i := strings.LastIndex(pathname, opt.TraceFilter); i != -1 {
i = i + len(opt.TraceFilter)
pathname = pathname[i:]
}
}
return strings.Trim(path.Clean(pathname), "/")
}

153
logger/options.go Normal file
View File

@@ -0,0 +1,153 @@
/***********************************************************************************************************************
*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
**********************************************************************************************************************/
package logger
import (
"log/syslog"
"os"
)
type FuncCustomConfig func(log Logger)
type NetworkType uint8
const (
NetworkEmpty NetworkType = iota
NetworkTCP
NetworkUDP
)
func (n NetworkType) String() string {
switch n {
case NetworkTCP:
return "tcp"
case NetworkUDP:
return "udp"
default:
return ""
}
}
type OptionsFile struct {
// LogLevel define the allowed level of log for this file.
LogLevel []string
// Filepath define the file path for log to file.
Filepath string
// Create define if the log file must exist or can create it.
Create bool
// CreatePath define if the path of the log file must exist or can try to create it.
CreatePath bool
// FileMode define mode to be used for the log file if the create it.
FileMode os.FileMode
// PathMode define mode to be used for the path of the log file if create it.
PathMode os.FileMode
// DisableStack allow to disable the goroutine id before each message.
DisableStack bool
// DisableTimestamp allow to disable the timestamp before each message.
DisableTimestamp bool
// EnableTrace allow to add the origin caller/file/line of each message.
EnableTrace bool
}
type OptionsSyslog struct {
// LogLevel define the allowed level of log for this syslog.
LogLevel []string
// Network define the network used to connect to this syslog.
Network NetworkType
// Host define the remote syslog to use.
// If Host and Network are empty, local syslog will be used.
Host string
// Priority define the priority used for this syslog.
Priority syslog.Priority
// Tag define the syslog tag used for log message.
Tag string
// DisableStack allow to disable the goroutine id before each message.
DisableStack bool
// DisableTimestamp allow to disable the timestamp before each message.
DisableTimestamp bool
// EnableTrace allow to add the origin caller/file/line of each message.
EnableTrace bool
}
type Options struct {
// DisableStandard allow to disable writing log to standard output stdout/stderr.
DisableStandard bool
// DisableStack allow to disable the goroutine id before each message.
DisableStack bool
// DisableTimestamp allow to disable the timestamp before each message.
DisableTimestamp bool
// EnableTrace allow to add the origin caller/file/line of each message.
EnableTrace bool
// TraceFilter define the path to clean for trace.
TraceFilter string
// DisableColor define if color could be use or not in messages format.
// If the running process is not a tty, no color will be used.
DisableColor bool
// LogFile define a list of log file configuration to allow log to files.
LogFile []OptionsFile
// LogSyslog define a list of syslog configuration to allow log to syslog.
LogSyslog []OptionsSyslog
// custom function handler.
init FuncCustomConfig
change FuncCustomConfig
}
// RegisterFuncUpdateLogger allow to register a function called when init or update of logger.
// To clean function, just call RegisterFuncUpdateLogger with nil as param.
func (o Options) RegisterFuncUpdateLogger(fct FuncCustomConfig) {
o.init = fct
}
// RegisterFuncUpdateLevel allow to register a function called when init or update level
// To clean function, just call RegisterFuncUpdateLevel with nil as param.
func (o Options) RegisterFuncUpdateLevel(fct FuncCustomConfig) {
o.change = fct
}

View File

@@ -76,17 +76,22 @@ var (
)
func init() {
liblog.EnableColor()
liblog.SetLevel(liblog.DebugLevel)
liblog.AddGID(true)
liblog.FileTrace(true)
liblog.SetFormat(liblog.TextFormat)
liblog.Timestamp(true)
liberr.SetModeReturnError(liberr.ErrorReturnCodeErrorTraceFull)
ctx, cnl = context.WithCancel(context.Background())
liblog.SetLevel(liblog.DebugLevel)
if err := liblog.GetDefault().SetOptions(ctx, &liblog.Options{
DisableStandard: false,
DisableStack: false,
DisableTimestamp: false,
EnableTrace: true,
TraceFilter: "",
DisableColor: false,
}); err != nil {
panic(err)
}
cfgPool = libsrv.PoolServerConfig{cfgSrv01, cfgSrv02, cfgSrv03}
cfgPool.MapUpdate(func(cfg libsrv.ServerConfig) libsrv.ServerConfig {
cfg.SetParentContext(func() context.Context {

View File

@@ -52,14 +52,21 @@ var (
)
func init() {
liblog.EnableColor()
liblog.AddGID(true)
liblog.FileTrace(true)
liblog.SetFormat(liblog.TextFormat)
liblog.SetLevel(liblog.DebugLevel)
liberr.SetModeReturnError(liberr.ErrorReturnCodeErrorTraceFull)
ctx, cnl = context.WithCancel(context.TODO())
liblog.SetLevel(liblog.DebugLevel)
if err := liblog.GetDefault().SetOptions(ctx, &liblog.Options{
DisableStandard: false,
DisableStack: false,
DisableTimestamp: false,
EnableTrace: true,
TraceFilter: "",
DisableColor: false,
}); err != nil {
panic(err)
}
}
func main() {

View File

@@ -30,7 +30,6 @@ package main
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"runtime"
@@ -69,38 +68,11 @@ var (
func init() {
liberr.SetModeReturnError(liberr.ErrorReturnCodeErrorTraceFull)
liblog.SetLevel(liblog.InfoLevel)
liblog.AddGID(true)
liblog.EnableColor()
liblog.FileTrace(true)
liblog.Timestamp(true)
}
type EmptyStruct struct{}
func main() {
if _, err := os.Stat(LoggerFile); err != nil && !errors.Is(err, os.ErrNotExist) {
panic(err)
} else if err == nil {
if err = os.Remove(LoggerFile); err != nil {
panic(err)
}
}
//nolint #gosec
/* #nosec */
if file, err := os.OpenFile(LoggerFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666); err != nil {
panic(err)
} else {
liblog.SetOutput(file)
defer func() {
if file != nil {
liblog.SetOutput(os.Stdout)
_ = file.Close()
}
}()
}
ctx, cnl := context.WithCancel(context.Background())
defer func() {
if cnl != nil {
@@ -108,6 +80,44 @@ func main() {
}
}()
log := liblog.New()
log.SetLevel(liblog.InfoLevel)
if err := log.SetOptions(ctx, &liblog.Options{
DisableStandard: true,
DisableStack: false,
DisableTimestamp: false,
EnableTrace: false,
TraceFilter: "",
DisableColor: false,
LogFile: []liblog.OptionsFile{
{
LogLevel: []string{
"panic",
"fatal",
"error",
"warning",
"info",
"debug",
},
Filepath: LoggerFile,
Create: true,
CreatePath: true,
FileMode: 0644,
PathMode: 0755,
DisableStack: false,
DisableTimestamp: false,
EnableTrace: true,
},
},
}); err != nil {
panic(err)
}
err := liblog.GetDefault().SetOptions(ctx, log.GetOptions())
if err != nil {
panic(err)
}
println(fmt.Sprintf("Running test with %d threads...", runtime.GOMAXPROCS(0)))
println(fmt.Sprintf("Init cluster..."))
tStart := time.Now()