Felhantering och rapportering
Allmänt
Den här sidan syftar till att sammanställa information om hur våra tjänster bör bete sig när fel av olika sorter uppstår. Fokuset är hantering av fel inom en tjänst, samt hur fel skall propageras tillbaka genom anropskedjan till anroparen. En väl definierad standard syftar dels till att göra upplevelsen bättre för konsumenten av våra API:er, men även att göra det lättare för oss på insidan att isolera felkällor när de uppstår. Rutiner för övervakning och felsökning är dock inte i scopet här, utan samlas på en egen sida.
Felkoder
Det är viktigt att skilja på det som HTTP 1.1 kallar klientfel (“intended for cases in which the client seems to have erred”) och serverfel (“cases in which the server is aware that it has erred or is incapable of performing the request”). Det vill säga, använd felkoder ur 4XX eller 5XX beroende på vad som har uppstått. Det är enkelt och smidigt att fånga fel med en generisk catch högst upp i stacken, men varken användaren eller de som är “on call” är hjälpta av att en tjänst returnerar 500 när det egentligen rörde sig om en 400.
Även om det kräver lite mer kodning och investering i testtid så ger det oftast en bra avkastning att hålla isär i alla fall de vanligaste felen i de olika grupperna. Här följer några exempel.
Klientfel
Även om vi jobbar mot att ha Open API-specifikationer på alla tjänster, så bör vi validera inkommande metoder och returnera ett 405 Method Not Allowed
om det inte matchar. Detta bör vi dock kunna räkna med att gatewayen hanterar åt oss.
Ett API bör validera inkommande Accepts
och generera ett 406 Not Acceptable
om klienten begär ett format som inte stöds, i stället för att förutsätta vad klienter brukar fråga efter och returnera det formatet.
Specificerar vi våra API:er som att de tar emot application/json
så bör vi även validera att det är det som klienten skickar till oss, och svara med ett 415 Unsupported Media Type
om det inte stämmer. Det kan dock finnas fall när vi integrerar legacy som gör att vi måste tumma på detta, men i sådana lägen bör det loggas en varning så att vi får koll på vilka anropande system som “inte beter sig”.
Serverfel
Det är en ordentlig skillnad på exempelvis 501 Not Implemented
, och 502 Bad Gateway
eller 504 Gateway Timeout
vilka båda beror på att någon annan tjänst har problem. Se till att din design gör det möjligt att veta vad problemet var, så att du kan logga den information som behövs för att snabbast möjligt kunna hitta rotorsaken.
Format
Det har länge saknats en standard för hur rapportering av fel skall formateras, vilket har lett till att många hittat på egna standarder, returnerat felmeddelanden som en vanlig textsträng, eller dumpat stack trace rakt ut i response body. Även om stack trace kan vara hjälpsamt för utvecklaren under utveckling, så är det sällan till någon hjälp för klienten och riskerar dessutom att exponera känslig information om systemet.
Sedan mars 2016 finns nu däremot RFC-7807 publicerad. Den beskriver “Problem Details for HTTP APIs” och syftar till att skapa en standard för hur felrapporter kan returneras på ett standardiserat format med Content-Type application/problem+json
. Vi beskriver inte formatet i detalj här utan hänvisar i stället till RFC och exempelvis https://blog.restcase.com/rest-api-error-handling-problem-details-response/ för mer information.
Exempel:
{
"type": "https://example.net/validation-error",
"title": "Your request parameters didn't validate.",
"invalid-params": [
{ "name": "age", "reason": "must be a positive integer" },
{ "name": "color", "reason": "must be 'green', 'red' or 'blue'"}
],
“correlation-id”: “018924ac-559c-4ed4-a7cd-db88ea13af94“,
“status”: 400
}
Detta betyder förstås inte att vi skall kasta bort eventuell information från ett exception. Innan vi returnerar ett fel till anroparen måste vi logga detaljerna kring vad som hänt så att vi får lättare att hitta felet.
Loggning
Ett system bestående av ett flertal mikrotjänster behöver följa en gemensam standard för hur loggning går till och vilken information som skall inkluderas i loggarna. Det här dokumentet går inte in på samtliga detaljer när det gäller vilken standard som skall gälla, utan försöker hålla sig till allmänna råd kring just loggning i samband med felhantering.
Absolut viktigast är att hålla reda på vilket data som hanteras i den kontext där felet uppstår. Att dumpa en request payload till log är inte en bra idé om den innehåller känsligt data som exempelvis personuppgifter. Var därför noga med vad som loggas och maska eventuellt känsligt data innan det skickas till log.
Att kunna skriva ett stack trace till log underlättar felavhjälpning, men tänk på loggnivån här så att det är möjligt att slå av och på det beteendet genom att styra loggnivån i applikationens inställningar eller i kollektorn. Allt för detaljrik loggning på motsvarande INFO-nivå riskerar att dränka andra viktiga signaler och kan i värsta fall bli en vektor för denial of service. Men, var även uppmärksam på det omvända. Om allt för mycket detaljer undanhålls i det normala fallet kommer vi alltid att behöva modifiera systemet och vänta på att felet händer igen innan vi har den information vi behöver.
Precis som vi returnerar ett correlation-id i exemplet ovan så bör samma id finnas med i den information som loggas ut, så att vi utifrån ett rapporterat fel har större chans att söka ut motsvarande logginformation. Ett korrelations id bör skapas automatiskt så tidigt i anropskedjan som möjligt, helst redan i första gatewayen, men kan om den saknas genereras av tjänsten när ett fel faktiskt uppstått.
Fördelen med att generera ett korrelations id tidigt är att vi kan få en spårbarhet på hela anropskedjan, vilket kan vara till hjälp om ett fel uppstår för att en ovanliggande tjänst gjort något fel. Det förutsätter då att vi gör det till en vana att inkludera detta id i loggarna även när det inte inträffat något fel, men då är vi på väg in på området spårningsinformation, vilket är en helt annan diskussion.