snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
44
server/pkg/logger/context.go
Normal file
44
server/pkg/logger/context.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
ctxLogKey struct{}
|
||||
)
|
||||
|
||||
// ContextWithValue allows us to pack custom logger to context and pass that to the
|
||||
func ContextWithValue(ctx context.Context, logger *zap.Logger) context.Context {
|
||||
return context.WithValue(ctx, ctxLogKey{}, logger)
|
||||
}
|
||||
|
||||
// ContextValue retrieves logger from given context or falls back to
|
||||
// any of the logger passed to it. If no loggers are found it uses default logger from pkg/logger
|
||||
func ContextValue(ctx context.Context, fallbacks ...*zap.Logger) *zap.Logger {
|
||||
if ctx != nil {
|
||||
if ctxLogger := ctx.Value(ctxLogKey{}); ctxLogger != nil {
|
||||
// This will panic if we somehow manage to set
|
||||
return ctxLogger.(*zap.Logger)
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range fallbacks {
|
||||
if l != nil {
|
||||
return l
|
||||
}
|
||||
}
|
||||
|
||||
return Default()
|
||||
}
|
||||
|
||||
// AddRequestID sets requestID field from context to logger and returns it
|
||||
func AddRequestID(ctx context.Context, logger *zap.Logger) *zap.Logger {
|
||||
if reqID := middleware.GetReqID(ctx); reqID != "" {
|
||||
logger = logger.With(zap.String("requestID", reqID))
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
16
server/pkg/logger/handlers.go
Normal file
16
server/pkg/logger/handlers.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// LogParamError for loggin invalid params
|
||||
func LogParamError(name string, r *http.Request, err error) {
|
||||
ContextValue(r.Context()).Debug(
|
||||
"invalid params for REST controller "+name,
|
||||
zap.Error(err),
|
||||
zap.String("controller", name),
|
||||
)
|
||||
}
|
||||
185
server/pkg/logger/logger.go
Normal file
185
server/pkg/logger/logger.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"github.com/spf13/cast"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"moul.io/zapfilter"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLogger = zap.NewNop()
|
||||
)
|
||||
|
||||
func Default() *zap.Logger {
|
||||
if defaultLogger == nil {
|
||||
return zap.NewNop()
|
||||
}
|
||||
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
func SetDefault(logger *zap.Logger) {
|
||||
defaultLogger = logger
|
||||
}
|
||||
|
||||
// Init (re)initializes global logger according to the settings
|
||||
//
|
||||
// It also peaks into http-server options to determinate if log events
|
||||
// should be buffered for use from web console
|
||||
func Init() {
|
||||
var (
|
||||
// @todo this should probably be refactored by adding a new option to LogOpt
|
||||
// that controls if we create a buffered output as well; and when not explicitly
|
||||
// set, we take state of web-console as a base
|
||||
hSrvOpt = options.HttpServer()
|
||||
logger = Must(Make(options.Log()))
|
||||
)
|
||||
|
||||
if hSrvOpt.WebConsoleEnabled {
|
||||
// web console is the only thing right now
|
||||
// that needs logger to buffer events for later access
|
||||
logger = withDebugBuffer(logger)
|
||||
}
|
||||
|
||||
defaultLogger = logger
|
||||
}
|
||||
|
||||
// Make creates a logger (debug or production) according to options
|
||||
func Make(opt *options.LogOpt) (logger *zap.Logger, err error) {
|
||||
if opt.Debug {
|
||||
// Do we want to enable debug logger
|
||||
// with a bit more dev-friendly output
|
||||
logger, err = Debug(opt)
|
||||
} else {
|
||||
logger, err = Production(opt)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger = withFilter(logger, opt.Filter)
|
||||
logger = withStacktraceLevel(logger, opt.StacktraceLevel)
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
// MakeDebugLogger creates a logger
|
||||
func MakeDebugLogger() *zap.Logger {
|
||||
var (
|
||||
logger = Must(Debug(options.Log()))
|
||||
)
|
||||
|
||||
logger = withStacktraceLevel(logger, "dpanic")
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// Must is a utility function that panics if given log maker returns an error
|
||||
func Must(logger *zap.Logger, err error) *zap.Logger {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to configure logger: %w", err))
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// Debug prepares debug logger using options
|
||||
func Debug(opt *options.LogOpt) (*zap.Logger, error) {
|
||||
var (
|
||||
// make a copy of debug options so that we do not
|
||||
dbgOpt = &options.LogOpt{
|
||||
Debug: true,
|
||||
Level: "debug",
|
||||
Filter: opt.Filter,
|
||||
IncludeCaller: opt.IncludeCaller,
|
||||
StacktraceLevel: opt.StacktraceLevel,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
conf = applyOptionsToConfig(zap.NewDevelopmentConfig(), dbgOpt)
|
||||
)
|
||||
|
||||
// Print log level in colors
|
||||
conf.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
|
||||
// Shorten timestamp, we do not care about the date
|
||||
conf.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(t.Format("15:04:05.000"))
|
||||
}
|
||||
|
||||
return conf.Build()
|
||||
}
|
||||
|
||||
func Production(opt *options.LogOpt) (*zap.Logger, error) {
|
||||
var (
|
||||
conf = applyOptionsToConfig(zap.NewProductionConfig(), opt)
|
||||
)
|
||||
|
||||
return conf.Build()
|
||||
}
|
||||
|
||||
// Uint64 properly encodes values to be represented in the web console
|
||||
func Uint64(k string, id uint64) zap.Field {
|
||||
return zap.String(k, strconv.FormatUint(id, 10))
|
||||
}
|
||||
|
||||
// Uint64s properly encodes values to be represented in the web console
|
||||
func Uint64s(k string, id []uint64) zap.Field {
|
||||
return zap.Strings(k, cast.ToStringSlice(id))
|
||||
}
|
||||
|
||||
// Applies options from environment variables
|
||||
func applyOptionsToConfig(conf zap.Config, opt *options.LogOpt) zap.Config {
|
||||
// LOG_LEVEL
|
||||
conf.Level = zap.NewAtomicLevelAt(mustParseLevel(opt.Level))
|
||||
|
||||
// LOG_INCLUDE_CALLER
|
||||
conf.DisableCaller = !opt.IncludeCaller
|
||||
|
||||
conf.Sampling = nil
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
// Applies filtering options
|
||||
//
|
||||
// This is controlled with LOG_FILTER environmental var
|
||||
func withFilter(l *zap.Logger, filter string) *zap.Logger {
|
||||
if len(filter) > 0 {
|
||||
l = zap.New(zapfilter.NewFilteringCore(l.Core(), zapfilter.MustParseRules(filter)))
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// Applies stacktrace level options
|
||||
//
|
||||
// This is controlled with LOG_STACKTRACE_LEVEL environmental var
|
||||
func withStacktraceLevel(l *zap.Logger, level string) *zap.Logger {
|
||||
return l.WithOptions(zap.AddStacktrace(mustParseLevel(level)))
|
||||
}
|
||||
|
||||
// Adds Tee logger that copies all log messages to debug buffer
|
||||
func withDebugBuffer(in *zap.Logger) *zap.Logger {
|
||||
return zap.New(zapcore.NewTee(
|
||||
in.Core(),
|
||||
|
||||
DebugBufferedLogger(debugLogRR),
|
||||
))
|
||||
}
|
||||
|
||||
func mustParseLevel(l string) (o zapcore.Level) {
|
||||
if err := o.Set(l); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
39
server/pkg/logger/masked.go
Normal file
39
server/pkg/logger/masked.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Mask is a passthrough for MaskIf
|
||||
func Mask(key string, val interface{}) zap.Field {
|
||||
return MaskIf(key, val, true)
|
||||
}
|
||||
|
||||
// MaskIf conditionally masks (replaces string with * of equal length input string/stringer
|
||||
func MaskIf(key string, val interface{}, mask bool) zap.Field {
|
||||
var str string
|
||||
|
||||
switch v := val.(type) {
|
||||
case fmt.Stringer:
|
||||
str = v.String()
|
||||
case string:
|
||||
str = v
|
||||
default:
|
||||
// empty string for other types
|
||||
str = ""
|
||||
}
|
||||
|
||||
if mask {
|
||||
str = strings.Repeat("*", len(str))
|
||||
}
|
||||
|
||||
return zap.Field{
|
||||
Key: key,
|
||||
Type: zapcore.StringType,
|
||||
String: str,
|
||||
}
|
||||
}
|
||||
154
server/pkg/logger/rrbuf.go
Normal file
154
server/pkg/logger/rrbuf.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type (
|
||||
rrBufEntry struct {
|
||||
num int
|
||||
rec []byte
|
||||
}
|
||||
|
||||
// simple round-robin struct that holds our buffer with log entries
|
||||
rr struct {
|
||||
mux sync.RWMutex
|
||||
num int
|
||||
buf []*rrBufEntry
|
||||
}
|
||||
|
||||
debugBufferingLogger struct {
|
||||
zapcore.LevelEnabler
|
||||
out *rr
|
||||
enc zapcore.Encoder
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// allowing 10k entries (no limiting the entry size)
|
||||
debugLogCap = 10240
|
||||
)
|
||||
|
||||
var (
|
||||
// initialize debug logger round robin db
|
||||
debugLogRR = &rr{
|
||||
buf: make([]*rrBufEntry, 0),
|
||||
}
|
||||
)
|
||||
|
||||
// WriteLogBuffer provides access to default debug log buffer
|
||||
func WriteLogBuffer(w io.Writer, after, limit int) (_ int, err error) {
|
||||
return writeLogBuffer(w, debugLogRR, after, limit)
|
||||
}
|
||||
|
||||
// writes stream of entries from log buffer into provided writer array of JSON objects.
|
||||
func writeLogBuffer(w io.Writer, logBuf *rr, after, limit int) (_ int, err error) {
|
||||
var (
|
||||
// was at least one entry outputted?
|
||||
has bool
|
||||
)
|
||||
|
||||
debugLogRR.mux.RLock()
|
||||
defer debugLogRR.mux.RUnlock()
|
||||
if _, err = w.Write([]byte{'['}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, e := range logBuf.buf {
|
||||
if after >= e.num {
|
||||
continue
|
||||
}
|
||||
|
||||
// count back to zero
|
||||
limit--
|
||||
|
||||
if has {
|
||||
if _, err = w.Write([]byte{','}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = w.Write(e.rec); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
has = true
|
||||
}
|
||||
|
||||
if _, err = w.Write([]byte{']'}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rr) append(ent []byte) {
|
||||
r.mux.Lock()
|
||||
defer r.mux.Unlock()
|
||||
|
||||
r.num++
|
||||
|
||||
// modify serialized json
|
||||
var bufEnt = &rrBufEntry{r.num, append(ent[:len(ent)-2], []byte(fmt.Sprintf(`,"index":%d}`, r.num))...)}
|
||||
|
||||
if len(r.buf) >= debugLogCap {
|
||||
r.buf = append(r.buf[1:], bufEnt)
|
||||
} else {
|
||||
r.buf = append(r.buf, bufEnt)
|
||||
}
|
||||
}
|
||||
|
||||
// DebugBufferedLogger provides buffered logger compatible with zap.
|
||||
func DebugBufferedLogger(out *rr) *debugBufferingLogger {
|
||||
var encConf = zap.NewProductionEncoderConfig()
|
||||
encConf.EncodeTime = zapcore.RFC3339NanoTimeEncoder
|
||||
|
||||
return &debugBufferingLogger{
|
||||
LevelEnabler: zapcore.DebugLevel,
|
||||
out: out,
|
||||
enc: zapcore.NewJSONEncoder(encConf),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *debugBufferingLogger) With(fields []zapcore.Field) zapcore.Core {
|
||||
clone := c.clone()
|
||||
for i := range fields {
|
||||
fields[i].AddTo(clone.enc)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func (c *debugBufferingLogger) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if c.Enabled(ent.Level) {
|
||||
return ce.AddCore(ent, c)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
func (c *debugBufferingLogger) Write(ent zapcore.Entry, fields []zapcore.Field) error {
|
||||
encbuf, err := c.enc.EncodeEntry(ent, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.out.append(encbuf.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *debugBufferingLogger) Sync() error { return nil }
|
||||
func (c *debugBufferingLogger) clone() *debugBufferingLogger {
|
||||
return &debugBufferingLogger{
|
||||
LevelEnabler: c.LevelEnabler,
|
||||
enc: c.enc.Clone(),
|
||||
out: c.out,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user