Skip to content

Commit f0ca2ea

Browse files
committed
initial scaffold
Signed-off-by: amanycodes <[email protected]>
0 parents  commit f0ca2ea

File tree

13 files changed

+424
-0
lines changed

13 files changed

+424
-0
lines changed

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Binaries
2+
bin/
3+
*.exe
4+
*.out
5+
*.test
6+
7+
# Build cache
8+
*.log
9+
*.swp
10+
*.tmp
11+
12+
# Vendor / deps
13+
vendor/
14+
15+
# OS files
16+
.DS_Store
17+
Thumbs.db
18+
19+
# IDE
20+
.idea/
21+
.vscode/
22+
*.iml
23+
24+
# Docker
25+
*.tar

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
APP=pine-gate
2+
PKG=./...
3+
IMG?=amanycodes/pine-gate:dev
4+
5+
.PHONY: build run test docker kind
6+
7+
build:
8+
go build -o bin/$(APP) ./cmd/gateway
9+
10+
run:
11+
CONFIG_FILE=./configs/config.example.yaml go run ./cmd/gateway
12+
13+
test:
14+
go test $(PKG) -v
15+
16+
docker:
17+
docker build -t $(IMG) -f deployments/docker/Dockerfile .
18+
19+
kind:
20+
kind create cluster --name aigw || true
21+
kubectl apply -f deployments/k8s/

cmd/gateway/main.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"net/http"
7+
"os"
8+
"time"
9+
10+
"github.com/go-chi/chi/v5"
11+
"github.com/prometheus/client_golang/prometheus/promhttp"
12+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
13+
14+
"github.com/amanycoes/pine-gate/pkg/api"
15+
"github.com/amanycoes/pine-gate/pkg/middlewares"
16+
"github.com/amanycoes/pine-gate/pkg/telemetry"
17+
)
18+
19+
func main() {
20+
cfgFile := envOr("CONFIG_FILE", "./configs/config.example.yaml")
21+
22+
logger := telemetry.NewLogger() // zap logger
23+
defer logger.Sync()
24+
tp := telemetry.InitTracing("go-ai-gateway") // minimal OTel provider
25+
defer func() { _ = tp.Shutdown(context.Background()) }()
26+
metrics := telemetry.InitMetrics() // registers default collectors
27+
28+
r := chi.NewRouter()
29+
r.Use(middlewares.Recoverer(logger))
30+
r.Use(middlewares.RequestLogger(logger))
31+
r.Route("/", func(r chi.Router) {
32+
// public
33+
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
34+
w.WriteHeader(http.StatusOK)
35+
_, _ = w.Write([]byte("ok"))
36+
})
37+
r.Handle("/metrics", promhttp.Handler())
38+
})
39+
40+
// protected
41+
r.Group(func(priv chi.Router) {
42+
priv.Use(middlewares.APIKeyAuth(envOr("API_KEY", "dev-key")))
43+
priv.Post("/v1/completions", api.HandleCompletions(metrics))
44+
priv.Get("/v1/stream", api.HandleSSEStream(metrics))
45+
})
46+
47+
addr := ":8080"
48+
srv := &http.Server{
49+
Addr: addr,
50+
Handler: otelhttp.NewHandler(r, "gateway-http"), // trace all HTTP
51+
ReadTimeout: 10 * time.Second,
52+
WriteTimeout: 60 * time.Second,
53+
IdleTimeout: 90 * time.Second,
54+
}
55+
logger.Info("starting gateway", telemetry.Field("addr", addr), telemetry.Field("cfg", cfgFile))
56+
log.Fatal(srv.ListenAndServe())
57+
}
58+
59+
func envOr(k, def string) string {
60+
if v := os.Getenv(k); v != "" {
61+
return v
62+
}
63+
return def
64+
}

deployments/docker/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM golang:1.22 AS build
2+
WORKDIR /app
3+
COPY . .
4+
RUN CGO_ENABLED=0 GOOS=linux go build -o /go-ai-gateway ./cmd/gateway
5+
6+
FROM gcr.io/distroless/base-debian12
7+
WORKDIR /
8+
COPY --from=build /go-ai-gateway /go-ai-gateway
9+
COPY configs/config.example.yaml /config.yaml
10+
EXPOSE 8080
11+
ENV CONFIG_FILE=/config.yaml
12+
ENTRYPOINT ["/go-ai-gateway"]

go.mod

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module github.com/amanycoes/pine-gate
2+
3+
go 1.24.2
4+
5+
require (
6+
github.com/go-chi/chi/v5 v5.2.3
7+
github.com/prometheus/client_golang v1.23.2
8+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
9+
go.opentelemetry.io/otel v1.38.0
10+
go.opentelemetry.io/otel/sdk v1.38.0
11+
go.opentelemetry.io/otel/trace v1.38.0
12+
go.uber.org/zap v1.27.0
13+
)
14+
15+
require (
16+
github.com/beorn7/perks v1.0.1 // indirect
17+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
18+
github.com/felixge/httpsnoop v1.0.4 // indirect
19+
github.com/go-logr/logr v1.4.3 // indirect
20+
github.com/go-logr/stdr v1.2.2 // indirect
21+
github.com/google/uuid v1.6.0 // indirect
22+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
23+
github.com/prometheus/client_model v0.6.2 // indirect
24+
github.com/prometheus/common v0.66.1 // indirect
25+
github.com/prometheus/procfs v0.16.1 // indirect
26+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
27+
go.opentelemetry.io/otel/metric v1.38.0 // indirect
28+
go.uber.org/multierr v1.10.0 // indirect
29+
go.yaml.in/yaml/v2 v2.4.2 // indirect
30+
golang.org/x/sys v0.35.0 // indirect
31+
google.golang.org/protobuf v1.36.8 // indirect
32+
)

go.sum

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
3+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
4+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
8+
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
9+
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
10+
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
11+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
12+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
13+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
14+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
15+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
16+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
17+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
18+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
19+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
20+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
21+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
22+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
23+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
24+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
25+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
26+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
27+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
28+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
29+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
30+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
31+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
32+
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
33+
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
34+
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
35+
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
36+
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
37+
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
38+
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
39+
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
40+
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
41+
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
42+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
43+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
44+
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
45+
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
46+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
47+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
48+
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
49+
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
50+
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
51+
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
52+
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
53+
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
54+
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
55+
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
56+
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
57+
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
58+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
59+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
60+
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
61+
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
62+
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
63+
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
64+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
65+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
66+
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
67+
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
68+
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
69+
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
70+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
71+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
72+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
73+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
74+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/api/handlers.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"time"
8+
9+
"github.com/amanycoes/pine-gate/pkg/telemetry"
10+
)
11+
12+
type completionReq struct {
13+
Model string `json:"model"`
14+
Prompt string `json:"prompt"`
15+
}
16+
17+
type completionResp struct {
18+
Model string `json:"model"`
19+
Output string `json:"output"`
20+
}
21+
22+
func HandleCompletions(m *telemetry.Metrics) http.HandlerFunc {
23+
return func(w http.ResponseWriter, r *http.Request) {
24+
timer := prometheusObserve(m, "/v1/completions", r.Method)
25+
defer timer()
26+
27+
var req completionReq
28+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
29+
recordErr(m, "/v1/completions", r.Method, http.StatusBadRequest)
30+
http.Error(w, "bad request", http.StatusBadRequest)
31+
return
32+
}
33+
resp := completionResp{
34+
Model: ifEmpty(req.Model, "dummy"),
35+
Output: fmt.Sprintf("echo: %s", req.Prompt),
36+
}
37+
_ = json.NewEncoder(w).Encode(resp)
38+
}
39+
}
40+
41+
func HandleSSEStream(m *telemetry.Metrics) http.HandlerFunc {
42+
return func(w http.ResponseWriter, r *http.Request) {
43+
timer := prometheusObserve(m, "/v1/stream", r.Method)
44+
defer timer()
45+
46+
w.Header().Set("Content-Type", "text/event-stream")
47+
w.Header().Set("Cache-Control", "no-cache")
48+
w.Header().Set("Connection", "keep-alive")
49+
50+
for i := 0; i < 5; i++ {
51+
fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("chunk-%d", i))
52+
if fl, ok := w.(http.Flusher); ok {
53+
fl.Flush()
54+
}
55+
time.Sleep(300 * time.Millisecond)
56+
}
57+
}
58+
}
59+
60+
// helpers
61+
func prometheusObserve(m *telemetry.Metrics, route, method string) func() {
62+
start := time.Now()
63+
m.ReqTotal.WithLabelValues(route, method).Inc()
64+
return func() {
65+
m.ReqLatency.WithLabelValues(route, method).Observe(time.Since(start).Seconds())
66+
}
67+
}
68+
func recordErr(m *telemetry.Metrics, route, method string, code int) {
69+
m.ReqErrors.WithLabelValues(route, method, fmt.Sprint(code)).Inc()
70+
}
71+
func ifEmpty(s, def string) string {
72+
if s == "" {
73+
return def
74+
}
75+
return s
76+
}

pkg/middlewares/auth.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package middlewares
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
func APIKeyAuth(expected string) func(http.Handler) http.Handler {
8+
return func(next http.Handler) http.Handler {
9+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
10+
key := r.Header.Get("x-api-key")
11+
if key == "" || key != expected {
12+
http.Error(w, "unauthorized", http.StatusUnauthorized)
13+
return
14+
}
15+
next.ServeHTTP(w, r)
16+
})
17+
}
18+
}

pkg/middlewares/logging.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package middlewares
2+
3+
import (
4+
"net/http"
5+
"time"
6+
7+
"go.uber.org/zap"
8+
)
9+
10+
func RequestLogger(logger *zap.Logger) func(http.Handler) http.Handler {
11+
return func(next http.Handler) http.Handler {
12+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13+
start := time.Now()
14+
next.ServeHTTP(w, r)
15+
logger.Info("request",
16+
zap.String("method", r.Method),
17+
zap.String("path", r.URL.Path),
18+
zap.Duration("dur", time.Since(start)),
19+
zap.String("ua", r.UserAgent()))
20+
})
21+
}
22+
}

pkg/middlewares/recover.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package middlewares
2+
3+
import (
4+
"net/http"
5+
6+
"go.uber.org/zap"
7+
)
8+
9+
func Recoverer(logger *zap.Logger) func(http.Handler) http.Handler {
10+
return func(next http.Handler) http.Handler {
11+
fn := func(w http.ResponseWriter, r *http.Request) {
12+
defer func() {
13+
if rec := recover(); rec != nil {
14+
logger.Error("panic", zap.Any("err", rec))
15+
http.Error(w, "internal error", http.StatusInternalServerError)
16+
}
17+
}()
18+
next.ServeHTTP(w, r)
19+
}
20+
return http.HandlerFunc(fn)
21+
}
22+
}

0 commit comments

Comments
 (0)