# 

## Why logging matters

Payment errors are inevitable — network blips, expired cards, processor downtime. What separates a resilient integration from a brittle one is how quickly you detect and respond. Structured logs give you:

* **Root-cause analysis** — trace a failed payment through every hop using `correlation_id`.
* **Trend detection** — spot rising decline rates before customers complain.
* **Compliance evidence** — auditable records for PCI-DSS and dispute resolution.


## What to log

### Required fields

| Field | Source | Example |
|  --- | --- | --- |
| `timestamp` | Your system | `2026-03-02T14:23:01.000Z` |
| `correlation_id` | Response body | `a1b2c3d4-e5f6-7890-abcd-ef1234567890` |
| `http_method` | Request | `POST` |
| `endpoint` | Request | `/v1/transactions` |
| `http_status` | Response | `200` |
| `response_status` | Response body | `declined` |
| `response_code` | Response body | `05` |
| `response_message` | Response body | `Transaction declined by the issuer.` |
| `elapsed_ms` | Your system | `342` |


### Useful additions

| Field | Purpose |
|  --- | --- |
| `idempotency_key` | Correlate retries |
| `transaction_id` | Link to gateway record |
| `processor_code` | Processor-specific debugging |
| `customer_id` | Correlate with your user |


### What NOT to log

PCI compliance
Never log full card numbers (PAN), CVV, bank account numbers, or API keys. Violations can result in PCI-DSS non-compliance and significant fines.

| Field | Log this instead |
|  --- | --- |
| Full PAN (`4111111111111111`) | Last four (`****1111`) |
| CVV (`123`) | Never log |
| API key | `****last4` or omit |
| Bank account number | Last four (`****6789`) |


## Structured logging implementation

Python
```python
import json, logging, time, os, requests

logger = logging.getLogger("paradise")
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.INFO)

API_KEY = os.environ["PARADISE_API_KEY"]
BASE = os.environ.get("PARADISE_BASE_URL", "https://api.dev.paradisegateway.net")

def logged_request(method: str, path: str, payload: dict | None = None) -> dict:
    url = f"{BASE}{path}"
    headers = {"Content-Type": "application/json", "APIKEY": API_KEY}

    start = time.monotonic()
    try:
        resp = requests.request(method, url, headers=headers, json=payload, timeout=30)
        body = resp.json()
        elapsed = int((time.monotonic() - start) * 1000)

        log_entry = {
            "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
            "level": "error" if resp.status_code >= 400 else "info",
            "http_method": method,
            "endpoint": path,
            "http_status": resp.status_code,
            "response_status": body.get("response_status"),
            "response_code": body.get("response_code"),
            "correlation_id": body.get("correlation_id"),
            "transaction_id": body.get("id"),
            "elapsed_ms": elapsed,
        }

        if resp.status_code >= 400 or body.get("response_status") in ("declined", "error"):
            log_entry["response_message"] = body.get("response_message")
            logger.error(json.dumps(log_entry))
        else:
            logger.info(json.dumps(log_entry))

        return body
    except Exception as e:
        elapsed = int((time.monotonic() - start) * 1000)
        logger.error(json.dumps({
            "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
            "level": "error",
            "http_method": method,
            "endpoint": path,
            "error": str(e),
            "elapsed_ms": elapsed,
        }))
        raise
```

JavaScript
```javascript
const API_KEY = process.env.PARADISE_API_KEY;
const BASE = process.env.PARADISE_BASE_URL || "https://api.dev.paradisegateway.net";

async function loggedRequest(method, path, payload = null) {
  const url = `${BASE}${path}`;
  const start = Date.now();

  try {
    const resp = await fetch(url, {
      method,
      headers: { "Content-Type": "application/json", "APIKEY": API_KEY },
      body: payload ? JSON.stringify(payload) : undefined,
    });
    const body = await resp.json();
    const elapsed = Date.now() - start;

    const logEntry = {
      timestamp: new Date().toISOString(),
      level: resp.status >= 400 ? "error" : "info",
      http_method: method,
      endpoint: path,
      http_status: resp.status,
      response_status: body.response_status,
      response_code: body.response_code,
      correlation_id: body.correlation_id,
      transaction_id: body.id,
      elapsed_ms: elapsed,
    };

    if (resp.status >= 400 || ["declined", "error"].includes(body.response_status)) {
      logEntry.response_message = body.response_message;
      console.error(JSON.stringify(logEntry));
    } else {
      console.info(JSON.stringify(logEntry));
    }

    return body;
  } catch (err) {
    console.error(JSON.stringify({
      timestamp: new Date().toISOString(),
      level: "error",
      http_method: method,
      endpoint: path,
      error: err.message,
      elapsed_ms: Date.now() - start,
    }));
    throw err;
  }
}
```

## Example log output

### Approved transaction

```json
{
  "timestamp": "2026-03-02T14:23:01.000Z",
  "level": "info",
  "http_method": "POST",
  "endpoint": "/v1/transactions",
  "http_status": 200,
  "response_status": "approved",
  "response_code": "00",
  "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "transaction_id": "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC",
  "elapsed_ms": 342
}
```

### Declined transaction

```json
{
  "timestamp": "2026-03-02T14:24:15.000Z",
  "level": "error",
  "http_method": "POST",
  "endpoint": "/v1/transactions",
  "http_status": 200,
  "response_status": "declined",
  "response_code": "51",
  "correlation_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "transaction_id": "TRANSACTION-01KEW33R7ZOW22U44YHES8UHXD",
  "elapsed_ms": 287,
  "response_message": "Insufficient funds"
}
```

## Monitoring and alerting

Once you have structured logs, feed them into a monitoring system to catch issues early.

![image](https://files.modern-mermaid.live/images/1776277712786-mermaid-diagram-1776277712922.png)

### Recommended alerts

| Alert | Condition | Severity |
|  --- | --- | --- |
| High decline rate | Decline rate > 15% over 15 minutes | Warning |
| Elevated 5xx errors | Any `5xx` response from the API | Critical |
| Slow responses | `elapsed_ms` > 5000 for 5+ requests in 10 minutes | Warning |
| Auth failures | `401` responses spike | Critical |
| Unknown processor code | Processor code not in your mapping | Info |


### Key metrics to track

| Metric | How to compute | Why it matters |
|  --- | --- | --- |
| **Approval rate** | `approved / total_transactions` | Core business health |
| **Decline rate by code** | Group declines by `response_code` | Identify specific card issues |
| **API latency (p50, p95, p99)** | `elapsed_ms` percentiles | Performance regression detection |
| **Error rate** | `5xx_responses / total_requests` | Gateway availability |
| **Retry rate** | Requests with same `idempotency_key` | Indicates instability |


## Using correlation_id for troubleshooting

Every response includes a `correlation_id`. This value traces the request through Paradise Gateway's internal systems and to the processor.

1. Log the `correlation_id` alongside your internal order ID.
2. When investigating an issue, query your logs by `correlation_id`.
3. When contacting Paradise Gateway support, provide the `correlation_id` — it lets the team trace the request end-to-end.


```python
log.info(
    "Payment for order %s: correlation_id=%s, status=%s",
    order_id,
    response["correlation_id"],
    response["response_status"],
)
```

## Best practices

* **Use structured JSON logs** — not free-text strings. JSON logs are searchable and parseable by aggregation tools.
* **Include `correlation_id` on every log line** — it is your primary tracing key.
* **Redact sensitive data before logging** — mask PAN, CVV, and API keys.
* **Set log retention** — keep payment logs for at least 12 months for dispute resolution.
* **Separate payment logs** — use a dedicated logger/channel for payment-related events to reduce noise.


## Next steps

* [Retry with exponential backoff](/docs/tutorials/error-handling/retry-with-exponential-backoff)
* [Processor-specific errors](/docs/tutorials/error-handling/processor-specific-errors)
* [Error handling concepts](/docs/concepts/api-fundamentals/error-handling)