Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syslog entry: "superfluous response.WriteHeader call" in Docker 25.x #47448

Open
jamescarppe opened this issue Feb 26, 2024 · 15 comments · May be fixed by #47796 or #47715
Open

Syslog entry: "superfluous response.WriteHeader call" in Docker 25.x #47448

jamescarppe opened this issue Feb 26, 2024 · 15 comments · May be fixed by #47796 or #47715
Labels
area/metrics/otel area/metrics kind/bug Bugs are bugs. The cause may or may not be known at triage time so debugging may be needed. status/0-triage version/25.0

Comments

@jamescarppe
Copy link

Description

When retrieving the logs for a container (via docker logs for example), a log entry is made in /var/log/syslog:

Feb 26 16:54:53 be-docker dockerd[410]: 2024/02/26 16:54:53 http: superfluous response.WriteHeader call from go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*respWriterWrapper).WriteHeader (wrap.go:98)

This appears to have been introduced alongside the OpenTelemetry addition in Docker 25.0.0 as this log message is not created on previous versions of Docker (for example 24.0.6).

Reproduce

  1. Install Docker 25.x
  2. Create a container (any will do)
  3. Monitor /var/log/syslog (via tail -f for example)
  4. While monitoring, run docker logs containername
  5. Note the entry in syslog

Expected behavior

No "superfluous response" message.

docker version

Client: Docker Engine - Community
 Version:           25.0.3
 API version:       1.44
 Go version:        go1.21.6
 Git commit:        4debf41
 Built:             Tue Feb  6 21:14:17 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          25.0.3
  API version:      1.44 (minimum version 1.24)
  Go version:       go1.21.6
  Git commit:       f417435
  Built:            Tue Feb  6 21:14:17 2024
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          1.6.28
  GitCommit:        ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

docker info

Client: Docker Engine - Community
 Version:    25.0.3
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.12.1
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.24.5
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 10
  Running: 7
  Paused: 0
  Stopped: 3
 Images: 33
 Server Version: 25.0.3
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: gelf
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive
 Runtimes: runc io.containerd.runc.v2
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc version: v1.1.12-0-g51d5e94
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: builtin
 Kernel Version: 5.4.0-171-generic
 Operating System: Ubuntu 20.04.6 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 1.925GiB
 Name: be-docker
 ID: HJDW:MIDK:VVF2:ZZC5:CCHA:6KOE:CCZU:TXJ3:2YUM:2KHI:NKJX:7ID3
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Username: jcarppe
 Experimental: true
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No swap limit support

Additional Info

While a single log line entry doesn't seem like much, when using a separate tool (such as Portainer) to view container logs that auto-refreshes, this can result in repeated syslog entries.

@rwillert
Copy link

rwillert commented Mar 1, 2024

docker compose stats will also create syslog messages

@LaXiS96
Copy link

LaXiS96 commented Mar 18, 2024

This line currently accounts for 8% of my total server syslog/journal logs (out of about 120k lines).
Please either suppress it if it's innocuous or fix the underlying problem, it's hard to look at system logs with this spam (journalctl does not support filters; inverse grepping works but breaks --follow for example).

@thaJeztah
Copy link
Member

Wonder if this is a bug in OTEL here; I see it keeps track whether it already wrote headers, but then proceeds calling w.ResponseWriter.WriteHeader(statusCode) to write headers anyway;

func (w *respWriterWrapper) WriteHeader(statusCode int) {
if !w.wroteHeader {
w.wroteHeader = true
w.statusCode = statusCode
}
w.ResponseWriter.WriteHeader(statusCode)
}

It looks like Write has a protection for that (it will only call WriteHeader if it wasn't written yet), but the same does not apply when WriteHeader is called directly (see above);

func (w *respWriterWrapper) Write(p []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
n, err := w.ResponseWriter.Write(p)

That said, the log itself seems to be coming from het/http in Go stdlib from either one of these two, and stdlib doesn't prevent it being called multiple times, so maybe in this case something hits the wrapper's Write() (writing the header), before existing code is executed and calling WriteHeader() 🤔;

@Shadok
Copy link

Shadok commented Apr 4, 2024

It would be nice to have a way to disable telemetry either.
I didn't find a way to do that.

@Ruppsn
Copy link

Ruppsn commented Apr 4, 2024

Hello,

we have the same problem with this message since Docker 25. I tried to update to 26 but no changes:

http: superfluous response.WriteHeader call from go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*respWriterWrapper).WriteHeader (wrap.go:98)

There are about 50000 Lines a day spamming the Syslog.

docker info

Client: Docker Engine - Community
 Version:    26.0.0
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.13.1
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.25.0
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 48
  Running: 26
  Paused: 0
  Stopped: 22
 Images: 36
 Server Version: 26.0.0
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: false
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: active
  NodeID: 63qhzfb68bifh1unoiwmp3b42
  Is Manager: true
  ClusterID: dwdlo1iwtmf1nojlzcjo4z620
  Managers: 3
  Nodes: 3
  Default Address Pool: 10.0.0.0/8  
  SubnetSize: 16
  Data Path Port: 4789
  Orchestration:
   Task History Retention Limit: 5
  Raft:
   Snapshot Interval: 10000
   Number of Old Snapshots to Retain: 0
   Heartbeat Tick: 1
   Election Tick: 10
  Dispatcher:
   Heartbeat Period: 30 seconds
  CA Configuration:
   Expiry Duration: 3 months
   Force Rotate: 0
  Autolock Managers: false
  Root Rotation In Progress: false
  Node Address: xxxx
  Manager Addresses:
   xxxxx:2377
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc version: v1.1.12-0-g51d5e94
 init version: de40ad0
 Security Options:
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 5.15.0-204.147.6.2.el9uek.x86_64
 Operating System: Oracle Linux Server 9.3
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 11.24GiB
 Name: xxxx
 ID: 24a66a42-19c5-4935-aa24-f50c19f4c381
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

docker version

Client: Docker Engine - Community
 Version:           26.0.0
 API version:       1.45
 Go version:        go1.21.8
 Git commit:        2ae903e
 Built:             Wed Mar 20 15:20:36 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          26.0.0
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.8
  Git commit:       8b79278
  Built:            Wed Mar 20 15:18:58 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.28
  GitCommit:        ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Additional Info
We are shipping our logs with a fluentbit container to a grafana/loki instance

@cpuguy83
Copy link
Member

cpuguy83 commented Apr 4, 2024

Telemetry is disabled by default.
Its only enabled if the OTLP env vars are set.
IIRC you should also be able to forcibly disable it with OTEL_SDK_DISABLED=1, although I don't know if this may be triggered even with tracing disabled (or effectively no-ops).

@Herrminator
Copy link

IIRC you should also be able to forcibly disable it with OTEL_SDK_DISABLED=1, although I don't know if this may be triggered even with tracing disabled (or effectively no-ops).

Not in Go 😢 : open-telemetry/opentelemetry-go#3559

@cpuguy83
Copy link
Member

cpuguy83 commented Apr 4, 2024

our tracing code supports this explicitly.
If the var is set we don't setup tracing.

@Herrminator
Copy link

Ah, I see. Thank you!

But as you suspected, it doesn't seem to have any effect.
BTW, in our environment, those syslog entries seem to happen every time, a container does a health check.

@krissetto
Copy link
Contributor

I've done a bit of research into this issue with the help of @laurazard, here's the situation so far:

What seems to be happening is that the OTEL wrapper is unaware of any calls that are made to the underlying ResponseWriter.WriteHeader() func it is wrapping.

In scenarios where the underlying ResponseWriter already has it's header set (and, therefore, wroteHeader == true in the underlying net/http response struct):

As a general question i've been having: should the otel wrapper be this opinionated?


In the case of docker compose stats, as the example i used to reproduce the issue, we seem to call Flush() on the raw http.ResponseWriter (not yet wrapped by otel) here:

moby/daemon/stats.go

Lines 47 to 53 in f9dfd13

outStream := config.OutStream
if config.Stream {
wf := ioutils.NewWriteFlusher(outStream)
defer wf.Close()
wf.Flush()
outStream = wf
}

I'm not sure if we can (or should) change that implementation as a fix for this issue (the commit where this is included on our side is from quite a while back, see here).

@thaJeztah
Copy link
Member

thaJeztah commented Apr 11, 2024

Related to the above (at least from a discussion earlier today); c0391bf was (I think) where the "write headers early" was added)

edit: this was related 1cbf5a5

We also discussed briefly; what if we would make the writer we pass on a wrapped writer that ignores writing headers (as we know we already sent headers, so anything after us was not expected to send any)

@laurazard
Copy link
Member

laurazard commented Apr 11, 2024

To add a little more info, the "early write" was originally added in 1cbf5a5.

Ahh, beat me to it @thaJeztah .

@krissetto krissetto linked a pull request Apr 14, 2024 that will close this issue
@krissetto
Copy link
Contributor

krissetto commented Apr 14, 2024

Opened a quick draft pr that should fix the superfluous.. warning log when running docker logs and docker compose stats, but we might need to fix other areas where responses are streamed as well

@cpuguy83
Copy link
Member

cpuguy83 commented May 3, 2024

@krissetto I think the simplest fix here may be best, which is to just call wf.Write([]byte{}) before calling Flush here:

wf.Flush()

Same for logs.

@cpuguy83
Copy link
Member

cpuguy83 commented May 3, 2024

Opened an issue over in the otelhttp repo: open-telemetry/opentelemetry-go-contrib#5438

In the meantime we can change from using direct flush calls to writing an empty value so it triggers otelhttp.
Also I think we can skip the otelhttp wrapper when we we aren't recording traces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/metrics/otel area/metrics kind/bug Bugs are bugs. The cause may or may not be known at triage time so debugging may be needed. status/0-triage version/25.0
Projects
None yet
10 participants