File: //proc/thread-self/root/opt/go/pkg/mod/github.com/prometheus/
[email protected]/promslog/slog_test.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
import (
"bytes"
"context"
"fmt"
"log/slog"
"regexp"
"strings"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
var (
slogStyleLogRegexp = regexp.MustCompile(`(?P<TimeKey>time)=.*level=(?P<LevelValue>WARN|INFO|ERROR|DEBUG).*(?P<SourceKey>source)=.*`)
goKitStyleLogRegexp = regexp.MustCompile(`(?P<TimeKey>ts)=.*level=(?P<LevelValue>warn|info|error|debug).*(?P<SourceKey>caller)=.*`)
)
// Make sure creating and using a logger with an empty configuration doesn't
// result in a panic.
func TestDefaultConfig(t *testing.T) {
require.NotPanics(t, func() {
logger := New(&Config{})
logger.Info("empty config `Info()` test", "hello", "world")
logger.Log(context.Background(), slog.LevelInfo, "empty config `Log()` test", "hello", "world")
logger.LogAttrs(context.Background(), slog.LevelInfo, "empty config `LogAttrs()` test", slog.String("hello", "world"))
})
}
func TestUnmarshallLevel(t *testing.T) {
l := &AllowedLevel{}
err := yaml.Unmarshal([]byte(`debug`), l)
if err != nil {
t.Error(err)
}
if l.s != "debug" {
t.Errorf("expected %s, got %s", "debug", l.s)
}
}
func TestUnmarshallEmptyLevel(t *testing.T) {
l := &AllowedLevel{}
err := yaml.Unmarshal([]byte(``), l)
if err != nil {
t.Error(err)
}
if l.s != "" {
t.Errorf("expected empty level, got %s", l.s)
}
}
func TestUnmarshallBadLevel(t *testing.T) {
l := &AllowedLevel{}
err := yaml.Unmarshal([]byte(`debugg`), l)
if err == nil {
t.Error("expected error")
}
expErr := `unrecognized log level debugg`
if err.Error() != expErr {
t.Errorf("expected error %s, got %s", expErr, err.Error())
}
if l.s != "" {
t.Errorf("expected empty level, got %s", l.s)
}
}
func getLogEntryLevelCounts(s string, re *regexp.Regexp) map[string]int {
counters := make(map[string]int)
lines := strings.Split(s, "\n")
for _, line := range lines {
matches := re.FindStringSubmatch(line)
if len(matches) > 1 {
levelIndex := re.SubexpIndex("LevelValue")
counters[strings.ToLower(matches[levelIndex])]++
}
}
return counters
}
func TestDynamicLevels(t *testing.T) {
var buf bytes.Buffer
wantedLevelCounts := map[string]int{"info": 1, "debug": 1}
tests := map[string]struct {
logStyle LogStyle
logStyleRegexp *regexp.Regexp
wantedLevelCount map[string]int
}{
"slog_log_style": {logStyle: SlogStyle, logStyleRegexp: slogStyleLogRegexp, wantedLevelCount: wantedLevelCounts},
"go-kit_log_style": {logStyle: GoKitStyle, logStyleRegexp: goKitStyleLogRegexp, wantedLevelCount: wantedLevelCounts},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
buf.Reset() // Ensure buf is reset prior to tests
config := &Config{Writer: &buf, Style: tc.logStyle}
logger := New(config)
// Test that log level can be adjusted on-the-fly to debug and that a
// log entry can be written to the file.
err := config.Level.Set("debug")
require.NoError(t, err)
logger.Info("info", "hello", "world")
logger.Debug("debug", "hello", "world")
counts := getLogEntryLevelCounts(buf.String(), tc.logStyleRegexp)
require.Equalf(t, tc.wantedLevelCount["info"], counts["info"], "info log successfully detected")
require.Equalf(t, tc.wantedLevelCount["debug"], counts["debug"], "debug log successfully detected")
// Print logs for humans to see, if needed.
fmt.Println(buf.String())
buf.Reset()
// Test that log level can be adjusted on-the-fly to info and that a
// subsequent call to write a debug level log is _not_ written to the
// file.
err = config.Level.Set("info")
require.NoError(t, err)
logger.Info("info", "hello", "world")
logger.Debug("debug", "hello", "world")
counts = getLogEntryLevelCounts(buf.String(), tc.logStyleRegexp)
require.Equalf(t, tc.wantedLevelCount["info"], counts["info"], "info log successfully detected")
require.NotEqualf(t, tc.wantedLevelCount["debug"], counts["debug"], "extra debug log detected")
// Print logs for humans to see, if needed.
fmt.Println(buf.String())
buf.Reset()
})
}
}
func TestTruncateSourceFileName_DefaultStyle(t *testing.T) {
var buf bytes.Buffer
config := &Config{
Writer: &buf,
}
logger := New(config)
logger.Info("test message")
output := buf.String()
if !strings.Contains(output, "source=slog_test.go:") {
t.Errorf("Expected source file name to be truncated to basename, got: %s", output)
}
if strings.Contains(output, "/") {
t.Errorf("Expected no directory separators in source file name, got: %s", output)
}
}
func TestTruncateSourceFileName_GoKitStyle(t *testing.T) {
var buf bytes.Buffer
config := &Config{
Writer: &buf,
Style: GoKitStyle,
}
logger := New(config)
logger.Info("test message")
output := buf.String()
// In GoKitStyle, the source key is "caller".
if !strings.Contains(output, "caller=slog_test.go:") {
t.Errorf("Expected caller to contain basename of source file, got: %s", output)
}
if strings.Contains(output, "/") {
t.Errorf("Expected no directory separators in caller, got: %s", output)
}
}