Coming up! Version 0.7-preview is out now! Check out the release notes.

On this page

Monitoring

Knowing your gateway is healthy shouldn't require guesswork. Portway gives you a few complementary ways to keep an eye on things: health check endpoints for your uptime probes, optional per-request traffic logging to file or SQLite, and SQL connection pool statistics written to the application log on a schedule. Let's walk through each of them, starting with the endpoints your monitoring tools will call most often.

Health checks

The health endpoints are designed to be cheap to call, so you can point your probes at them without worrying about load. Each one caches its result briefly, which keeps aggressive polling from touching your backends on every request.

Basic health check

http
GET /health

This is the endpoint most uptime monitors will want. It returns a cached status report that stays valid for 15 seconds:

json
{
  "status": "Healthy",
  "timestamp": "2025-05-03T10:30:00Z",
  "cache_expires_in": "15 seconds"
}

Liveness probe

http
GET /health/live

This one simply answers Alive with a 5-second cache. It is the natural choice for Kubernetes liveness probes or load balancer checks, since it only confirms the process is responsive without touching any downstream services.

Detailed health check

http
GET /health/details
Authorization: Bearer <token>

When something looks off, this is where you get the full picture. It reports every component check individually with its timing, so you can see at a glance whether it is disk space, a proxy target, or a database connection that is dragging the status down. Because it exposes internal details, it asks for a valid token:

json
{
  "status": "Healthy",
  "timestamp": "2025-05-03T10:30:00Z",
  "cache_expires_in": "60 seconds",
  "version": "1.0.0",
  "checks": [
    {
      "name": "Diskspace",
      "status": "Healthy",
      "description": "Disk space: 65% remaining",
      "duration": "2.45ms",
      "tags": ["storage", "system"]
    },
    {
      "name": "ProxyEndpoints",
      "status": "Healthy",
      "description": "All proxy services are responding",
      "duration": "145.32ms",
      "tags": ["proxies", "external", "readiness"]
    }
  ],
  "totalDuration": "147.77ms"
}

Request traffic logging

Sometimes you want more than a health status: you want to know exactly which requests came through, how long they took, and who sent them. Traffic logging records that per-request metadata to file or SQLite. It is disabled by default, since not every deployment needs it, and you can enable it in appsettings.json whenever the question comes up:

json
{
  "RequestTrafficLogging": {
    "Enabled": true,
    "StorageType": "file",
    "LogDirectory": "log/traffic",
    "MaxFileSizeMB": 50,
    "MaxFileCount": 5,
    "FilePrefix": "proxy_traffic_",
    "BatchSize": 100,
    "FlushIntervalMs": 1000,
    "IncludeRequestBodies": false,
    "IncludeResponseBodies": false,
    "MaxBodyCaptureSizeBytes": 4096,
    "CaptureHeaders": true
  }
}

Configuration options

The defaults are sensible for most deployments. Every field, including the SQLite storage options and the retention settings, is listed in Audit and traffic logging.

WARNING

IncludeRequestBodies and IncludeResponseBodies can capture sensitive data. Authorization headers are automatically redacted, but request and response bodies are not filtered.

Log entry format

json
{
  "Timestamp": "2025-05-03T10:30:00Z",
  "Method": "GET",
  "Path": "/api/prod/Products",
  "QueryString": "?$top=10",
  "Environment": "prod",
  "EndpointName": "Products",
  "StatusCode": 200,
  "DurationMs": 125,
  "Username": "api-user",
  "ClientIp": "192.168.1.100",
  "TraceId": "a1b2c3d4",
  "RequestHeaders": {
    "Accept": "application/json",
    "Authorization": "[REDACTED]"
  }
}

SQLite storage

File storage is fine for occasional inspection, but if you find yourself wanting to ask questions of your traffic data (which endpoints are slow, where errors cluster), SQLite is the more comfortable choice. It turns your traffic log into a queryable database:

json
{
  "RequestTrafficLogging": {
    "StorageType": "sqlite",
    "SqlitePath": "log/traffic_logs.db"
  }
}

A few queries to get you started; each answers a question you will sooner or later want answered:

sql
-- Top endpoints by request count (last hour)
SELECT EndpointName, COUNT(*) AS RequestCount
FROM TrafficLogs
WHERE Timestamp > datetime('now', '-1 hour')
GROUP BY EndpointName
ORDER BY RequestCount DESC
LIMIT 10;

-- Average response time by endpoint
SELECT EndpointName, AVG(DurationMs) AS AvgDuration
FROM TrafficLogs
WHERE Timestamp > datetime('now', '-1 hour')
GROUP BY EndpointName
ORDER BY AvgDuration DESC;

-- Error rate by environment (last 24 hours)
SELECT
    Environment,
    COUNT(*) AS TotalRequests,
    SUM(CASE WHEN StatusCode >= 400 THEN 1 ELSE 0 END) AS Errors,
    CAST(SUM(CASE WHEN StatusCode >= 400 THEN 1 ELSE 0 END) AS FLOAT) / COUNT(*) * 100 AS ErrorRate
FROM TrafficLogs
WHERE Timestamp > datetime('now', '-24 hours')
GROUP BY Environment;

-- Slowest requests
SELECT Path, QueryString, DurationMs, StatusCode
FROM TrafficLogs
WHERE DurationMs > 1000 AND Timestamp > datetime('now', '-1 hour')
ORDER BY DurationMs DESC
LIMIT 20;

Log levels

Verbosity lives in appsettings.json, under the Serilog section. Default sets the level for Portway's own events, and the Override entries quiet the framework so you get detail without drowning in ASP.NET and Entity Framework chatter:

json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft.AspNetCore": "Warning",
        "Microsoft.EntityFrameworkCore": "Warning"
      }
    }
  }
}

Overrides match on the source context that a log event carries. Portway's own events are written through Serilog's static logger and carry no source context, so they follow Default rather than any per-namespace override. Raise Default to Debug when you want the detailed traces.

Rotation is handled for you: application logs roll over daily (portwayapi-20250503.log) while traffic logs roll by file size (proxy_traffic_20250503_143000.json).

SQL connection pool metrics

If your gateway leans heavily on SQL endpoints, the connection pool is worth a periodic glance. Portway writes pool statistics to the application log every 10 minutes at Information level, so a slow pool exhaustion shows up in the log history before it becomes an outage:

txt
SQL Connection Pool Status: Active connections: 12, Available: 88

Pool sizing can be adjusted in appsettings.json if the defaults do not fit your workload. The SqlConnectionPooling properties and their defaults are listed in Application Settings.

Prometheus integration

Portway can serve its request metrics on a native Prometheus scrape endpoint. Set the telemetry provider and Prometheus pulls metrics straight from the gateway:

json
{
  "Telemetry": {
    "Provider": "Prometheus"
  }
}
yaml
scrape_configs:
  - job_name: "portway"
    scrape_interval: 15s
    static_configs:
      - targets: ["portway.yourdomain.com"]

The endpoint defaults to /metrics and exposes request duration histograms (tagged per endpoint), cache hit rates, and the standard ASP.NET Core server metrics. The Telemetry guide covers the full metric list, path configuration, and the OTLP push alternative.

Troubleshooting

A few situations come up often enough to mention here.

Missing traffic logs: usually one of three things. Check that Enabled is true, that the process can write to the log directory, and that the queue has not been exhausted (QueueCapacity in the configuration).

Health check degraded: GET /health/details tells you which check failed and how long it took. Disk space and proxy endpoint connectivity are the most common culprits, so those are good places to look first.

High response times: enabling traffic logging with StorageType: sqlite and querying DurationMs will quickly show you which endpoints are slow, and whether the slowness is broad or concentrated.

powershell
# Check disk space
Get-PSDrive -PSProvider FileSystem

# Review recent errors in application log
Select-String -Path ".\log\*.log" -Pattern "\[ERR\]" | Select-Object -Last 50
bash
# Check disk space
df -h

# Review recent errors in application log
grep -h "\[ERR\]" ./log/*.log | tail -n 50

Next steps

Last updated: 2026-09-07