Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4433c42f0 | ||
| 44ec3b663b | |||
| 4bb73514e9 |
@@ -1,5 +1,5 @@
|
||||
# Micro
|
||||

|
||||

|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://pkg.go.dev/go.unistack.org/micro/v3?tab=overview)
|
||||
[](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av3+event%3Apush)
|
||||
|
||||
@@ -4,18 +4,20 @@ package logger
|
||||
type Level int8
|
||||
|
||||
const (
|
||||
// TraceLevel level usually used to find bugs, very verbose
|
||||
// TraceLevel usually used to find bugs, very verbose
|
||||
TraceLevel Level = iota - 2
|
||||
// DebugLevel level used only when enabled debugging
|
||||
// DebugLevel used only when enabled debugging
|
||||
DebugLevel
|
||||
// InfoLevel level used for general info about what's going on inside the application
|
||||
// InfoLevel used for general info about what's going on inside the application
|
||||
InfoLevel
|
||||
// WarnLevel level used for non-critical entries
|
||||
// WarnLevel used for non-critical entries
|
||||
WarnLevel
|
||||
// ErrorLevel level used for errors that should definitely be noted
|
||||
// ErrorLevel used for errors that should definitely be noted
|
||||
ErrorLevel
|
||||
// FatalLevel level used for critical errors and then calls `os.Exit(1)`
|
||||
// FatalLevel used for critical errors and then calls `os.Exit(1)`
|
||||
FatalLevel
|
||||
// NoneLevel used to disable logging
|
||||
NoneLevel
|
||||
)
|
||||
|
||||
// String returns logger level string representation
|
||||
@@ -33,6 +35,8 @@ func (l Level) String() string {
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case NoneLevel:
|
||||
return "none"
|
||||
}
|
||||
return "info"
|
||||
}
|
||||
@@ -58,6 +62,8 @@ func ParseLevel(lvl string) Level {
|
||||
return ErrorLevel
|
||||
case FatalLevel.String():
|
||||
return FatalLevel
|
||||
case NoneLevel.String():
|
||||
return NoneLevel
|
||||
}
|
||||
return InfoLevel
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ type Options struct {
|
||||
AddStacktrace bool
|
||||
// DedupKeys deduplicate keys in log output
|
||||
DedupKeys bool
|
||||
// FatalFinalizers runs in order in [logger.Fatal] method
|
||||
FatalFinalizers []func(context.Context)
|
||||
}
|
||||
|
||||
var DefaultFatalFinalizer = func(ctx context.Context) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// NewOptions creates new options struct
|
||||
@@ -65,6 +71,7 @@ func NewOptions(opts ...Option) Options {
|
||||
AddSource: true,
|
||||
TimeFunc: time.Now,
|
||||
Meter: meter.DefaultMeter,
|
||||
FatalFinalizers: []func(context.Context){DefaultFatalFinalizer},
|
||||
}
|
||||
|
||||
WithMicroKeys()(&options)
|
||||
@@ -76,6 +83,13 @@ func NewOptions(opts ...Option) Options {
|
||||
return options
|
||||
}
|
||||
|
||||
// WithFatalFinalizers set logger.Fatal finalizers
|
||||
func WithFatalFinalizers(fncs ...func(context.Context)) Option {
|
||||
return func(o *Options) {
|
||||
o.FatalFinalizers = fncs
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextAttrFuncs appends default funcs for the context attrs filler
|
||||
func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option {
|
||||
return func(o *Options) {
|
||||
|
||||
@@ -4,14 +4,12 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v3/logger"
|
||||
"go.unistack.org/micro/v3/semconv"
|
||||
@@ -34,6 +32,7 @@ var (
|
||||
warnValue = slog.StringValue("warn")
|
||||
errorValue = slog.StringValue("error")
|
||||
fatalValue = slog.StringValue("fatal")
|
||||
noneValue = slog.StringValue("none")
|
||||
)
|
||||
|
||||
type wrapper struct {
|
||||
@@ -85,6 +84,8 @@ func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
|
||||
a.Value = errorValue
|
||||
case lvl >= logger.FatalLevel:
|
||||
a.Value = fatalValue
|
||||
case lvl >= logger.NoneLevel:
|
||||
a.Value = noneValue
|
||||
default:
|
||||
a.Value = infoValue
|
||||
}
|
||||
@@ -228,11 +229,12 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
|
||||
|
||||
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
|
||||
s.printLog(ctx, logger.FatalLevel, msg, attrs...)
|
||||
for _, fn := range s.opts.FatalFinalizers {
|
||||
fn(ctx)
|
||||
}
|
||||
if closer, ok := s.opts.Out.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
|
||||
@@ -316,6 +318,8 @@ func loggerToSlogLevel(level logger.Level) slog.Level {
|
||||
return slog.LevelDebug - 1
|
||||
case logger.FatalLevel:
|
||||
return slog.LevelError + 1
|
||||
case logger.NoneLevel:
|
||||
return slog.LevelError + 2
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
@@ -333,6 +337,8 @@ func slogToLoggerLevel(level slog.Level) logger.Level {
|
||||
return logger.TraceLevel
|
||||
case slog.LevelError + 1:
|
||||
return logger.FatalLevel
|
||||
case slog.LevelError + 2:
|
||||
return logger.NoneLevel
|
||||
default:
|
||||
return logger.InfoLevel
|
||||
}
|
||||
|
||||
@@ -36,6 +36,24 @@ func TestStacktrace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoneLevel(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(logger.WithLevel(logger.NoneLevel), logger.WithOutput(buf),
|
||||
WithHandlerFunc(slog.NewTextHandler),
|
||||
logger.WithAddStacktrace(true),
|
||||
)
|
||||
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Error(ctx, "msg1", errors.New("err"))
|
||||
|
||||
if buf.Len() != 0 {
|
||||
t.Fatalf("logger none level not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedBuffer(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
@@ -405,15 +423,16 @@ func TestLogger(t *testing.T) {
|
||||
func Test_WithContextAttrFunc(t *testing.T) {
|
||||
loggerContextAttrFuncs := []logger.ContextAttrFunc{
|
||||
func(ctx context.Context) []interface{} {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
attrs := make([]interface{}, 0, 10)
|
||||
for k, v := range md {
|
||||
switch k {
|
||||
case "X-Request-Id", "Phone", "External-Id", "Source-Service", "X-App-Install-Id", "Client-Id", "Client-Ip":
|
||||
attrs = append(attrs, strings.ToLower(k), v)
|
||||
key := strings.ToLower(k)
|
||||
switch key {
|
||||
case "x-request-id", "phone", "external-Id", "source-service", "x-app-install-id", "client-id", "client-ip":
|
||||
attrs = append(attrs, key, v)
|
||||
}
|
||||
}
|
||||
return attrs
|
||||
@@ -423,7 +442,7 @@ func Test_WithContextAttrFunc(t *testing.T) {
|
||||
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, loggerContextAttrFuncs...)
|
||||
|
||||
ctx := context.TODO()
|
||||
ctx = metadata.AppendIncomingContext(ctx, "X-Request-Id", uuid.New().String(),
|
||||
ctx = metadata.AppendOutgoingContext(ctx, "X-Request-Id", uuid.New().String(),
|
||||
"Source-Service", "Test-System")
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
@@ -436,17 +455,39 @@ func Test_WithContextAttrFunc(t *testing.T) {
|
||||
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"info"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test message"`))) {
|
||||
t.Fatalf("logger info, buf %s", buf.Bytes())
|
||||
}
|
||||
if !(bytes.Contains(buf.Bytes(), []byte(`"x-request-id":"`))) {
|
||||
if !(bytes.Contains(buf.Bytes(), []byte(`"x-request-id":`))) {
|
||||
t.Fatalf("logger info, buf %s", buf.Bytes())
|
||||
}
|
||||
if !(bytes.Contains(buf.Bytes(), []byte(`"source-service":"Test-System"`))) {
|
||||
t.Fatalf("logger info, buf %s", buf.Bytes())
|
||||
}
|
||||
buf.Reset()
|
||||
imd, _ := metadata.FromIncomingContext(ctx)
|
||||
omd, _ := metadata.FromOutgoingContext(ctx)
|
||||
l.Info(ctx, "test message1")
|
||||
imd.Set("Source-Service", "Test-System2")
|
||||
omd.Set("Source-Service", "Test-System2")
|
||||
l.Info(ctx, "test message2")
|
||||
|
||||
// t.Logf("xxx %s", buf.Bytes())
|
||||
}
|
||||
|
||||
func TestFatalFinalizers(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(
|
||||
logger.WithLevel(logger.TraceLevel),
|
||||
logger.WithOutput(buf),
|
||||
)
|
||||
if err := l.Init(
|
||||
logger.WithFatalFinalizers(func(ctx context.Context) {
|
||||
l.Info(ctx, "fatal finalizer")
|
||||
})); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l.Fatal(ctx, "info_msg1")
|
||||
if !bytes.Contains(buf.Bytes(), []byte("fatal finalizer")) {
|
||||
t.Fatalf("logger dont have fatal message, buf %s", buf.Bytes())
|
||||
}
|
||||
if !bytes.Contains(buf.Bytes(), []byte("info_msg1")) {
|
||||
t.Fatalf("logger dont have info_msg1 message, buf %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ package meter
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -49,9 +49,11 @@ type Meter interface {
|
||||
Set(opts ...Option) Meter
|
||||
// Histogram get or create histogram
|
||||
Histogram(name string, labels ...string) Histogram
|
||||
// HistogramExt get or create histogram with specified quantiles
|
||||
HistogramExt(name string, quantiles []float64, labels ...string) Histogram
|
||||
// Summary get or create summary
|
||||
Summary(name string, labels ...string) Summary
|
||||
// SummaryExt get or create summary with spcified quantiles and window time
|
||||
// SummaryExt get or create summary with specified quantiles and window time
|
||||
SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
|
||||
// Write writes metrics to io.Writer
|
||||
Write(w io.Writer, opts ...Option) error
|
||||
@@ -59,6 +61,8 @@ type Meter interface {
|
||||
Options() Options
|
||||
// String return meter type
|
||||
String() string
|
||||
// Unregister metric name and drop all data
|
||||
Unregister(name string, labels ...string) bool
|
||||
}
|
||||
|
||||
// Counter is a counter
|
||||
@@ -80,7 +84,11 @@ type FloatCounter interface {
|
||||
|
||||
// Gauge is a float64 gauge
|
||||
type Gauge interface {
|
||||
Add(float64)
|
||||
Get() float64
|
||||
Set(float64)
|
||||
Dec()
|
||||
Inc()
|
||||
}
|
||||
|
||||
// Histogram is a histogram for non-negative values with automatically created buckets
|
||||
@@ -117,6 +125,39 @@ func BuildLabels(labels ...string) []string {
|
||||
return labels
|
||||
}
|
||||
|
||||
var spool = newStringsPool(500)
|
||||
|
||||
type stringsPool struct {
|
||||
p *sync.Pool
|
||||
c int
|
||||
}
|
||||
|
||||
func newStringsPool(size int) *stringsPool {
|
||||
p := &stringsPool{c: size}
|
||||
p.p = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &strings.Builder{}
|
||||
},
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *stringsPool) Cap() int {
|
||||
return p.c
|
||||
}
|
||||
|
||||
func (p *stringsPool) Get() *strings.Builder {
|
||||
return p.p.Get().(*strings.Builder)
|
||||
}
|
||||
|
||||
func (p *stringsPool) Put(b *strings.Builder) {
|
||||
if b.Cap() > p.c {
|
||||
return
|
||||
}
|
||||
b.Reset()
|
||||
p.p.Put(b)
|
||||
}
|
||||
|
||||
// BuildName used to combine metric with labels.
|
||||
// If labels count is odd, drop last element
|
||||
func BuildName(name string, labels ...string) string {
|
||||
@@ -125,8 +166,6 @@ func BuildName(name string, labels ...string) string {
|
||||
}
|
||||
|
||||
if len(labels) > 2 {
|
||||
sort.Sort(byKey(labels))
|
||||
|
||||
idx := 0
|
||||
for {
|
||||
if labels[idx] == labels[idx+2] {
|
||||
@@ -141,7 +180,9 @@ func BuildName(name string, labels ...string) string {
|
||||
}
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b := spool.Get()
|
||||
defer spool.Put(b)
|
||||
|
||||
_, _ = b.WriteString(name)
|
||||
_, _ = b.WriteRune('{')
|
||||
for idx := 0; idx < len(labels); idx += 2 {
|
||||
@@ -149,8 +190,9 @@ func BuildName(name string, labels ...string) string {
|
||||
_, _ = b.WriteRune(',')
|
||||
}
|
||||
_, _ = b.WriteString(labels[idx])
|
||||
_, _ = b.WriteString(`=`)
|
||||
_, _ = b.WriteString(strconv.Quote(labels[idx+1]))
|
||||
_, _ = b.WriteString(`="`)
|
||||
_, _ = b.WriteString(labels[idx+1])
|
||||
_, _ = b.WriteRune('"')
|
||||
}
|
||||
_, _ = b.WriteRune('}')
|
||||
|
||||
|
||||
@@ -50,11 +50,12 @@ func TestBuildName(t *testing.T) {
|
||||
data := map[string][]string{
|
||||
`my_metric{firstlabel="value2",zerolabel="value3"}`: {
|
||||
"my_metric",
|
||||
"zerolabel", "value3", "firstlabel", "value2",
|
||||
"firstlabel", "value2",
|
||||
"zerolabel", "value3",
|
||||
},
|
||||
`my_metric{broker="broker2",register="mdns",server="tcp"}`: {
|
||||
"my_metric",
|
||||
"broker", "broker1", "broker", "broker2", "server", "http", "server", "tcp", "register", "mdns",
|
||||
"broker", "broker1", "broker", "broker2", "register", "mdns", "server", "http", "server", "tcp",
|
||||
},
|
||||
`my_metric{aaa="aaa"}`: {
|
||||
"my_metric",
|
||||
|
||||
@@ -28,6 +28,10 @@ func (r *noopMeter) Name() string {
|
||||
return r.opts.Name
|
||||
}
|
||||
|
||||
func (r *noopMeter) Unregister(name string, labels ...string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Init initialize options
|
||||
func (r *noopMeter) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
@@ -66,6 +70,11 @@ func (r *noopMeter) Histogram(_ string, labels ...string) Histogram {
|
||||
return &noopHistogram{labels: labels}
|
||||
}
|
||||
|
||||
// HistogramExt implements the Meter interface
|
||||
func (r *noopMeter) HistogramExt(_ string, quantiles []float64, labels ...string) Histogram {
|
||||
return &noopHistogram{labels: labels}
|
||||
}
|
||||
|
||||
// Set implements the Meter interface
|
||||
func (r *noopMeter) Set(opts ...Option) Meter {
|
||||
m := &noopMeter{opts: r.opts}
|
||||
@@ -132,6 +141,18 @@ type noopGauge struct {
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (r *noopGauge) Add(float64) {
|
||||
}
|
||||
|
||||
func (r *noopGauge) Set(float64) {
|
||||
}
|
||||
|
||||
func (r *noopGauge) Inc() {
|
||||
}
|
||||
|
||||
func (r *noopGauge) Dec() {
|
||||
}
|
||||
|
||||
func (r *noopGauge) Get() float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
var DefaultQuantiles = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
|
||||
|
||||
// Option powers the configuration for metrics implementations:
|
||||
type Option func(*Options)
|
||||
|
||||
@@ -23,6 +25,8 @@ type Options struct {
|
||||
WriteProcessMetrics bool
|
||||
// WriteFDMetrics flag to write fd metrics
|
||||
WriteFDMetrics bool
|
||||
// Quantiles specifies buckets for histogram
|
||||
Quantiles []float64
|
||||
}
|
||||
|
||||
// NewOptions prepares a set of options:
|
||||
@@ -61,14 +65,12 @@ func Address(value string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// TimingObjectives defines the desired spread of statistics for histogram / timing metrics:
|
||||
func TimingObjectives(value map[float64]float64) Option {
|
||||
// Quantiles defines the desired spread of statistics for histogram metrics:
|
||||
func Quantiles(quantiles []float64) Option {
|
||||
return func(o *Options) {
|
||||
o.TimingObjectives = value
|
||||
o.Quantiles = quantiles
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Labels add the meter labels
|
||||
func Labels(ls ...string) Option {
|
||||
|
||||
Reference in New Issue
Block a user