3
0

Add route with access to server-log buffer

This commit is contained in:
Denis Arh
2022-02-13 18:23:27 +01:00
parent 2082908f39
commit e0bcaf8662
4 changed files with 207 additions and 7 deletions

View File

@@ -2,13 +2,14 @@ package server
import (
"fmt"
"github.com/cortezaproject/corteza-server/pkg/corredor"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/davecgh/go-spew/spew"
"net/http"
"reflect"
"runtime"
"github.com/cortezaproject/corteza-server/pkg/corredor"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/davecgh/go-spew/spew"
"github.com/go-chi/chi/v5"
)

View File

@@ -4,12 +4,14 @@ import (
"fmt"
"net/http"
"path"
"strconv"
"strings"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/healthcheck"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/version"
"github.com/go-chi/chi/v5"
@@ -68,6 +70,7 @@ func shutdownRoutes() (r chi.Router) {
// routes used when in active mode
func activeRoutes(log *zap.Logger, mountable []func(r chi.Router), envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt) (r chi.Router) {
r = chi.NewRouter()
r.Use(handleCORS)
r.Route("/"+strings.TrimPrefix(httpOpt.BaseUrl, "/"), func(r chi.Router) {
// Reports error to Sentry if enabled
@@ -130,6 +133,8 @@ func mountServiceHandlers(r chi.Router, log *zap.Logger, opt options.HttpServerO
if opt.EnableHealthcheckRoute {
mountHealthCheckHandler(r, log, opt.BaseUrl)
}
mountDebugLogViewer(r, log)
}
func mountDebugHandler(r chi.Router, log *zap.Logger) {
@@ -184,3 +189,38 @@ func mountHealthCheckHandler(r chi.Router, log *zap.Logger, basePath string) {
}
}
func mountDebugLogViewer(r chi.Router, log *zap.Logger) {
var (
path = "/view-log"
)
r.Get(path+".json", func(w http.ResponseWriter, r *http.Request) {
var (
after int = 0
limit int = 100
err error
q = r.URL.Query()
)
if aux := q.Get("after"); len(aux) > 0 {
after, err = strconv.Atoi(aux)
if err != nil {
errors.ProperlyServeHTTP(w, r, errors.InvalidData("invalid value format for after: %w", err), false)
return
}
}
if aux := q.Get("limit"); len(aux) > 0 {
limit, err = strconv.Atoi(aux)
if err != nil {
errors.ProperlyServeHTTP(w, r, errors.InvalidData("invalid value format for limit: %w", err), false)
return
}
}
_, _ = logger.WriteLogBuffer(w, after, limit)
})
log.Debug("log viewer route enabled: " + path)
}

View File

@@ -37,16 +37,18 @@ func Init() {
}
var (
err error
conf = applyOptions(zap.NewProductionConfig(), opt)
conf = applyOptions(zap.NewProductionConfig(), opt)
logger, err = conf.Build()
)
defaultLogger, err = conf.Build()
if err != nil {
panic(err)
}
defaultLogger = applySpecials(defaultLogger, opt)
logger = applySpecials(defaultLogger, opt)
logger = withDebugBuffer(logger)
defaultLogger = logger
}
func MakeDebugLogger() *zap.Logger {
@@ -71,6 +73,8 @@ func MakeDebugLogger() *zap.Logger {
panic(err)
}
logger = withDebugBuffer(logger)
return applySpecials(logger, &dbgOpt)
}
@@ -98,6 +102,15 @@ func applySpecials(l *zap.Logger, opt *options.LogOpt) *zap.Logger {
return l.WithOptions(zap.AddStacktrace(mustParseLevel(opt.StacktraceLevel)))
}
// 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)

146
pkg/logger/rrbuf.go Normal file
View File

@@ -0,0 +1,146 @@
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 (
debugLogCap = 10240
)
var (
// initialize debug logger round robin db
debugLogRR = &rr{
buf: make([]*rrBufEntry, 0),
}
)
func WriteLogBuffer(w io.Writer, 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 debugLogRR.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)
}
}
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,
}
}