diff --git a/go.mod b/go.mod index a517a973e8..3bb9048e7b 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/gin-contrib/gzip v1.0.1 github.com/gin-contrib/sessions v1.0.1 github.com/gin-gonic/gin v1.10.0 + github.com/go-logr/logr v1.4.1 github.com/go-playground/form/v4 v4.2.1 github.com/go-swagger/go-swagger v0.31.0 github.com/google/go-cmp v0.6.0 @@ -122,7 +123,6 @@ require ( github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect - github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect diff --git a/internal/log/log.go b/internal/log/log.go index 15c917757e..85b3eeff76 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -20,6 +20,7 @@ package log import ( "context" "fmt" + "github.com/go-logr/logr" "log/syslog" "os" "strings" @@ -313,3 +314,96 @@ func args(count int) string { return buf.String() } + +func toFields(keysAndValues ...any) []kv.Field { + fields := make([]kv.Field, 0, (len(keysAndValues)+1)/2) // Preallocate slice to half of the input length plus one for odd cases. + for i := 0; i < len(keysAndValues); i += 2 { + if i+1 < len(keysAndValues) { + fields = append(fields, kv.Field{ + K: fmt.Sprint(keysAndValues[i]), + V: keysAndValues[i+1], + }) + } else { + fields = append(fields, kv.Field{ + K: fmt.Sprint(keysAndValues[i]), + V: nil, + }) + } + } + return fields +} + +// int log level mapping from https://pkg.go.dev/go.opentelemetry.io/otel/internal/global#SetLogger +// "To see Warn messages use a logger with `l.V(1).Enabled() == true` +// To see Info messages use a logger with `l.V(4).Enabled() == true` +// To see Debug messages use a logger with `l.V(8).Enabled() == true`." +func otelLogLevelToGoLoggerLevel(lvl int) level.LEVEL { + switch lvl { + case 0: + return level.ERROR + case 1: + return level.WARN + case 4: + return level.INFO + case 8: + return level.DEBUG + default: + return level.INFO + } +} + +type LogrSink struct { + ctx context.Context + fields []kv.Field + name string +} + +// Ensure Logger implements logr.LogSink +var _ logr.LogSink = &LogrSink{} + +func (l LogrSink) Init(_ logr.RuntimeInfo) {} + +func (l LogrSink) Enabled(level int) bool { + return otelLogLevelToGoLoggerLevel(level) <= loglvl +} + +func (l LogrSink) Info(level int, msg string, keysAndValues ...any) { + fields := toFields(keysAndValues...) + logf(l.ctx, 5, otelLogLevelToGoLoggerLevel(level), fields, msg) +} + +func (l LogrSink) Error(_ error, msg string, keysAndValues ...any) { + fields := toFields(keysAndValues...) + logf(l.ctx, 5, level.ERROR, fields, msg) +} + +func (l LogrSink) WithValues(keysAndValues ...any) logr.LogSink { + fields := l.fields + fields = append(fields, toFields(keysAndValues...)...) + return &LogrSink{ + ctx: l.ctx, + fields: fields, + name: l.name, + } +} + +func (l LogrSink) WithName(name string) logr.LogSink { + newName := l.name + if newName != "" { + newName += "/" + } + newName += name + return &LogrSink{ + ctx: l.ctx, + fields: l.fields, + name: newName, + } +} + +func NewLogrSink() logr.LogSink { + return &LogrSink{} +} + +func NewLogrLogger() logr.Logger { + return logr.New(NewLogrSink()) +} diff --git a/internal/log/log_test.go b/internal/log/log_test.go new file mode 100644 index 0000000000..5e27812228 --- /dev/null +++ b/internal/log/log_test.go @@ -0,0 +1,69 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package log + +import ( + "testing" + + "codeberg.org/gruf/go-kv" + "github.com/stretchr/testify/assert" +) + +func TestToFields(t *testing.T) { + tests := []struct { + name string + keysAndValues []any + expectedFields []kv.Field + }{ + { + name: "Even number of elements", + keysAndValues: []any{"count", 2, "total_dropped", 0}, + expectedFields: []kv.Field{ + {K: "count", V: 2}, + {K: "total_dropped", V: 0}, + }, + }, + { + name: "Odd number of elements", + keysAndValues: []any{"count", 2, "whatever"}, + expectedFields: []kv.Field{ + {K: "count", V: 2}, + {K: "whatever", V: nil}, + }, + }, + { + name: "Empty input", + keysAndValues: []any{}, + expectedFields: []kv.Field{}, + }, + { + name: "Single element", + keysAndValues: []any{"single"}, + expectedFields: []kv.Field{ + {K: "single", V: nil}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualFields := toFields(tt.keysAndValues...) + assert.Equal(t, tt.expectedFields, actualFields) + }) + } +} diff --git a/internal/tracing/tracing.go b/internal/tracing/tracing.go index dd1e19be96..4af7241ecc 100644 --- a/internal/tracing/tracing.go +++ b/internal/tracing/tracing.go @@ -55,6 +55,9 @@ func Initialize() error { insecure := config.GetTracingInsecureTransport() + // Log internal OTEL logs + otel.SetLogger(log.NewLogrLogger()) + var tpo trace.TracerProviderOption switch config.GetTracingTransport() { case "grpc":