mirror of
https://github.com/hanzoai/docdb.git
synced 2026-10-30 18:39:39 +00:00
142 lines
3.1 KiB
Go
142 lines
3.1 KiB
Go
// Copyright 2021 Hanzo AI Inc.
|
|
//
|
|
// 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 logging
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"maps"
|
|
"runtime"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/tracelog"
|
|
|
|
"github.com/hanzoai/docdb/internal/util/devbuild"
|
|
"github.com/hanzoai/docdb/internal/util/must"
|
|
)
|
|
|
|
// pgxLogLevels maps pgx log levels to slog levels.
|
|
var pgxLogLevels = map[tracelog.LogLevel]slog.Level{
|
|
// pgx's info level is used for queries, but we want to hide them by default
|
|
tracelog.LogLevelTrace: slog.LevelDebug - 2,
|
|
tracelog.LogLevelDebug: slog.LevelDebug,
|
|
tracelog.LogLevelInfo: slog.LevelDebug + 2,
|
|
|
|
tracelog.LogLevelWarn: slog.LevelWarn,
|
|
tracelog.LogLevelError: slog.LevelError,
|
|
}
|
|
|
|
// pgxLogger is a pgx's [tracelog.Logger] implementation that uses slog
|
|
// with correct source code location.
|
|
type pgxLogger struct {
|
|
l *slog.Logger
|
|
}
|
|
|
|
// NewPgxLogger creates a new [tracelog.Logger] that uses given [slog.Logger].
|
|
func NewPgxLogger(l *slog.Logger) tracelog.Logger {
|
|
return &pgxLogger{l: l}
|
|
}
|
|
|
|
// Log implements [tracelog.Logger].
|
|
func (sl *pgxLogger) Log(ctx context.Context, level tracelog.LogLevel, msg string, data map[string]any) {
|
|
now := time.Now()
|
|
|
|
l, ok := pgxLogLevels[level]
|
|
if !ok {
|
|
sl.l.LogAttrs(ctx, LevelDPanic, "Invalid pgx log level for: "+msg, slog.Int("level", int(level)))
|
|
l = slog.LevelError
|
|
}
|
|
|
|
if !sl.l.Enabled(ctx, l) {
|
|
return
|
|
}
|
|
|
|
// We can't call runtime.Caller(s) with some fixed skip value and then inspect a single frame
|
|
// because call Pgx calls Log method from different stack depths.
|
|
|
|
var pc uintptr
|
|
callers := make([]uintptr, 10)
|
|
|
|
if n := runtime.Callers(2, callers); n != 0 {
|
|
frames := runtime.CallersFrames(callers[:n])
|
|
|
|
for {
|
|
frame, more := frames.Next()
|
|
|
|
// skip both pgx and puddle
|
|
if !strings.Contains(frame.File, "github.com/jackc/") {
|
|
pc = frame.PC
|
|
break
|
|
}
|
|
|
|
if !more {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
record := slog.NewRecord(now, l, msg, pc)
|
|
|
|
dataKeys := slices.Sorted(maps.Keys(data))
|
|
|
|
msgAttrs := make([]slog.Attr, len(data))
|
|
|
|
for i, k := range dataKeys {
|
|
v := data[k]
|
|
|
|
var attr slog.Attr
|
|
|
|
switch v := v.(type) {
|
|
case []any:
|
|
attrs := make([]slog.Attr, len(v))
|
|
for i, v := range v {
|
|
attrs[i] = slog.Attr{
|
|
Key: strconv.Itoa(i),
|
|
Value: slog.AnyValue(v),
|
|
}
|
|
}
|
|
|
|
attr = slog.Attr{
|
|
Key: k,
|
|
Value: slog.GroupValue(attrs...),
|
|
}
|
|
|
|
case error:
|
|
attr = Error(v)
|
|
|
|
default:
|
|
attr = slog.Any(k, v)
|
|
}
|
|
|
|
msgAttrs[i] = attr
|
|
}
|
|
|
|
record.AddAttrs(msgAttrs...)
|
|
|
|
err := sl.l.Handler().Handle(ctx, record)
|
|
|
|
if devbuild.Enabled {
|
|
must.NoError(err)
|
|
}
|
|
}
|
|
|
|
// check interfaces
|
|
var (
|
|
_ tracelog.Logger = (*pgxLogger)(nil)
|
|
)
|