mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-01 14:47:38 +00:00
198 lines
4.8 KiB
Go
198 lines
4.8 KiB
Go
// Copyright 2024 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package promslog defines standardised ways to initialize the Go standard
|
|
// library's log/slog logger.
|
|
// It should typically only ever be imported by main packages.
|
|
|
|
package promslog
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type LogStyle string
|
|
|
|
const (
|
|
SlogStyle LogStyle = "slog"
|
|
GoKitStyle LogStyle = "go-kit"
|
|
)
|
|
|
|
var (
|
|
LevelFlagOptions = []string{"debug", "info", "warn", "error"}
|
|
FormatFlagOptions = []string{"logfmt", "json"}
|
|
|
|
callerAddFunc = false
|
|
defaultWriter = os.Stderr
|
|
goKitStyleReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
|
|
key := a.Key
|
|
switch key {
|
|
case slog.TimeKey:
|
|
a.Key = "ts"
|
|
|
|
// This timestamp format differs from RFC3339Nano by using .000 instead
|
|
// of .999999999 which changes the timestamp from 9 variable to 3 fixed
|
|
// decimals (.130 instead of .130987456).
|
|
t := a.Value.Time()
|
|
a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00"))
|
|
case slog.SourceKey:
|
|
a.Key = "caller"
|
|
src, _ := a.Value.Any().(*slog.Source)
|
|
|
|
switch callerAddFunc {
|
|
case true:
|
|
a.Value = slog.StringValue(filepath.Base(src.File) + "(" + filepath.Base(src.Function) + "):" + strconv.Itoa(src.Line))
|
|
default:
|
|
a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
|
|
}
|
|
case slog.LevelKey:
|
|
a.Value = slog.StringValue(strings.ToLower(a.Value.String()))
|
|
default:
|
|
}
|
|
|
|
return a
|
|
}
|
|
truncateSourceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
|
|
if a.Key != slog.SourceKey {
|
|
return a
|
|
}
|
|
|
|
if src, ok := a.Value.Any().(*slog.Source); ok {
|
|
a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
|
|
}
|
|
|
|
return a
|
|
}
|
|
)
|
|
|
|
// AllowedLevel is a settable identifier for the minimum level a log entry
|
|
// must be have.
|
|
type AllowedLevel struct {
|
|
s string
|
|
lvl *slog.LevelVar
|
|
}
|
|
|
|
func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var s string
|
|
type plain string
|
|
if err := unmarshal((*plain)(&s)); err != nil {
|
|
return err
|
|
}
|
|
if s == "" {
|
|
return nil
|
|
}
|
|
lo := &AllowedLevel{}
|
|
if err := lo.Set(s); err != nil {
|
|
return err
|
|
}
|
|
*l = *lo
|
|
return nil
|
|
}
|
|
|
|
func (l *AllowedLevel) String() string {
|
|
return l.s
|
|
}
|
|
|
|
// Set updates the value of the allowed level.
|
|
func (l *AllowedLevel) Set(s string) error {
|
|
if l.lvl == nil {
|
|
l.lvl = &slog.LevelVar{}
|
|
}
|
|
|
|
switch s {
|
|
case "debug":
|
|
l.lvl.Set(slog.LevelDebug)
|
|
callerAddFunc = true
|
|
case "info":
|
|
l.lvl.Set(slog.LevelInfo)
|
|
callerAddFunc = false
|
|
case "warn":
|
|
l.lvl.Set(slog.LevelWarn)
|
|
callerAddFunc = false
|
|
case "error":
|
|
l.lvl.Set(slog.LevelError)
|
|
callerAddFunc = false
|
|
default:
|
|
return fmt.Errorf("unrecognized log level %s", s)
|
|
}
|
|
l.s = s
|
|
return nil
|
|
}
|
|
|
|
// AllowedFormat is a settable identifier for the output format that the logger can have.
|
|
type AllowedFormat struct {
|
|
s string
|
|
}
|
|
|
|
func (f *AllowedFormat) String() string {
|
|
return f.s
|
|
}
|
|
|
|
// Set updates the value of the allowed format.
|
|
func (f *AllowedFormat) Set(s string) error {
|
|
switch s {
|
|
case "logfmt", "json":
|
|
f.s = s
|
|
default:
|
|
return fmt.Errorf("unrecognized log format %s", s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Config is a struct containing configurable settings for the logger
|
|
type Config struct {
|
|
Level *AllowedLevel
|
|
Format *AllowedFormat
|
|
Style LogStyle
|
|
Writer io.Writer
|
|
}
|
|
|
|
// New returns a new slog.Logger. Each logged line will be annotated
|
|
// with a timestamp. The output always goes to stderr.
|
|
func New(config *Config) *slog.Logger {
|
|
if config.Level == nil {
|
|
config.Level = &AllowedLevel{}
|
|
_ = config.Level.Set("info")
|
|
}
|
|
|
|
if config.Writer == nil {
|
|
config.Writer = defaultWriter
|
|
}
|
|
|
|
logHandlerOpts := &slog.HandlerOptions{
|
|
Level: config.Level.lvl,
|
|
AddSource: true,
|
|
ReplaceAttr: truncateSourceAttrFunc,
|
|
}
|
|
|
|
if config.Style == GoKitStyle {
|
|
logHandlerOpts.ReplaceAttr = goKitStyleReplaceAttrFunc
|
|
}
|
|
|
|
if config.Format != nil && config.Format.s == "json" {
|
|
return slog.New(slog.NewJSONHandler(config.Writer, logHandlerOpts))
|
|
}
|
|
return slog.New(slog.NewTextHandler(config.Writer, logHandlerOpts))
|
|
}
|
|
|
|
// NewNopLogger is a convenience function to return an slog.Logger that writes
|
|
// to io.Discard.
|
|
func NewNopLogger() *slog.Logger {
|
|
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
}
|