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
import (
"os"
"os/signal"
"github.com/spf13/cobra"
"github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/server"
)
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Starts the HTTP Server",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
hostPort := cfg.String("http.host") + ":" + cfg.String("http.port")
Run: func(cmd *cobra.Command, args []string) {
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

@ -11,6 +11,7 @@ var DefaultConfig = map[string]any{
"db.string": "data.db",
"db.automigrate": true,
"http.port": "8080",
"http.host": "0.0.0.0",
"http.port": "8080",
"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
}
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 {
return ca.Frame.Line
}
@ -35,7 +46,7 @@ func (ca Caller) LogValue() slog.Value {
}
return slog.GroupValue(
slog.String("file", ca.File()),
slog.String("file", ca.ShortFile()),
slog.Int("line", ca.Line()),
slog.String("function", ca.ShortFunction()),
)

View file

@ -139,11 +139,18 @@ func (er *Err) GetDetails() []any {
}
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
}
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{
origin: err,
details: details,

View file

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

View file

@ -1,19 +1,44 @@
package server
import (
"context"
"errors"
"net/http"
"github.com/go-chi/chi/v5"
"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/htmx"
)
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 {
@ -23,7 +48,10 @@ func New(cfg *config.Config) *Server {
router.Route("/htmx", htmx.Register)
return &Server{
handler: router,
server := &http.Server{
Handler: router,
Addr: cfg.String("http.host") + ":" + cfg.String("http.port"),
}
return &Server{server: server, config: cfg}
}