Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
package log
import "github.com/qsfera/server/pkg/shared"
// Configure initializes a service-specific logger instance.
func Configure(name string, commons *shared.Commons, localServiceLogLevel string) Logger {
return NewLogger(
Name(name),
Level(localServiceLogLevel),
Pretty(commons.Log.Pretty),
Color(commons.Log.Color),
File(commons.Log.File),
)
}
+145
View File
@@ -0,0 +1,145 @@
package log
import (
"context"
"fmt"
"os"
"runtime"
"strings"
"time"
chimiddleware "github.com/go-chi/chi/v5/middleware"
mzlog "github.com/go-micro/plugins/v4/logger/zerolog"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"go-micro.dev/v4/logger"
)
var (
RequestIDString = "request-id"
)
func init() {
// this is ugly, but "logger.DefaultLogger" is a global variable, and we need to set it _before_ anybody uses it
setMicroLogger()
}
// for logging reasons we don't want the same logging level on both КуСфера and micro. As a framework builder we do not
// want to expose to the end user the internal framework logs unless explicitly specified.
func setMicroLogger() {
if os.Getenv("MICRO_LOG_LEVEL") == "" {
_ = os.Setenv("MICRO_LOG_LEVEL", "error")
}
lev, err := zerolog.ParseLevel(os.Getenv("MICRO_LOG_LEVEL"))
if err != nil {
lev = zerolog.ErrorLevel
}
logger.DefaultLogger = mzlog.NewLogger(
logger.WithLevel(logger.Level(lev)),
logger.WithFields(map[string]any{
"system": "go-micro",
}),
)
}
// Logger simply wraps the zerolog logger.
type Logger struct {
zerolog.Logger
}
// NopLogger initializes a no-operation logger.
func NopLogger() Logger {
return Logger{zerolog.Nop()}
}
type LineInfoHook struct{}
// Run is a hook to add line info to log messages.
// I found the zerolog example for this here:
// https://github.com/rs/zerolog/issues/22#issuecomment-1127295489
func (h LineInfoHook) Run(e *zerolog.Event, _ zerolog.Level, _ string) {
_, file, line, ok := runtime.Caller(3)
if ok {
e.Str("line", fmt.Sprintf("%s:%d", file, line))
}
}
// NewLogger initializes a new logger instance.
func NewLogger(opts ...Option) Logger {
options := newOptions(opts...)
// set GlobalLevel() to the minimum value -1 = TraceLevel, so that only the services' log level matter
zerolog.SetGlobalLevel(zerolog.TraceLevel)
var logLevel zerolog.Level
switch strings.ToLower(options.Level) {
case "panic":
logLevel = zerolog.PanicLevel
case "fatal":
logLevel = zerolog.FatalLevel
case "error":
logLevel = zerolog.ErrorLevel
case "warn":
logLevel = zerolog.WarnLevel
case "info":
logLevel = zerolog.InfoLevel
case "debug":
logLevel = zerolog.DebugLevel
case "trace":
logLevel = zerolog.TraceLevel
default:
logLevel = zerolog.ErrorLevel
}
var l zerolog.Logger
if options.Pretty {
l = log.Output(
zerolog.NewConsoleWriter(
func(w *zerolog.ConsoleWriter) {
w.TimeFormat = time.RFC3339
w.Out = os.Stderr
w.NoColor = !options.Color
},
),
)
} else if options.File != "" {
f, err := os.OpenFile(options.File, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
print(fmt.Sprintf("file could not be opened for writing: %s. error: %v", options.File, err))
os.Exit(1)
}
l = l.Output(f)
} else {
l = zerolog.New(os.Stderr)
}
l = l.With().
Str("service", options.Name).
Timestamp().
Logger().Level(logLevel)
if logLevel <= zerolog.InfoLevel {
var lineInfoHook LineInfoHook
l = l.Hook(lineInfoHook)
}
return Logger{
l,
}
}
// SubloggerWithRequestID returns a sub-logger with the x-request-id added to all events
func (l Logger) SubloggerWithRequestID(c context.Context) Logger {
return Logger{
l.With().Str(RequestIDString, chimiddleware.GetReqID(c)).Logger(),
}
}
// Deprecation logs a deprecation message,
// it is used to inform the user that a certain feature is deprecated and will be removed in the future.
// Do not use a logger here because the message MUST be visible independent of the log level.
func Deprecation(a ...any) {
fmt.Printf("\033[1;31mDEPRECATION: %s\033[0m\n", a...)
}
+24
View File
@@ -0,0 +1,24 @@
package log_test
import (
"testing"
"github.com/onsi/gomega"
"github.com/qsfera/server/internal/testenv"
"github.com/qsfera/server/pkg/log"
)
func TestDeprecation(t *testing.T) {
cmdTest := testenv.NewCMDTest(t.Name())
if cmdTest.ShouldRun() {
log.Deprecation("this is a deprecation")
return
}
out, err := cmdTest.Run()
g := gomega.NewWithT(t)
g.Expect(err).ToNot(gomega.HaveOccurred())
g.Expect(string(out)).To(gomega.HavePrefix("\033[1;31mDEPRECATION: this is a deprecation"))
}
+74
View File
@@ -0,0 +1,74 @@
package log
import (
"io"
"github.com/rs/zerolog"
"github.com/sirupsen/logrus"
)
type levelMap map[logrus.Level]zerolog.Level
var levelMapping = levelMap{
logrus.PanicLevel: zerolog.PanicLevel,
logrus.ErrorLevel: zerolog.ErrorLevel,
logrus.TraceLevel: zerolog.TraceLevel,
logrus.DebugLevel: zerolog.DebugLevel,
logrus.WarnLevel: zerolog.WarnLevel,
logrus.InfoLevel: zerolog.InfoLevel,
}
// LogrusWrapper around zerolog. Required because idp uses logrus internally.
type LogrusWrapper struct {
zeroLog *zerolog.Logger
levelMap levelMap
}
// LogrusWrap returns a logrus logger which internally logs to /dev/null. Messages are passed to the
// underlying zerolog via hooks.
func LogrusWrap(zr zerolog.Logger) *logrus.Logger {
lr := logrus.New()
lr.SetOutput(io.Discard)
lr.SetLevel(logrusLevel(zr.GetLevel()))
lr.AddHook(&LogrusWrapper{
zeroLog: &zr,
levelMap: levelMapping,
})
return lr
}
// Levels on which logrus hooks should be triggered
func (h *LogrusWrapper) Levels() []logrus.Level {
return logrus.AllLevels
}
// Fire called by logrus on new message
func (h *LogrusWrapper) Fire(entry *logrus.Entry) error {
h.zeroLog.WithLevel(h.levelMap[entry.Level]).
Fields(zeroLogFields(entry.Data)).
Msg(entry.Message)
return nil
}
// Convert logrus fields to zerolog
func zeroLogFields(fields logrus.Fields) map[string]any {
fm := make(map[string]any)
for k, v := range fields {
fm[k] = v
}
return fm
}
// Convert logrus level to zerolog
func logrusLevel(level zerolog.Level) logrus.Level {
for lrLvl, zrLvl := range levelMapping {
if zrLvl == level {
return lrLvl
}
}
panic("Unexpected loglevel")
}
+64
View File
@@ -0,0 +1,64 @@
package log
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Name string
Level string
Pretty bool
Color bool
File string
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{
Name: "qsfera",
Level: "info",
Pretty: true,
Color: true,
}
for _, o := range opts {
o(&opt)
}
return opt
}
// Name provides a function to set the name option.
func Name(val string) Option {
return func(o *Options) {
o.Name = val
}
}
// Level provides a function to set the level option.
func Level(val string) Option {
return func(o *Options) {
o.Level = val
}
}
// Pretty provides a function to set the pretty option.
func Pretty(val bool) Option {
return func(o *Options) {
o.Pretty = val
}
}
// Color provides a function to set the color option.
func Color(val bool) Option {
return func(o *Options) {
o.Color = val
}
}
// File provides a function to set the file option.
func File(val string) Option {
return func(o *Options) {
o.File = val
}
}