dotnet / dotnet/yarp

Improve logging and error output in the YARP container image

Open
#3,016 0 comments 3 reactions 0 assignees View on GitHub
Container
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.