tracer and logger improvements #312
| @@ -71,76 +71,106 @@ type Logger interface { | ||||
| type Field interface{} | ||||
|  | ||||
| // Info writes msg to default logger on info level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Info(ctx context.Context, args ...interface{}) { | ||||
| 	DefaultLogger.Info(ctx, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Info(ctx, args...) | ||||
| } | ||||
|  | ||||
| // Error writes msg to default logger on error level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Error(ctx context.Context, args ...interface{}) { | ||||
| 	DefaultLogger.Error(ctx, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Error(ctx, args...) | ||||
| } | ||||
|  | ||||
| // Debug writes msg to default logger on debug level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Debug(ctx context.Context, args ...interface{}) { | ||||
| 	DefaultLogger.Debug(ctx, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debug(ctx, args...) | ||||
| } | ||||
|  | ||||
| // Warn writes msg to default logger on warn level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Warn(ctx context.Context, args ...interface{}) { | ||||
| 	DefaultLogger.Warn(ctx, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warn(ctx, args...) | ||||
| } | ||||
|  | ||||
| // Trace writes msg to default logger on trace level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Trace(ctx context.Context, args ...interface{}) { | ||||
| 	DefaultLogger.Trace(ctx, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Trace(ctx, args...) | ||||
| } | ||||
|  | ||||
| // Fatal writes msg to default logger on fatal level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Fatal(ctx context.Context, args ...interface{}) { | ||||
| 	DefaultLogger.Fatal(ctx, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatal(ctx, args...) | ||||
| } | ||||
|  | ||||
| // Infof writes formatted msg to default logger on info level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Infof(ctx context.Context, msg string, args ...interface{}) { | ||||
| 	DefaultLogger.Infof(ctx, msg, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Infof(ctx, msg, args...) | ||||
| } | ||||
|  | ||||
| // Errorf writes formatted msg to default logger on error level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Errorf(ctx context.Context, msg string, args ...interface{}) { | ||||
| 	DefaultLogger.Errorf(ctx, msg, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Errorf(ctx, msg, args...) | ||||
| } | ||||
|  | ||||
| // Debugf writes formatted msg to default logger on debug level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Debugf(ctx context.Context, msg string, args ...interface{}) { | ||||
| 	DefaultLogger.Debugf(ctx, msg, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debugf(ctx, msg, args...) | ||||
| } | ||||
|  | ||||
| // Warnf writes formatted msg to default logger on warn level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Warnf(ctx context.Context, msg string, args ...interface{}) { | ||||
| 	DefaultLogger.Warnf(ctx, msg, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warnf(ctx, msg, args...) | ||||
| } | ||||
|  | ||||
| // Tracef writes formatted msg to default logger on trace level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Tracef(ctx context.Context, msg string, args ...interface{}) { | ||||
| 	DefaultLogger.Tracef(ctx, msg, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Tracef(ctx, msg, args...) | ||||
| } | ||||
|  | ||||
| // Fatalf writes formatted msg to default logger on fatal level | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Fatalf(ctx context.Context, msg string, args ...interface{}) { | ||||
| 	DefaultLogger.Fatalf(ctx, msg, args...) | ||||
| 	DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatalf(ctx, msg, args...) | ||||
| } | ||||
|  | ||||
| // V returns true if passed level enabled in default logger | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func V(level Level) bool { | ||||
| 	return DefaultLogger.V(level) | ||||
| } | ||||
|  | ||||
| // Init initialize logger | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Init(opts ...Option) error { | ||||
| 	return DefaultLogger.Init(opts...) | ||||
| } | ||||
|  | ||||
| // Fields create logger with specific fields | ||||
| // | ||||
| // Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations | ||||
| func Fields(fields ...interface{}) Logger { | ||||
| 	return DefaultLogger.Fields(fields...) | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ type Options struct { | ||||
| 	Name string | ||||
| 	// Fields holds additional metadata | ||||
| 	Fields []interface{} | ||||
|  | ||||
| 	// CallerSkipCount number of frmaes to skip | ||||
| 	CallerSkipCount int | ||||
| 	// ContextAttrFuncs contains funcs that executed before log func on context | ||||
| @@ -28,14 +29,18 @@ type Options struct { | ||||
| 	TimeKey string | ||||
| 	// LevelKey is the key used for the level of the log call | ||||
| 	LevelKey string | ||||
| 	// ErroreKey is the key used for the error of the log call | ||||
| 	ErrorKey string | ||||
| 	// MessageKey is the key used for the message of the log call | ||||
| 	MessageKey string | ||||
| 	// SourceKey is the key used for the source file and line of the log call | ||||
| 	SourceKey string | ||||
| 	// StacktraceKey is the key used for the stacktrace | ||||
| 	StacktraceKey string | ||||
| 	// Stacktrace controls writing of stacktaces on error | ||||
| 	Stacktrace bool | ||||
| 	// AddStacktrace controls writing of stacktaces on error | ||||
| 	AddStacktrace bool | ||||
| 	// AddSource enabled writing source file and position in log | ||||
| 	AddSource bool | ||||
| 	// The logging level the logger should log | ||||
| 	Level Level | ||||
| } | ||||
| @@ -49,6 +54,7 @@ func NewOptions(opts ...Option) Options { | ||||
| 		CallerSkipCount:  DefaultCallerSkipCount, | ||||
| 		Context:          context.Background(), | ||||
| 		ContextAttrFuncs: DefaultContextAttrFuncs, | ||||
| 		AddSource:        true, | ||||
| 	} | ||||
|  | ||||
| 	WithMicroKeys()(&options) | ||||
| @@ -56,6 +62,7 @@ func NewOptions(opts ...Option) Options { | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	return options | ||||
| } | ||||
|  | ||||
| @@ -87,10 +94,17 @@ func WithOutput(out io.Writer) Option { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithStacktrace controls writing stacktrace on error | ||||
| func WithStacktrace(v bool) Option { | ||||
| // WitAddStacktrace controls writing stacktrace on error | ||||
| func WithAddStacktrace(v bool) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Stacktrace = v | ||||
| 		o.AddStacktrace = v | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WitAddSource controls writing source file and pos in log | ||||
| func WithAddSource(v bool) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.AddSource = v | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -122,6 +136,7 @@ func WithZapKeys() Option { | ||||
| 		o.MessageKey = "msg" | ||||
| 		o.SourceKey = "caller" | ||||
| 		o.StacktraceKey = "stacktrace" | ||||
| 		o.ErrorKey = "error" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -132,6 +147,7 @@ func WithZerologKeys() Option { | ||||
| 		o.MessageKey = "message" | ||||
| 		o.SourceKey = "caller" | ||||
| 		o.StacktraceKey = "stacktrace" | ||||
| 		o.ErrorKey = "error" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -142,6 +158,7 @@ func WithSlogKeys() Option { | ||||
| 		o.MessageKey = slog.MessageKey | ||||
| 		o.SourceKey = slog.SourceKey | ||||
| 		o.StacktraceKey = "stacktrace" | ||||
| 		o.ErrorKey = "error" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -152,5 +169,6 @@ func WithMicroKeys() Option { | ||||
| 		o.MessageKey = "msg" | ||||
| 		o.SourceKey = "caller" | ||||
| 		o.StacktraceKey = "stacktrace" | ||||
| 		o.ErrorKey = "error" | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -82,9 +82,9 @@ func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger { | ||||
|  | ||||
| 	l.leveler = new(slog.LevelVar) | ||||
| 	handleOpt := &slog.HandlerOptions{ | ||||
| 		ReplaceAttr: s.renameAttr, | ||||
| 		ReplaceAttr: l.renameAttr, | ||||
| 		Level:       l.leveler, | ||||
| 		AddSource:   true, | ||||
| 		AddSource:   l.opts.AddSource, | ||||
| 	} | ||||
| 	l.leveler.Set(loggerToSlogLevel(l.opts.Level)) | ||||
| 	handler := slog.NewJSONHandler(options.Out, handleOpt) | ||||
| @@ -109,22 +109,22 @@ func (s *slogLogger) Options() logger.Options { | ||||
|  | ||||
| func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger { | ||||
| 	s.mu.RLock() | ||||
| 	nl := &slogLogger{opts: s.opts} | ||||
| 	nl.leveler = new(slog.LevelVar) | ||||
| 	nl.leveler.Set(s.leveler.Level()) | ||||
| 	l := &slogLogger{opts: s.opts} | ||||
| 	l.leveler = new(slog.LevelVar) | ||||
| 	l.leveler.Set(s.leveler.Level()) | ||||
|  | ||||
| 	handleOpt := &slog.HandlerOptions{ | ||||
| 		ReplaceAttr: nl.renameAttr, | ||||
| 		Level:       nl.leveler, | ||||
| 		AddSource:   true, | ||||
| 		ReplaceAttr: l.renameAttr, | ||||
| 		Level:       l.leveler, | ||||
| 		AddSource:   l.opts.AddSource, | ||||
| 	} | ||||
|  | ||||
| 	handler := slog.NewJSONHandler(s.opts.Out, handleOpt) | ||||
| 	nl.slog = slog.New(handler).With(attrs...) | ||||
| 	l.slog = slog.New(handler).With(attrs...) | ||||
|  | ||||
| 	s.mu.RUnlock() | ||||
|  | ||||
| 	return nl | ||||
| 	return l | ||||
| } | ||||
|  | ||||
| func (s *slogLogger) Init(opts ...logger.Option) error { | ||||
| @@ -142,7 +142,7 @@ func (s *slogLogger) Init(opts ...logger.Option) error { | ||||
| 	handleOpt := &slog.HandlerOptions{ | ||||
| 		ReplaceAttr: s.renameAttr, | ||||
| 		Level:       s.leveler, | ||||
| 		AddSource:   true, | ||||
| 		AddSource:   s.opts.AddSource, | ||||
| 	} | ||||
| 	s.leveler.Set(loggerToSlogLevel(s.opts.Level)) | ||||
| 	handler := slog.NewJSONHandler(s.opts.Out, handleOpt) | ||||
| @@ -160,16 +160,35 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, attrs ...interfa | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||
| 	if s.opts.Stacktrace && lvl == logger.ErrorLevel { | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if s.opts.AddStacktrace && lvl == logger.ErrorLevel { | ||||
| 		stackInfo := make([]byte, 1024*1024) | ||||
| 		if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { | ||||
| 			traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) | ||||
| 			if len(traceLines) != 0 { | ||||
| 				r.AddAttrs(slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])) | ||||
| 				attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// r.Add(attrs[1:]...) | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	r.Attrs(func(a slog.Attr) bool { | ||||
| 		if a.Key == s.opts.ErrorKey { | ||||
| 			if span, ok := tracer.SpanFromContext(ctx); ok { | ||||
| 				span.SetStatus(tracer.SpanStatusError, a.Value.String()) | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 	}) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -179,17 +198,36 @@ func (s *slogLogger) Logf(ctx context.Context, lvl logger.Level, msg string, att | ||||
| 	} | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), fmt.Sprintf(msg, attrs...), pcs[0]) | ||||
| 	if s.opts.Stacktrace && lvl == logger.ErrorLevel { | ||||
| 	r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), msg, pcs[0]) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if s.opts.AddStacktrace && lvl == logger.ErrorLevel { | ||||
| 		stackInfo := make([]byte, 1024*1024) | ||||
| 		if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { | ||||
| 			traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) | ||||
| 			if len(traceLines) != 0 { | ||||
| 				r.AddAttrs(slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])) | ||||
| 				attrs = append(attrs, (slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// r.Add(attrs...) | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	r.Attrs(func(a slog.Attr) bool { | ||||
| 		if a.Key == s.opts.ErrorKey { | ||||
| 			if span, ok := tracer.SpanFromContext(ctx); ok { | ||||
| 				span.SetStatus(tracer.SpanStatusError, a.Value.String()) | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 	}) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -200,7 +238,17 @@ func (s *slogLogger) Info(ctx context.Context, attrs ...interface{}) { | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||
| 	// r.Add(attrs[1:]...) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -210,8 +258,18 @@ func (s *slogLogger) Infof(ctx context.Context, msg string, attrs ...interface{} | ||||
| 	} | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf(msg, attrs...), pcs[0]) | ||||
| 	// r.Add(attrs...) | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelInfo, msg, pcs[0]) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -222,7 +280,17 @@ func (s *slogLogger) Debug(ctx context.Context, attrs ...interface{}) { | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||
| 	// r.Add(attrs[1:]...) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -232,8 +300,18 @@ func (s *slogLogger) Debugf(ctx context.Context, msg string, attrs ...interface{ | ||||
| 	} | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf(msg, attrs...), pcs[0]) | ||||
| 	// r.Add(attrs...) | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelDebug, msg, pcs[0]) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -244,7 +322,17 @@ func (s *slogLogger) Trace(ctx context.Context, attrs ...interface{}) { | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelDebug-1, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||
| 	// r.Add(attrs[1:]...) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -254,8 +342,18 @@ func (s *slogLogger) Tracef(ctx context.Context, msg string, attrs ...interface{ | ||||
| 	} | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelDebug-1, fmt.Sprintf(msg, attrs...), pcs[0]) | ||||
| 	// r.Add(attrs...) | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelDebug-1, msg, pcs[0]) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -266,18 +364,28 @@ func (s *slogLogger) Error(ctx context.Context, attrs ...interface{}) { | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||
| 	//	r.Add(attrs[1:]...) | ||||
| 	if s.opts.Stacktrace { | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if s.opts.AddStacktrace { | ||||
| 		stackInfo := make([]byte, 1024*1024) | ||||
| 		if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { | ||||
| 			traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) | ||||
| 			if len(traceLines) != 0 { | ||||
| 				r.AddAttrs(slog.String("stacktrace", traceLines[len(traceLines)-1])) | ||||
| 				attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1])) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	r.Attrs(func(a slog.Attr) bool { | ||||
| 		if a.Key == "error" { | ||||
| 		if a.Key == s.opts.ErrorKey { | ||||
| 			if span, ok := tracer.SpanFromContext(ctx); ok { | ||||
| 				span.SetStatus(tracer.SpanStatusError, a.Value.String()) | ||||
| 				return false | ||||
| @@ -294,19 +402,29 @@ func (s *slogLogger) Errorf(ctx context.Context, msg string, attrs ...interface{ | ||||
| 	} | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf(msg, attrs...), pcs[0]) | ||||
| 	// r.Add(attrs...) | ||||
| 	if s.opts.Stacktrace { | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelError, msg, pcs[0]) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if s.opts.AddStacktrace { | ||||
| 		stackInfo := make([]byte, 1024*1024) | ||||
| 		if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { | ||||
| 			traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) | ||||
| 			if len(traceLines) != 0 { | ||||
| 				r.AddAttrs(slog.String("stacktrace", traceLines[len(traceLines)-1])) | ||||
| 				attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1])) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs...) | ||||
| 	r.Attrs(func(a slog.Attr) bool { | ||||
| 		if a.Key == "error" { | ||||
| 		if a.Key == s.opts.ErrorKey { | ||||
| 			if span, ok := tracer.SpanFromContext(ctx); ok { | ||||
| 				span.SetStatus(tracer.SpanStatusError, a.Value.String()) | ||||
| 				return false | ||||
| @@ -324,7 +442,17 @@ func (s *slogLogger) Fatal(ctx context.Context, attrs ...interface{}) { | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelError+1, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||
| 	// r.Add(attrs[1:]...) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| 	os.Exit(1) | ||||
| } | ||||
| @@ -335,8 +463,18 @@ func (s *slogLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{ | ||||
| 	} | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelError+1, fmt.Sprintf(msg, attrs...), pcs[0]) | ||||
| 	// r.Add(attrs...) | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelError+1, msg, pcs[0]) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| 	os.Exit(1) | ||||
| } | ||||
| @@ -348,7 +486,17 @@ func (s *slogLogger) Warn(ctx context.Context, attrs ...interface{}) { | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf("%s", attrs[0]), pcs[0]) | ||||
| 	// r.Add(attrs[1:]...) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
| @@ -358,8 +506,18 @@ func (s *slogLogger) Warnf(ctx context.Context, msg string, attrs ...interface{} | ||||
| 	} | ||||
| 	var pcs [1]uintptr | ||||
| 	runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf(msg, attrs...), pcs[0]) | ||||
| 	// r.Add(attrs...) | ||||
| 	r := slog.NewRecord(time.Now(), slog.LevelWarn, msg, pcs[0]) | ||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | ||||
| 		attrs = append(attrs, fn(ctx)...) | ||||
| 	} | ||||
|  | ||||
| 	for idx, attr := range attrs { | ||||
| 		if ve, ok := attr.(error); ok && ve != nil { | ||||
| 			attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	r.Add(attrs[1:]...) | ||||
| 	_ = s.slog.Handler().Handle(ctx, r) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package slog | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"testing" | ||||
|  | ||||
| @@ -17,10 +18,30 @@ func TestError(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	l.Error(ctx, "message") | ||||
| 	l.Error(ctx, "message", fmt.Errorf("error message")) | ||||
| 	if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) { | ||||
| 		t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes()) | ||||
| 	} | ||||
| 	if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) { | ||||
| 		t.Fatalf("logger error not works, buf contains: %s", buf.Bytes()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestErrorf(t *testing.T) { | ||||
| 	ctx := context.TODO() | ||||
| 	buf := bytes.NewBuffer(nil) | ||||
| 	l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithStacktrace(true)) | ||||
| 	if err := l.Init(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	l.Errorf(ctx, "message", fmt.Errorf("error message")) | ||||
| 	if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) { | ||||
| 		t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes()) | ||||
| 	} | ||||
| 	if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) { | ||||
| 		t.Fatalf("logger error not works, buf contains: %s", buf.Bytes()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestContext(t *testing.T) { | ||||
|   | ||||
							
								
								
									
										139
									
								
								tracer/memory/memory.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								tracer/memory/memory.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| package memory | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.unistack.org/micro/v3/tracer" | ||||
| 	"go.unistack.org/micro/v3/util/id" | ||||
| ) | ||||
|  | ||||
| var _ tracer.Tracer = (*Tracer)(nil) | ||||
|  | ||||
| type Tracer struct { | ||||
| 	opts  tracer.Options | ||||
| 	spans []tracer.Span | ||||
| } | ||||
|  | ||||
| func (t *Tracer) Spans() []tracer.Span { | ||||
| 	return t.spans | ||||
| } | ||||
|  | ||||
| func (t *Tracer) Start(ctx context.Context, name string, opts ...tracer.SpanOption) (context.Context, tracer.Span) { | ||||
| 	options := tracer.NewSpanOptions(opts...) | ||||
| 	span := &Span{ | ||||
| 		name:      name, | ||||
| 		ctx:       ctx, | ||||
| 		tracer:    t, | ||||
| 		kind:      options.Kind, | ||||
| 		startTime: time.Now(), | ||||
| 	} | ||||
| 	span.spanID.s, _ = id.New() | ||||
| 	span.traceID.s, _ = id.New() | ||||
| 	if span.ctx == nil { | ||||
| 		span.ctx = context.Background() | ||||
| 	} | ||||
| 	t.spans = append(t.spans, span) | ||||
| 	return tracer.NewSpanContext(ctx, span), span | ||||
| } | ||||
|  | ||||
| func (t *Tracer) Flush(_ context.Context) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (t *Tracer) Init(opts ...tracer.Option) error { | ||||
| 	for _, o := range opts { | ||||
| 		o(&t.opts) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (t *Tracer) Name() string { | ||||
| 	return t.opts.Name | ||||
| } | ||||
|  | ||||
| type noopStringer struct { | ||||
| 	s string | ||||
| } | ||||
|  | ||||
| func (s noopStringer) String() string { | ||||
| 	return s.s | ||||
| } | ||||
|  | ||||
| type Span struct { | ||||
| 	ctx        context.Context | ||||
| 	tracer     tracer.Tracer | ||||
| 	name       string | ||||
| 	statusMsg  string | ||||
| 	startTime  time.Time | ||||
| 	finishTime time.Time | ||||
| 	traceID    noopStringer | ||||
| 	spanID     noopStringer | ||||
| 	events     []*Event | ||||
| 	labels     []interface{} | ||||
| 	logs       []interface{} | ||||
| 	kind       tracer.SpanKind | ||||
| 	status     tracer.SpanStatus | ||||
| } | ||||
|  | ||||
| func (s *Span) Finish(_ ...tracer.SpanOption) { | ||||
| 	s.finishTime = time.Now() | ||||
| } | ||||
|  | ||||
| func (s *Span) Context() context.Context { | ||||
| 	return s.ctx | ||||
| } | ||||
|  | ||||
| func (s *Span) Tracer() tracer.Tracer { | ||||
| 	return s.tracer | ||||
| } | ||||
|  | ||||
| type Event struct { | ||||
| 	name   string | ||||
| 	labels []interface{} | ||||
| } | ||||
|  | ||||
| func (s *Span) AddEvent(name string, opts ...tracer.EventOption) { | ||||
| 	options := tracer.NewEventOptions(opts...) | ||||
| 	s.events = append(s.events, &Event{name: name, labels: options.Labels}) | ||||
| } | ||||
|  | ||||
| func (s *Span) SetName(name string) { | ||||
| 	s.name = name | ||||
| } | ||||
|  | ||||
| func (s *Span) AddLogs(kv ...interface{}) { | ||||
| 	s.logs = append(s.logs, kv...) | ||||
| } | ||||
|  | ||||
| func (s *Span) AddLabels(kv ...interface{}) { | ||||
| 	s.labels = append(s.labels, kv...) | ||||
| } | ||||
|  | ||||
| func (s *Span) Kind() tracer.SpanKind { | ||||
| 	return s.kind | ||||
| } | ||||
|  | ||||
| func (s *Span) TraceID() string { | ||||
| 	return s.traceID.String() | ||||
| } | ||||
|  | ||||
| func (s *Span) SpanID() string { | ||||
| 	return s.spanID.String() | ||||
| } | ||||
|  | ||||
| func (s *Span) Status() (tracer.SpanStatus, string) { | ||||
| 	return s.status, s.statusMsg | ||||
| } | ||||
|  | ||||
| func (s *Span) SetStatus(st tracer.SpanStatus, msg string) { | ||||
| 	s.status = st | ||||
| 	s.statusMsg = msg | ||||
| } | ||||
|  | ||||
| // NewTracer returns new memory tracer | ||||
| func NewTracer(opts ...tracer.Option) *Tracer { | ||||
| 	return &Tracer{ | ||||
| 		opts: tracer.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										38
									
								
								tracer/memory/memory_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								tracer/memory/memory_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package memory | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"go.unistack.org/micro/v3/logger" | ||||
| 	"go.unistack.org/micro/v3/logger/slog" | ||||
| 	"go.unistack.org/micro/v3/tracer" | ||||
| ) | ||||
|  | ||||
| func TestLoggerWithTracer(t *testing.T) { | ||||
| 	ctx := context.TODO() | ||||
| 	buf := bytes.NewBuffer(nil) | ||||
| 	logger.DefaultLogger = slog.NewLogger(logger.WithOutput(buf)) | ||||
|  | ||||
| 	if err := logger.Init(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	var span tracer.Span | ||||
| 	tr := NewTracer() | ||||
| 	ctx, span = tr.Start(ctx, "test1") | ||||
|  | ||||
| 	logger.Error(ctx, "my test error", fmt.Errorf("error")) | ||||
|  | ||||
| 	if !strings.Contains(buf.String(), span.TraceID()) { | ||||
| 		t.Fatalf("log does not contains trace id: %s", buf.Bytes()) | ||||
| 	} | ||||
|  | ||||
| 	_, _ = tr.Start(ctx, "test2") | ||||
|  | ||||
| 	for _, s := range tr.Spans() { | ||||
| 		_ = s | ||||
| 	} | ||||
| } | ||||
| @@ -2,6 +2,8 @@ package tracer | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"go.unistack.org/micro/v3/util/id" | ||||
| ) | ||||
|  | ||||
| var _ Tracer = (*noopTracer)(nil) | ||||
| @@ -24,6 +26,8 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) | ||||
| 		labels: options.Labels, | ||||
| 		kind:   options.Kind, | ||||
| 	} | ||||
| 	span.spanID.s, _ = id.New() | ||||
| 	span.traceID.s, _ = id.New() | ||||
| 	if span.ctx == nil { | ||||
| 		span.ctx = context.Background() | ||||
| 	} | ||||
| @@ -31,6 +35,14 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) | ||||
| 	return NewSpanContext(ctx, span), span | ||||
| } | ||||
|  | ||||
| type noopStringer struct { | ||||
| 	s string | ||||
| } | ||||
|  | ||||
| func (s noopStringer) String() string { | ||||
| 	return s.s | ||||
| } | ||||
|  | ||||
| func (t *noopTracer) Flush(ctx context.Context) error { | ||||
| 	return nil | ||||
| } | ||||
| @@ -56,6 +68,8 @@ type noopSpan struct { | ||||
| 	tracer    Tracer | ||||
| 	name      string | ||||
| 	statusMsg string | ||||
| 	traceID   noopStringer | ||||
| 	spanID    noopStringer | ||||
| 	events    []*noopEvent | ||||
| 	labels    []interface{} | ||||
| 	logs      []interface{} | ||||
| @@ -63,7 +77,15 @@ type noopSpan struct { | ||||
| 	status    SpanStatus | ||||
| } | ||||
|  | ||||
| func (s *noopSpan) Finish(opts ...SpanOption) { | ||||
| func (s *noopSpan) TraceID() string { | ||||
| 	return s.traceID.String() | ||||
| } | ||||
|  | ||||
| func (s *noopSpan) SpanID() string { | ||||
| 	return s.spanID.String() | ||||
| } | ||||
|  | ||||
| func (s *noopSpan) Finish(_ ...SpanOption) { | ||||
| } | ||||
|  | ||||
| func (s *noopSpan) Context() context.Context { | ||||
|   | ||||
| @@ -5,11 +5,33 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
|  | ||||
| 	"go.unistack.org/micro/v3/logger" | ||||
| ) | ||||
|  | ||||
| // DefaultTracer is the global default tracer | ||||
| var DefaultTracer = NewTracer() | ||||
|  | ||||
| var ( | ||||
| 	// TraceIDKey is the key used for the trace id in the log call | ||||
| 	TraceIDKey = "trace-id" | ||||
| 	// SpanIDKey is the key used for the span id in the log call | ||||
| 	SpanIDKey = "span-id" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, | ||||
| 		func(ctx context.Context) []interface{} { | ||||
| 			if span, ok := SpanFromContext(ctx); ok { | ||||
| 				return []interface{}{ | ||||
| 					TraceIDKey, span.TraceID(), | ||||
| 					SpanIDKey, span.SpanID(), | ||||
| 				} | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| // Tracer is an interface for distributed tracing | ||||
| type Tracer interface { | ||||
| 	// Name return tracer name | ||||
| @@ -43,6 +65,10 @@ type Span interface { | ||||
| 	AddLogs(kv ...interface{}) | ||||
| 	// Kind returns span kind | ||||
| 	Kind() SpanKind | ||||
| 	// TraceID returns trace id | ||||
| 	TraceID() string | ||||
| 	// SpanID returns span id | ||||
| 	SpanID() string | ||||
| } | ||||
|  | ||||
| // sort labels alphabeticaly by label name | ||||
|   | ||||
		Reference in New Issue
	
	Block a user