Log_appended fields not included in logs when connection is aborted

1. The problem I’m having: log_appended fields not included in logs when connection is aborted

I use caddy to reverse proxy a few dozen domains. I call enable_logging, shown below, to setup a domain-specific log file which includes a few log_appended fields.

Today, I noticed cases where these log_appended fields were not present in the json log files. It correlates with cases where I see errors like “aborting with incomplete response” in the caddy application logs, which I understand to indicate that the connection was aborted before all data was returned to the caller.

Even in the event of an aborted response, I would still like the log_appended fields to be present in the loged output, but I’m unable to find a way to do so. I tried format append as well, but was ending up with null values for the appended fields.

2. Error messages and/or full log output:

JSON connection logs do not include log_appended fields.

App logs at the time have:

{
  "level": "warn",
  "ts": 1747593144.5305367,
  "logger": "http.handlers.reverse_proxy",
  "msg": "aborting with incomplete response",
  ...
  "error": "reading: context canceled"
}

3. Caddy version:

2.10.0

4. How I installed and ran Caddy:

a. System environment:

Docker (host runs ubuntu server)

b. Command:

Run via docker compose

c. Service/unit/compose file:

Docker compose file is irrelevant for this issue.

d. My complete Caddy config:

Relevant snippet:

# Determine the caller IP. If the CF-Connecting-IP header is set, the call is from CloudFlare
# and this header will be used to as the client IP. Otherwise, fall back to IP used to connect to caddy
# (remote_host resolves to request.remote_ip in the logs)
# https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/#cf-connecting-ip
# This in particular was done to support sending the real client IP to vaultwarden.
(find_resolved_caller_ip) {
	map {http.request.header.CF-Connecting-IP} {resolved_caller_ip} {call_originates_from} {
		~.+ "${0}" "cloudflare"
		default "{remote_host}" "local-network"
	}
}

# Configures logging to a host-specific log file.
# Arg 0: Name of the log file (no extension)
(enable_logging) {
	import find_resolved_caller_ip
	log_append resolved_caller_ip {resolved_caller_ip}
	log_append call_originates_from {call_originates_from}

	# Generate and log the full URI, including rewrites
	vars {
		full_uri {http.request.scheme}://{http.request.host}{http.request.uri}
	}
	log_append full_uri {vars.full_uri}

	log {
		output file /var/log/caddyaccess/{args[0]}.jsonl {
			mode 640
			roll_size 10MiB
			roll_keep 5
		}

		format json {
			time_format iso8601
		}
	}
}

# All other reverse proxy includes should use this as a base to ensure consistent header
# handling.
# Arg 0: Path to proxy
# Arg 1: A block which will be injected into the reverse proxy. It minimally must provide
#        the backend "to", which should have form: "to 127.0.0.1:port"
(base_create_reverse_proxy) {
	import find_resolved_caller_ip
	reverse_proxy {args[0]} {
		{block}
		header_up X-Real-IP {resolved_caller_ip}
		header_up True-Client-IP {resolved_caller_ip}

		# Set HSTS with a duration of 2 years
		header_down Strict-Transport-Security "max-age=63072000; includeSubDomains"
	}

	encode zstd gzip
}

# Creates a reverse proxy with logging enabled
# Arg 0: Path to proxy (* for all)
# Arg 1: Internal port to proxy
# Arg 2: Log file name (no ext)
(create_reverse_proxy) {
	import base_create_reverse_proxy {args[0]} {
		to 127.0.0.1:{args[1]}
	}
	import enable_logging {args[2]}
}

sample_site.my_site.com {
	import create_reverse_proxy * 8000 sample_site
}

5. Links to relevant resources:

Just to make sure I’m understanding you correctly - you’re hoping to see those extra fields included in this specific log entry? This seems to be an error log, not an access log. The log_append directive only applies to access logs.

1 Like

you’re hoping to see those extra fields included in this specific log entry?

No, I’m expecting the 3 log_append fields to show up in the JSON connection logs (generated via the log directive), and this works fine in all cases other than when when the aborting with incomplete response error is shown in the application logs.

I included the application error/warn logs to provide context as to the circumstances in which the log_append fields are not being written to the connection logs.

Here’s what the connection logs show when the log_append is working properly. The 3 log_append fields are shown at the bottom of the structure.

{
  "level": "info",
  "ts": "2025-05-18T19:52:43.154Z",
  "logger": "http.log.access.log14",
  "msg": "handled request",
  "request": {
    "remote_ip": "192.168.0.196",
    "remote_port": "57912",
    "client_ip": "192.168.0.196",
    "proto": "HTTP/2.0",
    "method": "GET",
    "host": "sample_site.my_site.com",
    "uri": "/favicon.ico",
    "headers": {
      "Dnt": [
        "1"
      ],
      "Sec-Ch-Ua-Mobile": [
        "?0"
      ],
      "Cookie": [
        "REDACTED"
      ],
      "Referer": [
        "https://sample_site.my_site.com/testing123"
      ],
      "Sec-Gpc": [
        "1"
      ],
      "Sec-Ch-Ua-Platform": [
        "\"Linux\""
      ],
      "Accept-Language": [
        "en-US,en;q=0.9"
      ],
      "Sec-Fetch-Mode": [
        "no-cors"
      ],
      "Sec-Fetch-Dest": [
        "image"
      ],
      "Sec-Fetch-Site": [
        "same-origin"
      ],
      "Sec-Ch-Ua": [
        "\"Chromium\";v=\"136\", \"Brave\";v=\"136\", \"Not.A/Brand\";v=\"99\""
      ],
      "Priority": [
        "u=1, i"
      ],
      "Accept": [
        "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
      ],
      "Accept-Encoding": [
        "gzip, deflate, br, zstd"
      ],
      "User-Agent": [
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
      ]
    },
    "tls": {
      "resumed": false,
      "version": 772,
      "cipher_suite": 4865,
      "proto": "h2",
      "server_name": "sample_site.my_site.com"
    }
  },
  "bytes_read": 0,
  "user_id": "",
  "duration": 0.000590406,
  "size": 14,
  "status": 404,
  "resp_headers": {
    "Content-Type": [
      "text/plain; charset=utf-8"
    ],
    "Server": [
      "Caddy"
    ]
  },
  "full_uri": "https://sample_site.my_site.com/favicon.ico",
  "call_originates_from": "local-network",
  "resolved_caller_ip": "192.168.0.196"
}