Improve logging and error output in the YARP container image
- Dominant language
- C#
- Stars
- 9.6k
- Forks
- 933
- Avg merge
- 12d 18h
- Merged PRs (30d)
- 2
Description
## Context
The YARP container image startup and error output is raw ASP.NET Core framework logging. Compared to tools like Caddy and nginx, the experience is noisy and hard to troubleshoot.
## What we have today
**Startup** (after #3015 work):
```
YARP
Config: /app/config.json
Static files: /app/wwwroot
SPA fallback: /index.html
Listening on: http://localhost:5999
```
This is good — clean and useful.
**Proxy error (backend down)**:
```
warn: Yarp.ReverseProxy.Forwarder.HttpForwarder[48]
Request: An error was encountered before receiving a response.
System.Net.Http.HttpRequestException: Connection refused (localhost:5100)
---> System.Net.Sockets.SocketException (61): Connection refused
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs...
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync...
...15 more lines...
```
This is bad — 20 lines of stack trace per failed request. No route name, no request URI, no status code, no duration.
## What Caddy shows for the same error
```
ERROR http.log.error dial tcp [::1]:5100: connect: connection refused
{"request": {"method": "GET", "uri": "/api/users"}, "status": 502, "duration": 0.001943}
```
One structured line: error message, request, status, duration.
## Proposed improvements
### 1. Request logging middleware
A `Log.Level` config option that controls our own request logging:
| Level | Behavior |
|---|---|
| `default` | Banner + warnings/errors only. No per-request output. |
| `requests` | Single-line per request: method, path, what handled it, status, duration |
| `debug` | Full framework logs + resolved config dump at startup |
Example verbose output:
```
GET / → static 200 510b 3ms
GET /_astro/app.f4e2d1.css → static 200 224b 1ms
GET /api/users → proxy 200 45ms
GET /dashboard → fallback 200 510b 2ms
GET /missing.js → 404 0ms
GET /api/users → proxy 502 74ms Connection refused (localhost:5100)
```
### 2. Condensed error output
Proxy errors should be single-line on console by default, not full stack traces. The root cause message (`Connection refused`, `no such host`) is what matters — not the .NET call stack.
Include: route name, destination address, request path, status code, error summary.
### 3. Config dump in debug mode
`Log.Level=debug` should print the resolved `YarpAppConfig` object as JSON at startup, so operators can verify what the app is actually running with.
### 4. Preserve escape hatch
The standard `Logging:LogLevel` and `Logging:Console:LogLevel` config sections continue to work for fine-grained control. Our `Log.Level` is the opinionated layer on top.
## Tested scenarios
These were tested with an actual Caddy comparison using a Node.js backend at `localhost:5100` and broken routes pointing at `localhost:9999` and `wrong-hostname:8080`. The sample is at `/tmp/yarp-spa-sample/`.
| Scenario | YARP today | Caddy | Goal |
|---|---|---|---|
| Startup | Clean banner ✅ | Structured JSON startup | Keep our banner |
| Successful request | Silent ✅ | Silent (unless access log on) | Keep silent by default |
| Proxy error | 20-line stack trace | 1-line structured JSON | 1-line summary |
| Verbose/access log | Not available | Via access log config | `Log.Level=requests` |
| Debug/config dump | Not available | N/A | `Log.Level=debug` |
Contributor guide
Assessment
This issue has not been assessed yet.