errs

Integráció

Az errs bedrótozása logolásba, Sentrybe és sima net/http handlerekbe.

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:

errmap/errmap.go
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),
    })
}
main.go
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 errshttperrslog/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 LogValue kimenete táplál.
  • Sentry: a teljes Sentry-integráció, StackTrace() és kód-alapú fingerprinting.
Copyright © 2026