server: server now starts properly

This commit is contained in:
Tigor Hutasuhut 2024-04-07 16:06:33 +07:00
parent 94e5a3b127
commit 7de31e87d5
7 changed files with 109 additions and 20 deletions

View file

@ -1,20 +1,34 @@
package cli package cli
import ( import (
"os"
"os/signal"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tigorlazuardi/redmage/pkg/log" "github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/server"
) )
var serveCmd = &cobra.Command{ var serveCmd = &cobra.Command{
Use: "serve", Use: "serve",
Short: "Starts the HTTP Server", Short: "Starts the HTTP Server",
SilenceUsage: true, SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error { Run: func(cmd *cobra.Command, args []string) {
hostPort := cfg.String("http.host") + ":" + cfg.String("http.port") server := server.New(cfg)
log.Log(cmd.Context()).Info("starting http server", "host", hostPort) exit := make(chan struct{}, 1)
return nil go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
<-sig
exit <- struct{}{}
}()
if err := server.Start(exit); err != nil {
log.Log(cmd.Context()).Err(err).Error("failed to start server")
os.Exit(1)
}
}, },
} }

View file

@ -13,4 +13,5 @@ var DefaultConfig = map[string]any{
"http.port": "8080", "http.port": "8080",
"http.host": "0.0.0.0", "http.host": "0.0.0.0",
"http.shutdown_timeout": "5s",
} }

View file

@ -16,6 +16,17 @@ func (ca Caller) File() string {
return ca.Frame.File return ca.Frame.File
} }
func (ca Caller) ShortFile() string {
wd, err := os.Getwd()
if err != nil {
return ca.Frame.File
}
if after, found := strings.CutPrefix(ca.Frame.File, wd); found {
return strings.TrimPrefix(after, string(os.PathSeparator))
}
return ca.Frame.File
}
func (ca Caller) Line() int { func (ca Caller) Line() int {
return ca.Frame.Line return ca.Frame.Line
} }
@ -35,7 +46,7 @@ func (ca Caller) LogValue() slog.Value {
} }
return slog.GroupValue( return slog.GroupValue(
slog.String("file", ca.File()), slog.String("file", ca.ShortFile()),
slog.Int("line", ca.Line()), slog.Int("line", ca.Line()),
slog.String("function", ca.ShortFunction()), slog.String("function", ca.ShortFunction()),
) )

View file

@ -139,11 +139,18 @@ func (er *Err) GetDetails() []any {
} }
func (er *Err) Log(ctx context.Context) Error { func (er *Err) Log(ctx context.Context) Error {
log.Log(ctx).Caller(er.caller).Error(er.message, "error", er) log.Log(ctx).Caller(er.caller).Err(er).Error(er.message)
return er return er
} }
func Wrap(err error, message string, details ...any) Error { func Wrap(err error) Error {
return &Err{
origin: err,
caller: caller.New(3),
}
}
func Wrapw(err error, message string, details ...any) Error {
return &Err{ return &Err{
origin: err, origin: err,
details: details, details: details,

View file

@ -49,6 +49,7 @@ type Entry struct {
handler slog.Handler handler slog.Handler
caller caller.Caller caller caller.Caller
time time.Time time time.Time
err error
} }
// Log prepares a new entry to write logs. // Log prepares a new entry to write logs.
@ -65,10 +66,18 @@ func (entry *Entry) Caller(caller caller.Caller) *Entry {
return entry return entry
} }
func (entry *Entry) Err(err error) *Entry {
entry.err = err
return entry
}
func (entry *Entry) Info(message string, fields ...any) { func (entry *Entry) Info(message string, fields ...any) {
record := slog.NewRecord(entry.time, slog.LevelInfo, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelInfo, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
record.AddAttrs(slog.Group("context", fields...)) record.AddAttrs(slog.Group("details", fields...))
if entry.err != nil {
record.AddAttrs(slog.Any("error", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }
@ -76,13 +85,19 @@ func (entry *Entry) Infof(format string, args ...any) {
message := fmt.Sprintf(format, args...) message := fmt.Sprintf(format, args...)
record := slog.NewRecord(entry.time, slog.LevelInfo, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelInfo, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
if entry.err != nil {
record.AddAttrs(slog.Any("error", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }
func (entry *Entry) Error(message string, fields ...any) { func (entry *Entry) Error(message string, fields ...any) {
record := slog.NewRecord(entry.time, slog.LevelError, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelError, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
record.AddAttrs(slog.Group("context", fields...)) record.AddAttrs(slog.Group("details", fields...))
if entry.err != nil {
record.AddAttrs(slog.Any("error", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }
@ -90,13 +105,19 @@ func (entry *Entry) Errorf(format string, args ...any) {
message := fmt.Sprintf(format, args...) message := fmt.Sprintf(format, args...)
record := slog.NewRecord(entry.time, slog.LevelError, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelError, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
if entry.err != nil {
record.AddAttrs(slog.Any("details", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }
func (entry *Entry) Debug(message string, fields ...any) { func (entry *Entry) Debug(message string, fields ...any) {
record := slog.NewRecord(entry.time, slog.LevelDebug, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelDebug, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
record.AddAttrs(slog.Group("context", fields...)) record.AddAttrs(slog.Group("details", fields...))
if entry.err != nil {
record.AddAttrs(slog.Any("error", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }
@ -104,13 +125,19 @@ func (entry *Entry) Debugf(format string, args ...any) {
message := fmt.Sprintf(format, args...) message := fmt.Sprintf(format, args...)
record := slog.NewRecord(entry.time, slog.LevelDebug, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelDebug, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
if entry.err != nil {
record.AddAttrs(slog.Any("error", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }
func (entry *Entry) Warn(message string, fields ...any) { func (entry *Entry) Warn(message string, fields ...any) {
record := slog.NewRecord(entry.time, slog.LevelWarn, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelWarn, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
record.AddAttrs(slog.Group("context", fields...)) record.AddAttrs(slog.Group("details", fields...))
if entry.err != nil {
record.AddAttrs(slog.Any("error", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }
@ -118,6 +145,9 @@ func (entry *Entry) Warnf(format string, args ...any) {
message := fmt.Sprintf(format, args...) message := fmt.Sprintf(format, args...)
record := slog.NewRecord(entry.time, slog.LevelWarn, message, entry.getCaller().PC) record := slog.NewRecord(entry.time, slog.LevelWarn, message, entry.getCaller().PC)
record.AddAttrs(entry.getExtra()...) record.AddAttrs(entry.getExtra()...)
if entry.err != nil {
record.AddAttrs(slog.Any("error", entry.err))
}
_ = entry.handler.Handle(entry.ctx, record) _ = entry.handler.Handle(entry.ctx, record)
} }

View file

@ -118,10 +118,8 @@ func (pr *PrettyHandler) Handle(ctx context.Context, record slog.Record) error {
_ = serializer.Handle(ctx, record) _ = serializer.Handle(ctx, record)
if jsonBuf.Len() > 3 { // Ignore empty json like "{}\n" if jsonBuf.Len() > 3 { // Ignore empty json like "{}\n"
_ = json.Indent(buf, jsonBuf.Bytes(), "", " ") _ = json.Indent(buf, jsonBuf.Bytes(), "", " ")
// json indent includes new line, no need to add extra new line.
} else {
buf.WriteByte('\n')
} }
buf.WriteByte('\n')
pr.mu.Lock() pr.mu.Lock()
defer pr.mu.Unlock() defer pr.mu.Unlock()

View file

@ -1,19 +1,44 @@
package server package server
import ( import (
"context"
"errors"
"net/http" "net/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/tigorlazuardi/redmage/config" "github.com/tigorlazuardi/redmage/config"
"github.com/tigorlazuardi/redmage/pkg/caller"
"github.com/tigorlazuardi/redmage/pkg/errs"
"github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/server/routes/api" "github.com/tigorlazuardi/redmage/server/routes/api"
"github.com/tigorlazuardi/redmage/server/routes/htmx" "github.com/tigorlazuardi/redmage/server/routes/htmx"
) )
type Server struct { type Server struct {
handler http.Handler server *http.Server
config *config.Config
} }
func (svr *Server) Serve() { func (srv *Server) Start(exit <-chan struct{}) error {
errch := make(chan error, 1)
caller := caller.New(3)
go func() {
log.Log(context.Background()).Caller(caller).Info("starting http server", "address", "http://"+srv.server.Addr)
errch <- srv.server.ListenAndServe()
}()
select {
case <-exit:
log.Log(context.Background()).Caller(caller).Info("received exit signal. shutting down server")
ctx, cancel := context.WithTimeout(context.Background(), srv.config.Duration("http.shutdown_timeout"))
defer cancel()
return srv.server.Shutdown(ctx)
case err := <-errch:
if errors.Is(err, http.ErrServerClosed) {
return nil
}
return errs.Wrap(err)
}
} }
func New(cfg *config.Config) *Server { func New(cfg *config.Config) *Server {
@ -23,7 +48,10 @@ func New(cfg *config.Config) *Server {
router.Route("/htmx", htmx.Register) router.Route("/htmx", htmx.Register)
return &Server{ server := &http.Server{
handler: router, Handler: router,
Addr: cfg.String("http.host") + ":" + cfg.String("http.port"),
} }
return &Server{server: server, config: cfg}
} }