mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-15 00:31:30 -05:00
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.23.0 to 1.23.2. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.23.0...v1.23.2) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-version: 1.23.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
290 lines
7.8 KiB
Go
290 lines
7.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"
|
|
"time"
|
|
)
|
|
|
|
// LogStyle represents the common logging formats in the Prometheus ecosystem.
|
|
type LogStyle string
|
|
|
|
const (
|
|
SlogStyle LogStyle = "slog"
|
|
GoKitStyle LogStyle = "go-kit"
|
|
|
|
reservedKeyPrefix = "logged_"
|
|
)
|
|
|
|
var (
|
|
// LevelFlagOptions represents allowed logging levels.
|
|
LevelFlagOptions = []string{"debug", "info", "warn", "error"}
|
|
// FormatFlagOptions represents allowed formats.
|
|
FormatFlagOptions = []string{"logfmt", "json"}
|
|
|
|
defaultWriter = os.Stderr
|
|
)
|
|
|
|
// Level controls a logging level, with an info default.
|
|
// It wraps slog.LevelVar with string-based level control.
|
|
// Level is safe to be used concurrently.
|
|
type Level struct {
|
|
lvl *slog.LevelVar
|
|
}
|
|
|
|
// NewLevel returns a new Level.
|
|
func NewLevel() *Level {
|
|
return &Level{
|
|
lvl: &slog.LevelVar{},
|
|
}
|
|
}
|
|
|
|
func (l *Level) 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
|
|
}
|
|
if err := l.Set(s); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Level returns the value of the logging level as an slog.Level.
|
|
func (l *Level) Level() slog.Level {
|
|
return l.lvl.Level()
|
|
}
|
|
|
|
// String returns the current level.
|
|
func (l *Level) String() string {
|
|
switch l.lvl.Level() {
|
|
case slog.LevelDebug:
|
|
return "debug"
|
|
case slog.LevelInfo:
|
|
return "info"
|
|
case slog.LevelWarn:
|
|
return "warn"
|
|
case slog.LevelError:
|
|
return "error"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// Set updates the logging level with the validation.
|
|
func (l *Level) Set(s string) error {
|
|
switch strings.ToLower(s) {
|
|
case "debug":
|
|
l.lvl.Set(slog.LevelDebug)
|
|
case "info":
|
|
l.lvl.Set(slog.LevelInfo)
|
|
case "warn":
|
|
l.lvl.Set(slog.LevelWarn)
|
|
case "error":
|
|
l.lvl.Set(slog.LevelError)
|
|
default:
|
|
return fmt.Errorf("unrecognized log level %s", s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Format controls a logging output format.
|
|
// Not concurrency-safe.
|
|
type Format struct {
|
|
s string
|
|
}
|
|
|
|
// NewFormat creates a new Format.
|
|
func NewFormat() *Format { return &Format{} }
|
|
|
|
func (f *Format) String() string {
|
|
return f.s
|
|
}
|
|
|
|
// Set updates the value of the allowed format.
|
|
func (f *Format) 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 *Level
|
|
Format *Format
|
|
Style LogStyle
|
|
Writer io.Writer
|
|
}
|
|
|
|
func newGoKitStyleReplaceAttrFunc(lvl *Level) func(groups []string, a slog.Attr) slog.Attr {
|
|
return func(_ []string, a slog.Attr) slog.Attr {
|
|
key := a.Key
|
|
switch key {
|
|
case slog.TimeKey, "ts":
|
|
if t, ok := a.Value.Any().(time.Time); ok {
|
|
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).
|
|
a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00"))
|
|
} else {
|
|
// If we can't cast the any from the value to a
|
|
// time.Time, it means the caller logged
|
|
// another attribute with a key of `ts`.
|
|
// Prevent duplicate keys (necessary for proper
|
|
// JSON) by renaming the key to `logged_ts`.
|
|
a.Key = reservedKeyPrefix + key
|
|
}
|
|
case slog.SourceKey, "caller":
|
|
if src, ok := a.Value.Any().(*slog.Source); ok {
|
|
a.Key = "caller"
|
|
switch lvl.String() {
|
|
case "debug":
|
|
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))
|
|
}
|
|
} else {
|
|
// If we can't cast the any from the value to
|
|
// an *slog.Source, it means the caller logged
|
|
// another attribute with a key of `caller`.
|
|
// Prevent duplicate keys (necessary for proper
|
|
// JSON) by renaming the key to
|
|
// `logged_caller`.
|
|
a.Key = reservedKeyPrefix + key
|
|
}
|
|
case slog.LevelKey:
|
|
if lvl, ok := a.Value.Any().(slog.Level); ok {
|
|
a.Value = slog.StringValue(strings.ToLower(lvl.String()))
|
|
} else {
|
|
// If we can't cast the any from the value to
|
|
// an slog.Level, it means the caller logged
|
|
// another attribute with a key of `level`.
|
|
// Prevent duplicate keys (necessary for proper
|
|
// JSON) by renaming the key to `logged_level`.
|
|
a.Key = reservedKeyPrefix + key
|
|
}
|
|
default:
|
|
}
|
|
|
|
// Ensure time.Duration values are _always_ formatted as a Go
|
|
// duration string (ie, "1d2h3m").
|
|
if v, ok := a.Value.Any().(time.Duration); ok {
|
|
a.Value = slog.StringValue(v.String())
|
|
}
|
|
|
|
return a
|
|
}
|
|
}
|
|
|
|
func defaultReplaceAttr(_ []string, a slog.Attr) slog.Attr {
|
|
key := a.Key
|
|
switch key {
|
|
case slog.TimeKey:
|
|
// Note that we do not change the timezone to UTC anymore.
|
|
if _, ok := a.Value.Any().(time.Time); !ok {
|
|
// If we can't cast the any from the value to a
|
|
// time.Time, it means the caller logged
|
|
// another attribute with a key of `time`.
|
|
// Prevent duplicate keys (necessary for proper
|
|
// JSON) by renaming the key to `logged_time`.
|
|
a.Key = reservedKeyPrefix + key
|
|
}
|
|
case slog.SourceKey:
|
|
if src, ok := a.Value.Any().(*slog.Source); ok {
|
|
a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
|
|
} else {
|
|
// If we can't cast the any from the value to
|
|
// an *slog.Source, it means the caller logged
|
|
// another attribute with a key of `source`.
|
|
// Prevent duplicate keys (necessary for proper
|
|
// JSON) by renaming the key to
|
|
// `logged_source`.
|
|
a.Key = reservedKeyPrefix + key
|
|
}
|
|
case slog.LevelKey:
|
|
if _, ok := a.Value.Any().(slog.Level); !ok {
|
|
// If we can't cast the any from the value to
|
|
// an slog.Level, it means the caller logged
|
|
// another attribute with a key of `level`.
|
|
// Prevent duplicate keys (necessary for proper
|
|
// JSON) by renaming the key to
|
|
// `logged_level`.
|
|
a.Key = reservedKeyPrefix + key
|
|
}
|
|
default:
|
|
}
|
|
|
|
// Ensure time.Duration values are _always_ formatted as a Go duration
|
|
// string (ie, "1d2h3m").
|
|
if v, ok := a.Value.Any().(time.Duration); ok {
|
|
a.Value = slog.StringValue(v.String())
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
// 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 = NewLevel()
|
|
}
|
|
|
|
if config.Writer == nil {
|
|
config.Writer = defaultWriter
|
|
}
|
|
|
|
logHandlerOpts := &slog.HandlerOptions{
|
|
Level: config.Level.lvl,
|
|
AddSource: true,
|
|
ReplaceAttr: defaultReplaceAttr,
|
|
}
|
|
|
|
if config.Style == GoKitStyle {
|
|
logHandlerOpts.ReplaceAttr = newGoKitStyleReplaceAttrFunc(config.Level)
|
|
}
|
|
|
|
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 New(&Config{Writer: io.Discard})
|
|
}
|