From 275523a8ea1793f3aa9437e1d8ec3e9fcf0c7917 Mon Sep 17 00:00:00 2001 From: Mike Gouline <1960272+gouline@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:00:28 +1000 Subject: [PATCH] Zap logging throughout --- go.mod | 5 ++ go.sum | 49 ++++++++++++++++++ internal/pkg/server/server.go | 65 ++++++++++++++++++++---- internal/pkg/slack/context.go | 8 +-- internal/pkg/slack/handlers.go | 2 +- internal/pkg/slack/slack.go | 27 +++++++--- internal/pkg/templates/templates.go | 39 +++++++++++--- internal/pkg/templates/templates_test.go | 8 ++- main.go | 32 ++++++++++-- 9 files changed, 200 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index c2c51bf..e2d31f8 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,12 @@ require ( github.com/labstack/echo/v4 v4.12.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/slack-go/slack v0.14.0 + github.com/sykesm/zap-logfmt v0.0.4 + go.uber.org/zap v1.27.0 ) require ( + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -16,8 +19,10 @@ require ( github.com/stretchr/testify v1.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + go.uber.org/multierr v1.10.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 4f3fa04..7766b69 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,20 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -17,27 +26,67 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/slack-go/slack v0.14.0 h1:6c0UTfbRnvRssZUsZ2qe0Iu07VAMPjRqOa6oX8ewF4k= github.com/slack-go/slack v0.14.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/sykesm/zap-logfmt v0.0.4 h1:U2WzRvmIWG1wDLCFY3sz8UeEmsdHQjHFNlIdmroVFaI= +github.com/sykesm/zap-logfmt v0.0.4/go.mod h1:AuBd9xQjAe3URrWT1BBDk2v2onAZHkZkWRMiYZXiZWA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/pkg/server/server.go b/internal/pkg/server/server.go index aad224e..f616633 100644 --- a/internal/pkg/server/server.go +++ b/internal/pkg/server/server.go @@ -2,10 +2,13 @@ package server import ( "fmt" + "os" "github.com/gouline/blaster/internal/pkg/slack" "github.com/gouline/blaster/internal/pkg/templates" "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "go.uber.org/zap" ) const ( @@ -13,6 +16,8 @@ const ( ) type Config struct { + Logger *zap.Logger + Debug bool Host string @@ -34,27 +39,62 @@ type Server struct { } func New(config Config) (*Server, error) { + var err error s := &Server{ config: config, echo: echo.New(), - slack: slack.New(config.SlackClientID, config.SlackClientSecret), } s.echo.Debug = config.Debug - // Slack auth - s.echo.Use(s.slack.Middleware) - s.echo.GET("/login", s.slack.HandleLogin) - s.echo.GET("/logout", s.slack.HandleLogout) + s.slack, err = slack.New(slack.Config{ + Logger: config.Logger, + ClientID: config.SlackClientID, + ClientSecret: config.SlackClientSecret, + }) + if err != nil { + return s, fmt.Errorf("failed to init Slack: %w", err) + } + + s.echo.Use(middleware.Recover()) + s.echo.Use(middleware.Gzip()) + if s.echo.Debug { + s.echo.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogURI: true, + LogStatus: true, + LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { + config.Logger.Info("request", + zap.String("URI", v.URI), + zap.Int("status", v.Status), + ) + return nil + }, + })) + } + // Static + if f, err := os.Stat(config.StaticRoot); os.IsNotExist(err) { + return s, fmt.Errorf("static not found: %w", err) + } else if err == nil && !f.IsDir() { + return s, fmt.Errorf("static not directory") + } s.echo.Static("/static", config.StaticRoot) - var err error - s.echo.Renderer, err = templates.New(config.TemplatesRoot, "layout.html") + // Templates + s.echo.Renderer, err = templates.New(templates.Config{ + Logger: config.Logger, + RootPath: config.TemplatesRoot, + LayoutFile: "layout.html", + }) if err != nil { - return nil, err + return nil, fmt.Errorf("templates parsing failed: %w", err) } + // Slack auth + s.echo.Use(s.slack.Middleware) + s.echo.GET("/login", s.slack.HandleLogin) + s.echo.GET("/logout", s.slack.HandleLogout) + // Pages s.echo.GET("/", s.handleIndex) s.echo.RouteNotFound("/*", s.handleNotFound) @@ -67,7 +107,14 @@ func New(config Config) (*Server, error) { return s, nil } -func (s *Server) Run() error { +// Start starts HTTP or HTTPS server, depending on the presence of cert/key. +func (s *Server) Start() error { + s.config.Logger.Info("Starting server", + zap.String("host", s.config.Host), + zap.String("port", s.config.Port), + zap.String("certFile", s.config.CertFile), + zap.String("keyFile", s.config.KeyFile)) + addr := fmt.Sprintf("%s:%s", s.config.Host, s.config.Port) if s.config.CertFile != "" && s.config.KeyFile != "" { return s.echo.StartTLS(addr, s.config.CertFile, s.config.KeyFile) diff --git a/internal/pkg/slack/context.go b/internal/pkg/slack/context.go index 3656ed2..8bf4b74 100644 --- a/internal/pkg/slack/context.go +++ b/internal/pkg/slack/context.go @@ -34,10 +34,10 @@ func (s *Slack) Context(c echo.Context) *Context { teamInfo, err := client.GetTeamInfo() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get team info: %w", err) } - return teamInfo.Name, err + return teamInfo.Name, nil }) if cacheResponse.Error == nil { ctx.TeamName = cacheResponse.Value.(string) @@ -96,7 +96,7 @@ func buildDestinationCache(token string) <-chan scache.Response { // Get all users users, err := client.GetUsers() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get users: %w", err) } destinations = []*Destination{} @@ -119,7 +119,7 @@ func buildDestinationCache(token string) <-chan scache.Response { usergroups, err := client.GetUserGroups(slack.GetUserGroupsOptionIncludeUsers(true)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get user groups: %w", err) } for _, usergroup := range usergroups { diff --git a/internal/pkg/slack/handlers.go b/internal/pkg/slack/handlers.go index 487a9db..77a38e5 100644 --- a/internal/pkg/slack/handlers.go +++ b/internal/pkg/slack/handlers.go @@ -12,7 +12,7 @@ func (s *Slack) Middleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if code := c.QueryParam("code"); code != "" { redirectURI := redirectURI(c, c.Request().RequestURI) - response, err := slack.GetOAuthResponse(http.DefaultClient, s.clientID, s.clientSecret, code, redirectURI) + response, err := slack.GetOAuthResponse(http.DefaultClient, s.config.ClientID, s.config.ClientSecret, code, redirectURI) if err != nil { return c.String(http.StatusUnauthorized, err.Error()) } diff --git a/internal/pkg/slack/slack.go b/internal/pkg/slack/slack.go index d872c9f..04f23a9 100644 --- a/internal/pkg/slack/slack.go +++ b/internal/pkg/slack/slack.go @@ -1,11 +1,13 @@ package slack import ( + "fmt" "net/http" "net/url" "strings" "github.com/labstack/echo/v4" + "go.uber.org/zap" ) const ( @@ -24,16 +26,27 @@ var ( } ) +type Config struct { + Logger *zap.Logger + + ClientID string + ClientSecret string +} + type Slack struct { - clientID string - clientSecret string + config Config } -func New(clientID, clientSecret string) *Slack { - return &Slack{ - clientID: clientID, - clientSecret: clientSecret, +func New(config Config) (*Slack, error) { + s := &Slack{ + config: config, } + + if config.ClientID == "" || config.ClientSecret == "" { + return s, fmt.Errorf("missing Slack credentials") + } + + return s, nil } func (s *Slack) authorizeURI(redirectURI string) (string, error) { @@ -42,7 +55,7 @@ func (s *Slack) authorizeURI(redirectURI string) (string, error) { return "", err } q := redirectURL.Query() - q.Set("client_id", s.clientID) + q.Set("client_id", s.config.ClientID) q.Set("scope", strings.Join(scopes, ",")) q.Set("redirect_uri", redirectURI) redirectURL.RawQuery = q.Encode() diff --git a/internal/pkg/templates/templates.go b/internal/pkg/templates/templates.go index 90b3aa8..13aed88 100644 --- a/internal/pkg/templates/templates.go +++ b/internal/pkg/templates/templates.go @@ -9,39 +9,62 @@ import ( "path/filepath" "github.com/labstack/echo/v4" + "go.uber.org/zap" ) +type Config struct { + Logger *zap.Logger + + RootPath string + LayoutFile string +} + type Templates struct { + config Config templates map[string]*template.Template } // NewRenderer creates new renderer and parses templates directory recursively // Relative path including extension is used as template name. -func New(root string, layout string) (*Templates, error) { +func New(config Config) (*Templates, error) { t := &Templates{ + config: config, templates: map[string]*template.Template{}, } - if _, err := os.Stat(root); os.IsNotExist(err) { - return t, err + if f, err := os.Stat(config.RootPath); os.IsNotExist(err) { + return t, fmt.Errorf("root not found: %w", err) + } else if err == nil && !f.IsDir() { + return t, fmt.Errorf("root not directory") } - layoutPath := root + "/" + layout - layoutExt := filepath.Ext(layoutPath) + layoutPath := config.RootPath + "/" + config.LayoutFile + if f, err := os.Stat(layoutPath); os.IsNotExist(err) { + return t, fmt.Errorf("layout not found: %w", err) + } else if err == nil && f.IsDir() { + return t, fmt.Errorf("layout is directory") + } - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + err := filepath.WalkDir(config.RootPath, func(path string, d fs.DirEntry, err error) error { name := d.Name() - if d.IsDir() || name == layout || filepath.Ext(path) != layoutExt { + if d.IsDir() || name == config.LayoutFile { + return nil + } else if filepath.Ext(path) != filepath.Ext(layoutPath) { + t.config.Logger.Info("discarding template", zap.String("path", path)) return nil } t.templates[name] = template.Must(template.ParseFiles(layoutPath, path)) + t.config.Logger.Info("compiled template", zap.String("path", path)) return nil }) + if err != nil { + return t, fmt.Errorf("failed to walk root: %w", err) + } - return t, err + return t, nil } func (t *Templates) Render(w io.Writer, name string, data interface{}, c echo.Context) error { diff --git a/internal/pkg/templates/templates_test.go b/internal/pkg/templates/templates_test.go index 5158a2a..51fdb08 100644 --- a/internal/pkg/templates/templates_test.go +++ b/internal/pkg/templates/templates_test.go @@ -3,10 +3,16 @@ package templates import ( "net/http/httptest" "testing" + + "go.uber.org/zap" ) func Test_New(t *testing.T) { - templates, err := New("examples", "layout.html") + templates, err := New(Config{ + Logger: zap.Must(zap.NewDevelopment()), + RootPath: "examples", + LayoutFile: "layout.html", + }) if err != nil { t.Fatalf("unexpected error: %s", err) } diff --git a/main.go b/main.go index 8459f52..52584b1 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,37 @@ package main import ( "fmt" - "log" "os" + "time" "github.com/gouline/blaster/internal/pkg/server" + zaplogfmt "github.com/sykesm/zap-logfmt" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) func main() { + debug := os.Getenv("DEBUG") == "1" + + var logger *zap.Logger + if debug { + logger = zap.Must(zap.NewDevelopment()) + } else { + loggerConfig := zap.NewProductionEncoderConfig() + loggerConfig.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString(ts.UTC().Format(time.RFC3339)) + } + logger = zap.New(zapcore.NewCore( + zaplogfmt.NewEncoder(loggerConfig), + os.Stdout, + zapcore.DebugLevel, + )) + } + defer logger.Sync() + s, err := server.New(server.Config{ - Debug: os.Getenv("DEBUG") == "1", + Logger: logger, + Debug: debug, Host: os.Getenv("HOST"), Port: os.Getenv("PORT"), CertFile: os.Getenv("CERT_FILE"), @@ -21,8 +43,8 @@ func main() { SlackClientSecret: os.Getenv("SLACK_CLIENT_SECRET"), }) if err != nil { - panic(fmt.Sprintf("Failed to create server: %s", err)) + panic(fmt.Sprintf("failed to create server: %s", err)) } - - log.Fatal(s.Run()) + err = s.Start() + logger.Fatal("server stopped", zap.Error(err)) }