Working dev and health endpoint
parent
f5e555b7ff
commit
1d3c1de858
@ -0,0 +1,6 @@
|
||||
|
||||
```
|
||||
[build]
|
||||
kill_delay = "1s"
|
||||
send_interrupt = true
|
||||
```
|
@ -0,0 +1 @@
|
||||
main
|
@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (app *application) HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "status: available!")
|
||||
fmt.Fprintf(w, "environment: %s\n", app.config.env)
|
||||
fmt.Fprintf(w, "version: %s\n", Version)
|
||||
}
|
@ -1,15 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.runcible.io/learning/pulley/internal/logging"
|
||||
)
|
||||
|
||||
func Run(w io.Writer) {
|
||||
fmt.Fprint(w, "Hello API\n")
|
||||
const Version = "1.0.0"
|
||||
|
||||
type config struct {
|
||||
port int
|
||||
env string
|
||||
logLevel string
|
||||
}
|
||||
|
||||
type application struct {
|
||||
config config
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func run(ctx context.Context, w io.Writer, args []string) error {
|
||||
var cfg config
|
||||
|
||||
flagSet := flag.NewFlagSet(args[0], flag.ExitOnError)
|
||||
|
||||
flagSet.IntVar(&cfg.port, "port", 5002, "API server port")
|
||||
flagSet.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)")
|
||||
flagSet.StringVar(&cfg.logLevel, "log-level", "INFO", "Logging Level (INFO|DEBUG|WARN|ERROR)")
|
||||
|
||||
if err := flagSet.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger := logging.InitLogging(cfg.logLevel, w, true)
|
||||
app := application{config: cfg, logger: logger}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/v1/healthcheck", app.HealthCheckHandler)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", "0.0.0.0", app.config.port),
|
||||
ErrorLog: slog.NewLogLogger(app.logger.Handler(), slog.LevelError),
|
||||
IdleTimeout: time.Minute,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxHeaderBytes: 524288, // 0.5 mb
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
slog.Info(fmt.Sprintf("Listening on http://%s", srv.Addr))
|
||||
|
||||
defer func() {
|
||||
slog.Debug("Must have hit a graceful shutdown")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
slog.Error(err.Error())
|
||||
os.Exit(1)
|
||||
slog.Info("Stopped serving connections")
|
||||
}
|
||||
}()
|
||||
|
||||
// Handle graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
// Block until signal is received
|
||||
<-sigChan
|
||||
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer shutdownRelease()
|
||||
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
slog.Error("Failed to close within context timeout. Forcing server close.")
|
||||
srv.Close()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
Run(os.Stdout)
|
||||
ctx := context.Background()
|
||||
if err := run(ctx, os.Stdout, os.Args); err != nil {
|
||||
slog.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
buffer := bytes.Buffer{}
|
||||
Run(&buffer)
|
||||
got := buffer.String()
|
||||
want := "Hello API\n"
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
// buffer := bytes.Buffer{}
|
||||
// run(&buffer)
|
||||
// got := buffer.String()
|
||||
// want := "Hello API\n"
|
||||
// if got != want {
|
||||
// t.Errorf("got %q, want %q", got, want)
|
||||
// }
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
module git.runcible.io/learning/pulley
|
||||
|
||||
go 1.24.1
|
||||
//go 1.24.1
|
||||
go 1.23.3
|
@ -0,0 +1,37 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseLogLevel(levelStr string) slog.Level {
|
||||
switch strings.ToUpper(levelStr) {
|
||||
case "DEBUG":
|
||||
return slog.LevelDebug
|
||||
case "INFO":
|
||||
return slog.LevelInfo
|
||||
case "WARN":
|
||||
return slog.LevelWarn
|
||||
case "ERROR":
|
||||
return slog.LevelError
|
||||
default:
|
||||
return slog.LevelInfo // Default level
|
||||
}
|
||||
}
|
||||
|
||||
// InitLogggin initializes global structured logging for the entire application
|
||||
func InitLogging(level string, w io.Writer, addSource bool) *slog.Logger {
|
||||
// Use os.Stderr
|
||||
//
|
||||
// Stderr is used for diagnostics and logging. Stdout is used for program
|
||||
// output. Stderr also have greater likely hood of being seen if a programs
|
||||
// output is being redirected.
|
||||
parsedLogLevel := parseLogLevel(level)
|
||||
loggerHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: parsedLogLevel, AddSource: addSource})
|
||||
logger := slog.New(loggerHandler)
|
||||
slog.SetDefault(logger)
|
||||
return logger
|
||||
}
|
Loading…
Reference in New Issue