Skip to content

vendor: github.com/containerd/log v0.2.0 - #7347

Merged
vvoland merged 1 commit into
docker:masterfrom
thaJeztah:bump_containerd_logs
Oct 1, 2026
Merged

vvoland merged 1 commit into
docker:masterfrom
thaJeztah:bump_containerd_logs

Conversation

@thaJeztah

Copy link
Copy Markdown
Member

full diff: containerd/log@v0.1.0...v0.2.0

Summary

Release notes (optional)

A picture of a cute animal (not mandatory but encouraged)

full diff: containerd/log@v0.1.0...v0.2.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

key += "." + attr.Key
}
}
fields[key] = attr.Value.Any()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] appendAttr stores non-group attrs with empty keys under the group prefix instead of skipping them

When a non-group slog.Attr has an empty key (attr.Key == "") but a non-zero value (e.g. slog.Any("", someValue)), the function does not skip it. Instead, when len(groups) > 0, key is set to strings.Join(groups, ".") — the bare group prefix — and then fields[key] = attr.Value.Any() is executed unconditionally. This silently stores the value under the group name key.

The Go standard library's slog.TextHandler and slog.JSONHandler both skip non-group attrs with empty keys, per the log/slog package specification. This implementation deviates from that behaviour: it may clobber an earlier value stored at the group prefix key, or introduce an unexpected field in the log output.

The fix is to add an early return for the empty-key non-group case, before the assignment:

Suggested change
fields[key] = attr.Value.Any()
if key == "" {
return
}
fields[key] = attr.Value.Any()
Confidence Score
🟢 strong 100/100

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the empty key would make it so far here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe not; I'll have a look in logrus though; seems like an easy fix to include in a future release.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually; @docker-agent is wrong here; slog does explicitly allow empty keys, and ONLY ignore if an attribute is a zero-value; https://go.dev/src/log/slog/handler.go#L53

	// - If an Attr's key and value are both the zero value, ignore the Attr.
	//     This can be tested with attr.Equal(Attr{}).

It even has an Attr.isEmpty it uses to check for that; https://go.dev/src/log/slog/handler.go#L487

https://go.dev/src/log/slog/attr.go#L106

// isEmpty reports whether a has an empty key and a nil value.
// That can be written as Attr{} or Any("", nil).
func (a Attr) isEmpty() bool {
 	return a.Key == "" && a.Value.num == 0 && a.Value.any == nil
}

@thaJeztah
thaJeztah requested a review from vvoland October 1, 2026 20:27
@vvoland
vvoland merged commit 48988f5 into docker:master Oct 1, 2026
107 checks passed

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

var slogOnce sync.Once

func UseSlog() {
slogOnce.Do(func() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] slogOut package variable written without synchronization between UseSlog and SetFormat

slogOut is a plain io.Writer interface variable (not an atomic pointer). It is written inside slogOnce.Do(...) in UseSlog (line 39) and read without any synchronization in SetFormat (context.go line 186, if slogOut != nil { ... }).

sync.Once guarantees that the function passed to Do completes before Do itself returns in any goroutine — but it does not synchronize a write to slogOut with a concurrent non-Do read in a different function (SetFormat). If a goroutine calls UseSlog() while another goroutine concurrently calls SetFormat(), the race detector will flag the write at line 39 and the read at context.go:186 as a data race under Go's memory model.

The fix is to guard slogOut with an atomic or a mutex. For example, using atomic.Pointer[io.Writer]:

var slogOut atomic.Pointer[io.Writer]

Or protecting both the write and read with the same mutex already used elsewhere in the package.

In practice, UseSlog() is typically called once at startup before concurrent use begins, so the impact is low — but the race is real and the race detector will surface it.

Confidence Score
🟢 strong 97/100


func UseSlog() {
slogOnce.Do(func() {
L.Logger.SetNoLock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] SetNoLock() is called before hooks and formatter are configured, creating a concurrent-logging window without the mutex

UseSlog() calls L.Logger.SetNoLock() as its first action (line 37), then proceeds to call AddHook, SetFormatter, and SetOutput on the now-unlocked logger. Between line 37 and the completion of the remaining setup, any concurrent goroutine that logs will:

  1. Proceed without the logrus mutex (it has been disabled), and
  2. Fire hooks and formatters that have not yet been configured to the slog state — the slog hook may not be installed yet, and the old formatter may still be active.

logrus.SetNoLock() is documented for use when the caller takes responsibility for external synchronization, or when it is called before any concurrent use begins. Here, UseSlog() can be called at any point during the program lifetime (it is guarded only against repeated calls via slogOnce, not against concurrent logging).

The safer ordering would be to configure the hook, formatter, and output first while the mutex is still active, and then call SetNoLock() last — or to hold an external lock for the full setup sequence.

Confidence Score
🟡 moderate 67/100

@thaJeztah
thaJeztah deleted the bump_containerd_logs branch October 1, 2026 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants