Skip to content

Commit 678c3e6

Browse files
authored
🐛 [logs] resolve problem with the logger source being set for a logr.Logger (#479)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description - avoid infinite logger creation loop ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent cb46dde commit 678c3e6

File tree

3 files changed

+158
-14
lines changed

3 files changed

+158
-14
lines changed

changes/20240627132421.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:bug: `[logs]` resolve problem with the logger source being set for a logr.Logger

utils/logs/logr_logger.go

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,29 @@ package logs
77
import (
88
"context"
99
"fmt"
10+
"strings"
11+
"sync"
1012

1113
"github.com/go-logr/logr"
1214
"github.com/go-logr/stdr"
15+
"go.uber.org/atomic"
1316

17+
"github.com/ARM-software/golang-utils/utils/collection"
1418
"github.com/ARM-software/golang-utils/utils/commonerrors"
1519
"github.com/ARM-software/golang-utils/utils/reflection"
1620
)
1721

1822
const (
19-
KeyLogSource = "source"
20-
KeyLoggerSource = "logger-source"
23+
KeyLogSource = "source"
24+
KeyLoggerSource = "logger-source"
25+
keyloggerSources = "logger-sources"
2126
)
2227

2328
type logrLogger struct {
2429
logger logr.Logger
2530
closeFunc func() error
31+
source *atomic.String
32+
logSource *atomic.String
2633
}
2734

2835
func (l *logrLogger) Close() error {
@@ -36,18 +43,28 @@ func (l *logrLogger) Check() error {
3643
return nil
3744
}
3845

46+
// SetLogSource will not overwrite the source if already defined
3947
func (l *logrLogger) SetLogSource(source string) error {
4048
if reflection.IsEmpty(source) {
4149
return commonerrors.ErrNoLogSource
4250
}
51+
if strings.EqualFold(source, l.logSource.Load()) {
52+
return nil
53+
}
54+
l.logSource.Store(source)
4355
l.logger = l.logger.WithValues(KeyLogSource, source)
4456
return nil
4557
}
4658

59+
// SetLoggerSource will not overwrite the source if already defined
4760
func (l *logrLogger) SetLoggerSource(source string) error {
4861
if reflection.IsEmpty(source) {
4962
return commonerrors.ErrNoLoggerSource
5063
}
64+
if strings.EqualFold(source, l.source.Load()) {
65+
return nil
66+
}
67+
l.source.Store(source)
5168
l.logger = l.logger.WithName(source).WithValues(KeyLoggerSource, source)
5269
return nil
5370
}
@@ -76,7 +93,12 @@ func NewLogrLogger(logrImpl logr.Logger, loggerSource string) (Loggers, error) {
7693

7794
// NewLogrLoggerWithClose creates loggers based on a logr implementation (https://github.com/go-logr/logr)
7895
func NewLogrLoggerWithClose(logrImpl logr.Logger, loggerSource string, closeFunc func() error) (loggers Loggers, err error) {
79-
loggers = &logrLogger{logger: logrImpl, closeFunc: closeFunc}
96+
loggers = &logrLogger{
97+
logger: logrImpl,
98+
closeFunc: closeFunc,
99+
source: atomic.NewString(""),
100+
logSource: atomic.NewString(""),
101+
}
80102
err = loggers.SetLoggerSource(loggerSource)
81103
return
82104
}
@@ -131,13 +153,13 @@ func (s *plainLoggersSinkAdapter) Error(err error, msg string, keysAndValues ...
131153
}
132154
}
133155

134-
func (s *plainLoggersSinkAdapter) WithValues(keysAndValues ...any) logr.LogSink {
156+
func (s *plainLoggersSinkAdapter) WithValues(_ ...any) logr.LogSink {
135157
return &plainLoggersSinkAdapter{underlying: s.underlying}
136158
}
137159

138160
func (s *plainLoggersSinkAdapter) WithName(name string) logr.LogSink {
139161
if s.underlying != nil {
140-
_ = s.underlying.SetLogSource(name)
162+
_ = s.underlying.SetLoggerSource(name)
141163
}
142164
return &plainLoggersSinkAdapter{underlying: s.underlying}
143165
}
@@ -151,6 +173,7 @@ func NewPlainLoggersSink(logger Loggers) logr.LogSink {
151173
type loggersLogSinkAdapter struct {
152174
stdOut logr.LogSink
153175
stdErr logr.LogSink
176+
values sync.Map
154177
underlying Loggers
155178
}
156179

@@ -172,23 +195,106 @@ func (l *loggersLogSinkAdapter) Error(err error, msg string, keysAndValues ...an
172195
l.stdErr.Error(err, msg, keysAndValues...)
173196
}
174197

198+
// WithValues will not overwrite already defined values.
175199
func (l *loggersLogSinkAdapter) WithValues(keysAndValues ...any) logr.LogSink {
176-
return &loggersLogSinkAdapter{
177-
stdOut: l.stdOut.WithValues(keysAndValues...),
178-
stdErr: l.stdErr.WithValues(keysAndValues...),
200+
sink := &loggersLogSinkAdapter{
201+
stdOut: l.stdOut,
202+
stdErr: l.stdErr,
203+
values: sync.Map{},
179204
underlying: l.underlying,
180205
}
206+
l.transferValues(sink)
207+
sink.withValues(keysAndValues...)
208+
return sink
181209
}
182210

183-
func (l *loggersLogSinkAdapter) WithName(name string) logr.LogSink {
211+
func (l *loggersLogSinkAdapter) transferValues(sink *loggersLogSinkAdapter) {
212+
if sink == nil {
213+
return
214+
}
215+
l.values.Range(func(k, v any) bool {
216+
return sink.recordValue(k, v)
217+
})
218+
}
219+
220+
func (l *loggersLogSinkAdapter) withValues(keysAndValues ...any) {
221+
if len(keysAndValues)%2 != 0 {
222+
return
223+
}
224+
for i := 0; i < len(keysAndValues); i += 2 {
225+
k := keysAndValues[i]
226+
v := keysAndValues[i+1]
227+
_, _ = l.values.LoadOrStore(k, v)
228+
}
229+
}
230+
231+
func (l *loggersLogSinkAdapter) recordValue(k, v any) bool {
232+
l.values.Store(k, v)
233+
return true
234+
}
235+
236+
func (l *loggersLogSinkAdapter) recordName(name string) {
237+
if reflection.IsEmpty(name) {
238+
return
239+
}
184240
if l.underlying != nil {
185-
_ = l.underlying.SetLogSource(name)
241+
_ = l.underlying.SetLoggerSource(name)
186242
}
187-
return &loggersLogSinkAdapter{
188-
stdOut: l.stdOut.WithName(name),
189-
stdErr: l.stdErr.WithName(name),
190-
underlying: l.underlying,
243+
if l.hasName(name) {
244+
return
245+
}
246+
namesAny, ok := l.values.Load(keyloggerSources)
247+
var names []string
248+
if ok {
249+
namesC, ok := namesAny.([]string)
250+
if !ok {
251+
return
252+
}
253+
names = namesC
254+
}
255+
names = append(names, name)
256+
_ = l.recordValue(keyloggerSources, names)
257+
}
258+
259+
func (l *loggersLogSinkAdapter) hasName(name string) (found bool) {
260+
if reflection.IsEmpty(name) {
261+
return
262+
}
263+
namesAny, ok := l.values.Load(keyloggerSources)
264+
if !ok {
265+
return
266+
}
267+
names, ok := namesAny.([]string)
268+
if !ok {
269+
return
191270
}
271+
_, found = collection.FindInSlice(false, names, name)
272+
return
273+
}
274+
275+
func (l *loggersLogSinkAdapter) WithName(name string) logr.LogSink {
276+
setName := !l.hasName(name)
277+
var sink *loggersLogSinkAdapter
278+
if setName {
279+
sink = &loggersLogSinkAdapter{
280+
stdOut: l.stdOut.WithName(name),
281+
stdErr: l.stdErr.WithName(name),
282+
values: sync.Map{},
283+
underlying: l.underlying,
284+
}
285+
} else {
286+
sink = &loggersLogSinkAdapter{
287+
stdOut: l.stdOut,
288+
stdErr: l.stdErr,
289+
values: sync.Map{},
290+
underlying: l.underlying,
291+
}
292+
}
293+
294+
l.transferValues(sink)
295+
sink.recordName(name)
296+
297+
return sink
192298
}
193299

194300
func NewLoggersLogSink(logger Loggers) logr.LogSink {

utils/logs/logr_logger_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package logs
66

77
import (
88
"context"
9+
"fmt"
10+
"strings"
911
"testing"
1012

1113
"github.com/go-faker/faker/v4"
@@ -36,6 +38,41 @@ func TestLogrLoggerConversion(t *testing.T) {
3638
converted.Info(faker.Sentence(), faker.Word(), faker.Name())
3739
}
3840

41+
func TestLogrFromLoggersWithMultipleName(t *testing.T) {
42+
loggerSource := "src-" + faker.Name()
43+
strLogger, err := NewStringLogger(loggerSource)
44+
require.NoError(t, err)
45+
converted := NewLogrLoggerFromLoggers(strLogger)
46+
converted.WithName(loggerSource).WithName(faker.Name()).WithName(faker.Name()).WithName(faker.Name()).WithName(faker.Name()).WithName(faker.Name()).WithName(loggerSource).WithName(faker.Name()).Error(commonerrors.ErrUnexpected, faker.Sentence())
47+
assert.Contains(t, strLogger.GetLogContent(), loggerSource)
48+
assert.Equal(t, 2, strings.Count(strLogger.GetLogContent(), loggerSource))
49+
converted.WithName(loggerSource).Info(faker.Sentence(), faker.Word(), faker.Name())
50+
assert.Equal(t, 4, strings.Count(strLogger.GetLogContent(), loggerSource))
51+
fmt.Println(strLogger.GetLogContent())
52+
}
53+
54+
func TestLoggersFromLoggerWithMultipleSource(t *testing.T) {
55+
loggerSource := "src-" + faker.Name()
56+
strLogger, err := NewStringLogger(loggerSource)
57+
require.NoError(t, err)
58+
logrl := NewLogrLoggerFromLoggers(strLogger)
59+
logger, err := NewLogrLogger(logrl, loggerSource)
60+
require.NoError(t, err)
61+
require.NoError(t, logger.SetLogSource("logsrc-"+faker.Name()))
62+
require.NoError(t, logger.SetLoggerSource(loggerSource))
63+
require.NoError(t, logger.SetLoggerSource(loggerSource))
64+
require.NoError(t, logger.SetLoggerSource(faker.Name()))
65+
require.NoError(t, logger.SetLoggerSource(loggerSource))
66+
logger.Log(faker.Sentence())
67+
assert.Contains(t, strLogger.GetLogContent(), loggerSource)
68+
assert.Equal(t, 2, strings.Count(strLogger.GetLogContent(), loggerSource))
69+
require.NoError(t, logger.SetLoggerSource(loggerSource))
70+
require.NoError(t, logger.SetLoggerSource(faker.Name()))
71+
logger.Log(faker.Sentence())
72+
assert.Contains(t, strLogger.GetLogContent(), loggerSource)
73+
assert.Equal(t, 4, strings.Count(strLogger.GetLogContent(), loggerSource))
74+
}
75+
3976
func TestLogrLoggerConversionPlain(t *testing.T) {
4077
loggers, err := NewPipeLogger()
4178
require.NoError(t, err)

0 commit comments

Comments
 (0)