Skip to content

Commit

Permalink
fix: update newHandlerFn to take a logger to canonicalize logging for…
Browse files Browse the repository at this point in the history
… system integration
  • Loading branch information
jsteenb2 committed Sep 10, 2024
1 parent a2e9a5a commit 15e5221
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 64 deletions.
4 changes: 1 addition & 3 deletions examples/complex/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"log/slog"
"net/http"
"os"

fdk "github.com/CrowdStrike/foundry-fn-go"
)
Expand All @@ -17,8 +16,7 @@ func (c config) OK() error {
return nil
}

func newHandler(ctx context.Context, cfg config) fdk.Handler {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))
func newHandler(_ context.Context, logger *slog.Logger, cfg config) fdk.Handler {
mux := fdk.NewMux()

h := handler{
Expand Down
3 changes: 2 additions & 1 deletion examples/fn_config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"

fdk "github.com/CrowdStrike/foundry-fn-go"
Expand All @@ -16,7 +17,7 @@ func main() {
// newHandler here is showing how a config is integrated. It is using generics,
// so we can unmarshal the config into a concrete type and then validate it. The
// OK method is run to validate the contents of the config.
func newHandler(_ context.Context, cfg config) fdk.Handler {
func newHandler(context.Context, *slog.Logger, config) fdk.Handler {
mux := fdk.NewMux()
mux.Get("/foo", fdk.HandlerFn(func(ctx context.Context, r fdk.Request) fdk.Response {
return fdk.Response{
Expand Down
3 changes: 2 additions & 1 deletion examples/fn_no_config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"log/slog"
"net/http"

fdk "github.com/CrowdStrike/foundry-fn-go"
Expand All @@ -15,7 +16,7 @@ func main() {
// newHandlerWithCfg here is showcasing a handler that does not utilize a config, so
// it provides the SkipCfg as the config so no config load is attempted. This is the
// minority of functions.
func newHandler(context.Context, fdk.SkipCfg) fdk.Handler {
func newHandler(context.Context, *slog.Logger, fdk.SkipCfg) fdk.Handler {
mux := fdk.NewMux()
mux.Get("/", fdk.HandlerFn(func(ctx context.Context, r fdk.Request) fdk.Response {
return fdk.Response{
Expand Down
12 changes: 5 additions & 7 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import (
)

// Runner defines the runtime that executes the request/response handler lifecycle.
type Runner interface {
Run(ctx context.Context, logger *slog.Logger, h Handler)
}
type Runner func(ctx context.Context, newHandlerFn func(context.Context, *slog.Logger) Handler)

// RegisterRunner registers a runner.
func RegisterRunner(runnerType string, r Runner) {
Expand All @@ -21,7 +19,7 @@ func RegisterRunner(runnerType string, r Runner) {
runners[runnerType] = r
}

func run(ctx context.Context, logger *slog.Logger, h Handler) {
func run(ctx context.Context, newHandlerFn func(context.Context, *slog.Logger) Handler) {
rt := os.Getenv("CS_RUNNER_TYPE")
if rt == "" {
rt = "http"
Expand All @@ -32,9 +30,9 @@ func run(ctx context.Context, logger *slog.Logger, h Handler) {
panic(fmt.Sprintf("invalid RUNNER_TYPE provided: %q", rt))
}

r.Run(ctx, logger, h)
r(ctx, newHandlerFn)
}

var runners = map[string]Runner{
"http": new(runnerHTTP),
var runners = map[string]func(ctx context.Context, newHandlerFn func(context.Context, *slog.Logger) Handler){
"http": runHTTP,
}
64 changes: 35 additions & 29 deletions runner_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,40 @@ const (
mb = 1 << 20
)

type runnerHTTP struct{}
func runHTTP(ctx context.Context, newHandlerFn func(context.Context, *slog.Logger) Handler) {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))

handler := newHandlerFn(ctx, logger)

func (r *runnerHTTP) Run(ctx context.Context, logger *slog.Logger, h Handler) {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
mux.Handle("/", dispatchReq(logger, handler))

s := &http.Server{
Addr: fmt.Sprintf(":%d", port()),
Handler: mux,
MaxHeaderBytes: mb,
}
go func() {
select {
case <-ctx.Done():
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

logger.Info("shutting down HTTP server...")
if err := s.Shutdown(shutdownCtx); err != nil {
logger.Error("failed to shutdown server", "err", err)
}
}
}()

logger.Info("serving HTTP server on port " + strconv.Itoa(port()))
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("unexpected shutdown of server", "err", err)
}
}

func dispatchReq(logger *slog.Logger, handler Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer func() {
if n, err := io.Copy(io.Discard, req.Body); err != nil {
logger.Error("failed to drain request body", "err", err.Error(), "bytes_drained", n)
Expand All @@ -54,9 +83,9 @@ func (r *runnerHTTP) Run(ctx context.Context, logger *slog.Logger, h Handler) {
}()

const ctxKeyTraceID = "_traceid"
ctx = context.WithValue(ctx, ctxKeyTraceID, r.TraceID)
ctx := context.WithValue(req.Context(), ctxKeyTraceID, r.TraceID)

resp := h.Handle(ctx, r)
resp := handler.Handle(ctx, r)

if f, ok := resp.Body.(File); ok {
f = NormalizeFile(f)
Expand Down Expand Up @@ -94,30 +123,7 @@ func (r *runnerHTTP) Run(ctx context.Context, logger *slog.Logger, h Handler) {
if err != nil {
logger.Error("failed to write response", "err", err)
}
}))

s := &http.Server{
Addr: fmt.Sprintf(":%d", port()),
Handler: mux,
MaxHeaderBytes: mb,
}
go func() {
select {
case <-ctx.Done():
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

logger.Info("shutting down HTTP server...")
if err := s.Shutdown(shutdownCtx); err != nil {
logger.Error("failed to shutdown server", "err", err)
}
}
}()

logger.Info("serving HTTP server on port " + strconv.Itoa(port()))
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("unexpected shutdown of server", "err", err)
}
})
}

func toRequest(req *http.Request) (Request, func() error, error) {
Expand Down
15 changes: 8 additions & 7 deletions runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"io"
"log/slog"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -567,7 +568,7 @@ integer: 1`,
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

addr := newServer(ctx, t, func(ctx context.Context, cfg config) fdk.Handler {
addr := newServer(ctx, t, func(ctx context.Context, _ *slog.Logger, cfg config) fdk.Handler {
return tt.newHandlerFn(ctx, cfg)
})

Expand Down Expand Up @@ -720,7 +721,7 @@ integer: 1`,
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

addr := newServer(ctx, t, func(ctx context.Context, cfg config) fdk.Handler {
addr := newServer(ctx, t, func(ctx context.Context, _ *slog.Logger, cfg config) fdk.Handler {
return tt.newHandlerFn(ctx, cfg)
})

Expand Down Expand Up @@ -840,7 +841,7 @@ integer: 1`,
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

addr := newServer(ctx, t, func(ctx context.Context, cfg fdk.SkipCfg) fdk.Handler {
addr := newServer(ctx, t, func(ctx context.Context, _ *slog.Logger, cfg fdk.SkipCfg) fdk.Handler {
return tt.newHandlerFn(ctx, cfg)
})

Expand Down Expand Up @@ -1006,7 +1007,7 @@ integer: 1`,
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

addr := newServer(ctx, t, func(ctx context.Context, _ fdk.SkipCfg) fdk.Handler {
addr := newServer(ctx, t, func(ctx context.Context, _ *slog.Logger, _ fdk.SkipCfg) fdk.Handler {
return tt.newHandlerFn(ctx)
})

Expand Down Expand Up @@ -1250,7 +1251,7 @@ func decodeJSON(t testing.TB, b []byte, v any) {
}
}

func newServer[CFG fdk.Cfg](ctx context.Context, t *testing.T, newHandlerFn func(context.Context, CFG) fdk.Handler) string {
func newServer[CFG fdk.Cfg](ctx context.Context, t *testing.T, newHandlerFn func(context.Context, *slog.Logger, CFG) fdk.Handler) string {
t.Helper()

port := newIP(t)
Expand All @@ -1261,8 +1262,8 @@ func newServer[CFG fdk.Cfg](ctx context.Context, t *testing.T, newHandlerFn func
done := make(chan struct{})
go func() {
defer close(done)
fdk.Run(ctx, func(ctx context.Context, cfg CFG) fdk.Handler {
h := newHandlerFn(ctx, cfg)
fdk.Run(ctx, func(ctx context.Context, logger *slog.Logger, cfg CFG) fdk.Handler {
h := newHandlerFn(ctx, logger, cfg)
close(readyChan)
return h
})
Expand Down
31 changes: 15 additions & 16 deletions sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"log/slog"
"net/http"
"os"
"runtime/debug"
)

Expand All @@ -14,25 +13,25 @@ type Handler interface {
}

// Run is the meat and potatoes. This is the entrypoint for everything.
func Run[T Cfg](ctx context.Context, newHandlerFn func(_ context.Context, cfg T) Handler) {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))

var runFn Handler = HandlerFn(func(ctx context.Context, r Request) Response {
cfg, loadErr := readCfg[T](ctx)
if loadErr != nil {
if loadErr.err != nil {
logger.Error("failed to load config", "err", loadErr.err)
func Run[T Cfg](ctx context.Context, newHandlerFn func(context.Context, *slog.Logger, T) Handler) {
run(ctx, func(ctx context.Context, logger *slog.Logger) Handler {
var runFn Handler = HandlerFn(func(ctx context.Context, r Request) Response {
cfg, loadErr := readCfg[T](ctx)
if loadErr != nil {
if loadErr.err != nil {
logger.Error("failed to load config", "err", loadErr.err)
}
return ErrResp(loadErr.apiErr)
}
return ErrResp(loadErr.apiErr)
}

h := newHandlerFn(ctx, cfg)
h := newHandlerFn(ctx, logger, cfg)

return h.Handle(ctx, r)
})
runFn = recoverer(logger)(runFn)
return h.Handle(ctx, r)
})
runFn = recoverer(logger)(runFn)

run(ctx, logger, runFn)
return runFn
})
}

func recoverer(logger *slog.Logger) func(Handler) Handler {
Expand Down

0 comments on commit 15e5221

Please sign in to comment.