Integráció
Az errs felépíti a hibákat; szándékosan nem tud a slog-ról, a Sentryről vagy a HTTP-ről. Ez az oldal azt a három helyet írja le, ahol valami más veszi fel az *errs.Error-t és csinál vele valamit: a standard loggert, a Sentryt, és egy kézzel írt HTTP mappert azoknak a projekteknek, amik errs-t akarnak httperr nélkül.
Log kimenet
Az *errs.Error implementálja az slog.LogValuer-t:
func (e *Error) LogValue() slog.Value
Egy hiba slog.Any("error", err)-nek átadva strukturált csoportot bocsát ki, nem egy lapos stringet: a belső üzenetet, a kódot, a publikus üzenetet, a Chain-t (egy objektum wrap-szintenként) és a Frames-t (a teljes hívási stack a keletkezés pontjától). Ez automatikusan megtörténik, hívási helyenkénti formázás nélkül, bárhol, ahol egy *errs.Error egy slog hívásba kerül:
slog.Error("request failed", slog.Any("error", err))
{
"msg": "request failed",
"error": {
"msg": "shop: place order: orders: decrement stock: connection refused",
"code": "shop_api_place_order_failed",
"public": "A rendelés feladása nem sikerült, próbáld újra később.",
"chain": [
{ "msg": "shop: place order", "file": "service.go", "line": 42, "function": "service.(*Service).PlaceOrder" },
{ "msg": "orders: decrement stock", "file": "orders.go", "line": 31, "function": "repository.(*Orders).DecrementStock", "meta": { "product_id": "prod_42" } },
{ "msg": "connection refused" }
],
"stack": [
{ "file": "orders.go", "line": 31, "function": "repository.(*Orders).DecrementStock" },
{ "file": "service.go", "line": 42, "function": "service.(*Service).PlaceOrder" }
]
}
}
Egy csapdát érdemes elkerülni: a LogValue csak akkor sül el, ha az slog ténylegesen egy error-típusú, *errs.Error-t hordozó értéket kap. Ha előbb hívod az err.Error()-t, és a kapott stringet logolod, pontosan azt kapod: egy lapos stringet, struktúra nélkül. Mindig magát a hibát add át (slog.Any("error", err)), sose a renderelt üzenetét. A Logolás oldal írja le a logolási setup további részét, a console/JSON handlereket, és hogy ez a strukturált csoport hogyan néz ki mindegyikben.
Sentry
Az *errs.Error kiteszi az egyetlen metódust, amit a sentry-go reflexióval keres, amikor egy exception stack-frame-jeit építi:
func (e *Error) StackTrace() []uintptr
Bármely Sentry-integráció, ami sima error-t fogad el és ellenőrzi ezt a metódust (ahogy a telemetry/sentryx teszi), pontos stacket kap extra bekötés nélkül: nincs kézi sentry.NewStacktrace() hívás, nincs frame-átugrási aritmetika. A hiba Code-ja (ha be van állítva, jellemzően errs.Define-on keresztül) egyben stabil Sentry-fingerprintként is szolgál: az exception átcímkézése a kóddal küldés előtt azt jelenti, hogy egy hibamód egy Sentry issue-ba csoportosul, függetlenül attól, hogy az üzenetszöveg hogyan van megfogalmazva az egyes wrap-helyeken, vagy hány különböző hívási hely termeli.
Minimális net/http mapper
A httperr azért létezik, mert ez a mapping, rendesen megcsinálva (content negotiation, RFC 9457 forma, validációs hibák, dev-mode gating), több, mint néhány sor. De ha csak az errs stack/kód/publikus-üzenet modelljét akarod, és nem akarod az extra függőséget, a minimális verzió tényleg kicsi:
package errmap
import (
"encoding/json"
"errors"
"log/slog"
"net/http"
"github.com/gp-system/errs"
)
func Write(w http.ResponseWriter, r *http.Request, err error) {
status := errs.StatusOf(err)
if status == 0 {
status = http.StatusInternalServerError
}
detail := errs.PublicOf(err)
if status >= 500 {
slog.ErrorContext(r.Context(), "request failed", slog.Any("error", err))
if detail == "" {
detail = "Internal server error."
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(map[string]any{
"status": status,
"detail": detail,
"code": errs.CodeOf(err),
})
}
func placeOrder(w http.ResponseWriter, r *http.Request) {
if err := svc.PlaceOrder(r.Context(), input); err != nil {
errmap.Write(w, r, err)
return
}
w.WriteHeader(http.StatusCreated)
}
Ez szándékosan nem RFC 9457, nincs validációs hiba-formája és nincs dev-mode detail-gatingje: azt mutatja, hogy az errs nem kényszerít rád semmilyen keretrendszert, nem pedig ajánlott alternatívaként a httperr helyett egy éles projektben. Amint az RFC 9457 forma, mezőnkénti validációs hibák vagy a Stack/Chain dev-mode extension memberek kellenek, nyúlj a httperr-hez: pontosan ugyanaz az errs.StatusOf/PublicOf/CodeOf hívás van mögötte, mint ebben a kézzel írt verzióban, csak a körülötte lévő gépezet nagy része már megvan.
A kittel
Egy generált gpsystem projekt sosem hívja közvetlenül a fenti accessorokat: a httperr.WriteError teszi, a generált szerver hibahookjaiba drótozva a server-en keresztül. A service-eidben és repositoryidban pontosan úgy írod az errs.Define/errs.Wrap-et, ahogy itt és az API oldalon látod; onnantól az errs → httperr → slog/Sentry az a bekötés, amit a Hibamodell végigvezet.
Kapcsolódó oldalak
- errs: API: a teljes konstruktor/attribútum/accessor referencia.
- httperr: Áttekintés és Hibaválaszok: az erre épülő RFC 9457 renderelő.
- Logolás: a console/JSON handlerek, amiket a
LogValuekimenete táplál. - Sentry: a teljes Sentry-integráció,
StackTrace()és kód-alapú fingerprinting.