Trasare și jurnalizare a cererilor
Trasare și jurnalizare a cererilor
Această pagină este pentru oricine apelează API-ul HTTP e-bon — parteneri POS, integrări de contabilitate, dashboard-uri terțe. Explică singurul antet de corelare de care ai nevoie (X-Request-Id), cum arată fiecare răspuns de eroare și cum să depui un raport de bug pe care îl putem rezolva rapid.
X-Request-Id. Dacă trimiți unul, îl returnăm. Dacă nu, generăm un UUID v4. Citează acel ID în fiecare raport de bug și găsim apelul tău în jurnalele noastre în câteva secunde.Trimite o cerere cu un ID de trasare
Antetul X-Request-Id călătorește cu fiecare cerere și fiecare răspuns.
| Direcție | Comportament |
|---|---|
Cerere X-Request-Id (insensibil la majuscule) | Opțional. Dacă trimiți o valoare nevidă, o folosim ca atare — fără validare, fără limită de lungime, fără filtrare de caractere. |
Răspuns X-Request-Id | Mereu prezent. Returnăm valoarea ta dacă ai trimis una, altfel returnăm UUID-ul v4 generat pentru tine. |
Câteva lucruri de fixat din start în modelul tău mental:
- Antetul este mereu returnat. Nu există opt-out — chiar și răspunsurile de health-check îl poartă.
- Antetele sunt insensibile la majuscule la nivel de protocol HTTP; tratează antetul de răspuns ca insensibil la majuscule când îl citești.
- Nu validăm valoarea de intrare. Alege ceva rezonabil: rămâi la ASCII, sub ~256 de caractere și preferă UUID-uri sau ID-urile tale de corelare (de ex. o cheie primară din tabela ta de coadă). Nu pune date personale în antet — ele ajung în jurnalele noastre.
- Dacă omiți antetul, generăm un UUID v4 proaspăt. Coliziunile nu sunt o îngrijorare.
X-Request-Id-ul tău, prin care echipa de suport îți urmărește apelul înapoi la linia exactă din sistem.Înțelege de ce contează acest antet
Trei motive concrete pentru care te interesează:
- Rapoarte de bug. Când ne spui „am primit un 500 de la
POST /api/v1/receiptsmarți pe la 14:30 UTC”, putem găsi acea cerere — în principiu — dar durează minute per apel. Când ne spuiX-Request-Id-ul, o găsim în câteva secunde. Vezi lista de raport de bug mai jos. - Corelarea jurnalelor client și server. Jurnalizează
X-Request-Id-ul din răspuns la fiecare rezultat non-2xx (și, ideal, la fiecare apel, eșantionat). Când un dashboard partener emite un eveniment Sentry cu ID-ul cererii atașat, inginerii voștri și ai noștri se uită la aceeași linie. - Trasarea încercărilor repetate. Dacă reutilizezi același
X-Request-Idla o încercare repetată, fiecare încercare apare în jurnalele noastre sub aceeași cheie de corelare. Aceasta este doar observabilitate — reutilizarea ID-ului nu declanșează nicio deduplicare. Dacă ai nevoie de încercări repetate sigure cu redare, asta este treaba luiIdempotency-Key, un antet separat (vezi tabelul de comparație și referința de idempotență).
Citește plicul de eroare
Fiecare răspuns de eroare care nu este de plafon de rată folosește aceeași formă JSON:
{
"error": {
"code": "COD_STRING",
"message": "Explicație lizibilă pentru oameni",
"details": "opțional, tipul variază în funcție de cod"
}
}
Trei ramuri produc acel plic:
| Ramură | Declanșator | Status | Corp |
|---|---|---|---|
| Validare | Corpul cererii sau query-ul nu a trecut validarea de schemă | 400 | code: VALIDATION_ERROR, message: 'Request validation failed', details: [{path, message}, …] |
| Eroare de domeniu | A fost ridicată o eroare tipizată de aplicație (de ex. negăsit, conflict, interzis) | mapat după cod | code: err.code, message: err.message, details dacă este setat |
| Netratat | Orice altceva (excepție necapturată) | 500 | code: INTERNAL_ERROR, message: 'An internal error occurred' |
Redactarea din ramura netratată contează: în producție message-ul este întotdeauna 'An internal error occurred'. Mesajul original al excepției rămâne în jurnalele noastre server, indexat după X-Request-Id-ul tău. Acesta este exact scenariul în care ai nevoie de antet în raportul tău de bug.
RATE_LIMIT_EXCEEDED este un obiect plat {code, message, status} la nivelul de top, neîncapsulat în {error: {…}}. Vezi Plafon de rată. Verifică pe code-ul de la nivelul de top pentru 429-uri.Ignoră zgomotul endpoint-urilor de health-check
Cele patru endpoint-uri de health — /health, /ready, /healthz, /livez — nu apar în access log-ul nostru. Se execută în continuare, returnează în continuare X-Request-Id, doar nu produc o linie de jurnal (altfel sondele de liveness/readiness Kubernetes ar îneca jurnalul). Vezi Stare și meta pentru contractele lor.
Copiază o rețetă client
Trei clienți de referință scurți care arată turul antetului și șablonul de captură pentru raportul de bug. Niciunul nu este pregătit pentru producție (fără încercări repetate, fără metrici) — există ca să-ți dea un punct de plecare.
import { randomUUID } from 'node:crypto';
async function callWithTrace(url, options = {}) {
const requestId = randomUUID();
const headers = { ...(options.headers ?? {}), 'X-Request-Id': requestId };
const res = await fetch(url, { ...options, headers });
const echoed = res.headers.get('x-request-id');
if (!res.ok) {
const body = await res.json().catch(() => null);
console.error('[ebon] cerere eșuată', {
method: options.method ?? 'GET',
url,
status: res.status,
sentRequestId: requestId,
receivedRequestId: echoed,
errorCode: body?.error?.code ?? body?.code,
});
}
return res;
}
import logging, uuid, requests
log = logging.getLogger('ebon')
def call_with_trace(method, url, **kwargs):
request_id = str(uuid.uuid4())
headers = {**kwargs.pop('headers', {}), 'X-Request-Id': request_id}
r = requests.request(method, url, headers=headers, **kwargs)
if r.status_code >= 400:
body = r.json() if r.headers.get('content-type', '').startswith('application/json') else None
log.error(
'apel ebon eșuat method=%s url=%s status=%s sent_id=%s recv_id=%s code=%s',
method, url, r.status_code, request_id,
r.headers.get('X-Request-Id'),
(body or {}).get('error', {}).get('code') or (body or {}).get('code'),
)
return r
curl -sS -D - -o /tmp/body.json -w '\nHTTP %{http_code}\n' \
-H "Authorization: Bearer $EBON_KEY" \
-H "X-Request-Id: $(uuidgen)" \
-X POST -d @body.json \
https://api.e-bon.ro/api/v1/receipts \
| grep -i '^x-request-id:'
# Apoi, la un eșec:
cat /tmp/body.json | jq '.error // .'
Depune un raport de bug util
Când ceva e stricat în producție, trimite-ne lista asta. Jumătate dintre puncte sunt evidente; ID-ul cererii este cel care transformă o investigație de 30 de minute într-una de 30 de secunde.
Metoda HTTP și URL-ul
POST /api/v1/receipts, GET /api/v1/devices/dev_abc/commands?status=pending. Include query string-ul; exclude orice secret din cale.
Codul de status și corpul răspunsului
error.code-ul exact, error.message și (dacă e prezent) error.details din plicul JSON. Pentru 429-uri, corpul plat {code, message, status}. Citează-l ca atare — nu parafraza.
X-Request-Id-ul din răspuns (obligatoriu)
Cel mai util singur câmp. Fără el căutăm după timestamp + metodă + status în jurnalele fiecărei replici. Cu el, un singur grep.
Timestamp aproximativ în UTC
Până la minut este suficient. Ajută atunci când ID-ul cererii lipsește sau când raportezi un val de eșecuri.
Corpul cererii redactat
Forma, nu secretele. Elimină Authorization, x-api-key, JWT-uri Bearer, datele personale ale clienților. Nu capturăm corpurile cererilor în access log-ul nostru, așa că nu îl putem reconstrui pe al tău.
Perspectiva clientului tău
Ce ai trimis (metodă/URL/antete, redactat), ce te așteptai să primești, ce ai primit. Dacă reîncerci cu același Idempotency-Key, spune asta — redarea din cache-ul Idempotency-Key arată identic cu primul apel la nivel de protocol.
Pentru un context mai larg de depanare, Ghidul de depanare acoperă moduri de eșec la nivel de integrare (probleme de autentificare, livrare de webhook-uri, împerechere de dispozitive) care apar adesea ca erori API derutante.
Idempotency-Key vs X-Request-Id
Cele două antete sunt independente și complementare. A le folosi pe amândouă la o încercare repetată este corect.
| Aspect | Idempotency-Key | X-Request-Id |
|---|---|---|
| Scop | Semantică de încercare repetată sigură — al doilea apel returnează răspunsul memorat al primului | Observabilitate — corelează jurnalele client cu jurnalele server |
| Comportament server | Prima cerere rulează; cererile ulterioare cu aceeași cheie redau corpul memorat | Fiecare cerere rulează normal; valoarea este jurnalizată și returnată |
| Domeniu de aplicare | Doar POST /api/v1/commands și POST /api/v1/receipts | Fiecare cerere HTTP, fiecare endpoint |
| Implicit dacă e omis | Fără cache; cererea rulează ca un POST proaspăt | Serverul generează un UUID v4 |
| Reținere | 24h per {orgId}_{key} (vezi Idempotență) | Niciodată persistat de API — trăiește doar în linia de jurnal a cererii |
| Validare | Regex ^[a-zA-Z0-9_-]+$, 1–128 caractere; 400 VALIDATION_ERROR la nepotrivire | Niciuna — acceptat ca atare |
| Deduplică apeluri? | Da — reluarea cu aceeași cheie returnează rezultatul memorat | Nu — doar pentru trasare |
| Când să-l reutilizezi | La reîncercarea unui POST non-idempotent pe care îl vrei sigur la reîncercare | La reîncercarea oricărui apel când vrei reîncercarea trasabilă spre ID-ul original |
Rețeta combinată pentru reîncercarea unui POST de bon fiscal: păstrează același Idempotency-Key (ca al doilea apel să redea), păstrează același X-Request-Id (ca ambele încercări să se înlănțuie în jurnalele noastre) și lasă API-ul să facă restul.
Continuă explorarea
- Prezentare API — ușa din față a suprafeței API.
- Erori API — catalogul complet de
code-uri și mapările lor de status HTTP. - Idempotență — contractul de încercare repetată sigură; se asociază cu
X-Request-Idla fiecare reluare. - Plafon de rată — plicul 429 este PLAT, nu wrapper-ul documentat mai sus.
- Stare și meta — cele patru căi tăcute (
/health,/ready,/healthz,/livez). - Depanare — moduri comune de eșec la nivel de integrare.
Limite de rată
Cum limitează API-ul e-bon traficul de cereri, ce înseamnă antetele RateLimit, cum arată un răspuns 429 și cum să reîncerci corect din integrarea ta POS.
Bonuri
Endpoint-uri REST pentru stocarea, listarea și consultarea bonurilor fiscale după tipărirea pe AMEF — scheme cerere/răspuns, exemple curl și coduri de eroare per endpoint.