Frequently Asked Questions (FAQ)
How to set 404 Not Found
handler?
Use WithNotFound
option:
h := myHandler{}
srv, err := api.NewServer(h, api.WithNotFound(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, `{"error": "not found"}`)
}))
if err != nil {
panic(err)
}
See Static router section for more details about router errors.
How to set error handler?
Use WithErrorHandler
option:
h := myHandler{}
srv, err := api.NewServer(h, api.WithErrorHandler(func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) {
var (
code = http.StatusInternalServerError
ogenErr ogenerrors.Error
)
switch {
case errors.Is(err, ht.ErrNotImplemented):
code = http.StatusNotImplemented
case errors.As(err, &ogenErr):
code = ogenErr.Code()
}
_, _ = io.WriteString(w, http.StatusText(code))
}))
if err != nil {
panic(err)
}
Also, check out the Convenient errors concept.
How to use net/http
middlewares?
The Server
type implements http.Handler
interface, so you can use any net/http
middleware with it.
type Middleware func(next http.Handler) http.Handler
func Wrap(h http.Handler, mw Middleware) http.Handler {
return mw(h)
}
// TraceMiddleware adds traces.
func TraceMiddleware(lg *zap.Logger, tp trace.TracerProvider) middleware.Middleware {
return func(next http.Handler) http.Handler {
t := tp.Tracer("http")
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
start := time.Now()
w := &writerProxy{ResponseWriter: rw}
ctx, span := t.Start(r.Context(), "HandleRequest")
defer span.End()
spanCtx := span.SpanContext()
ctx = With(ctx, lg.With(
zap.String("trace_id", spanCtx.TraceID().String()),
zap.String("span_id", spanCtx.SpanID().String()),
))
defer func() {
if r := recover(); r != nil {
lg.Error("Panic", zap.Stack("stack"))
if w.status == 0 {
w.WriteHeader(http.StatusInternalServerError)
}
span.AddEvent("Panic recovered",
trace.WithStackTrace(true),
)
span.SetStatus(codes.Error, "Panic recovered")
}
lg.Debug("Request",
zap.Duration("duration", time.Since(start)),
zap.Int("http.status", w.status),
zap.Int64("http.response.size", w.wrote),
zap.String("http.path", r.URL.String()),
zap.String("http.method", r.Method),
)
}()
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func newServer() *http.Server {
h, err := oas.NewServer(h,
oas.WithMeterProvider(m.MeterProvider()),
oas.WithTracerProvider(m.TracerProvider()),
)
if err != nil {
return errors.Wrap(err, "oas server")
}
return &http.Server{
Handler: middleware.Wrap(h, TraceMiddleware(lg, m.TracerProvider())),
Addr: addr,
}
}
See Static router section for more details.
How to use prometheus?
To instrument server or client with OpenTelemetry and prometheus exporter, pass oas.Option
:
package main
import (
"github.com/go-faster/errors"
promClient "github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram"
controller "go.opentelemetry.io/otel/sdk/metric/controller/basic"
processor "go.opentelemetry.io/otel/sdk/metric/processor/basic"
selector "go.opentelemetry.io/otel/sdk/metric/selector/simple"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"application/oas"
)
// Resource returns new resource for application.
func Resource(name string) *resource.Resource {
r, _ := resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(name),
),
)
return r
}
func newPrometheus(config prometheus.Config, options ...controller.Option) (*prometheus.Exporter, error) {
c := controller.New(
processor.NewFactory(
selector.NewWithHistogramDistribution(
histogram.WithExplicitBoundaries(config.DefaultHistogramBoundaries),
),
aggregation.CumulativeTemporalitySelector(),
processor.WithMemory(true),
),
options...,
)
return prometheus.New(config, c)
}
// newMeterProviderOption returns oas.Option for prometheus instrumentation.
func newMeterProviderOption() (oas.Option, error) {
registry := promClient.NewPedanticRegistry()
res := Resource("app")
promExporter, err := newPrometheus(prometheus.Config{
DefaultHistogramBoundaries: promClient.DefBuckets,
Registry: registry,
Gatherer: registry,
Registerer: registry,
},
controller.WithCollectPeriod(0),
controller.WithResource(res),
)
if err != nil {
return nil, errors.Wrap(err, "prometheus")
}
return oas.WithMeterProvider(promExporter.MeterProvider()), nil
}