Skip to content

Commit

Permalink
Web-APP: Add gracefully shutdown, JSON structured logging
Browse files Browse the repository at this point in the history
  • Loading branch information
egasimov committed Jul 7, 2023
1 parent e76fa1d commit 4d8b911
Show file tree
Hide file tree
Showing 22 changed files with 140 additions and 20 deletions.
Empty file modified cmd/aznum2words-webapp/.env.local
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/converter-server.cfg.yaml
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/converterapi/converter-api.go
100644 → 100755
Empty file.
Empty file.
Empty file modified cmd/aznum2words-webapp/api/doc.go
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/health-server.cfg.yaml
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/healthapi/health-api.go
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/healthapi/healthapi-server.gen.go
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/models.cfg.yaml
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/models/models.gen.go
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/api/open-api-spec.yaml
100644 → 100755
Empty file.
57 changes: 49 additions & 8 deletions cmd/aznum2words-webapp/aznum2words-webapp.go
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package main

import (
"context"
"fmt"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/api/converterapi"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/api/healthapi"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/config"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/handler"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/logger"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/router"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

func main() {
config.LoadConfig()
logger := logger.Logger()
defer logger.Sync()

// Create an instance of our handler which satisfies the generated interface
var converterApi = new(converterapi.Api)
Expand All @@ -24,13 +33,45 @@ func main() {
routerPrometheus := router.NewPrometheusServer(routerApp)

go func() {
routerPrometheus.Logger.Fatal(routerPrometheus.Start(
config.GetConfig().GetMetricNetListenAddr()),
)
logger.
Info(fmt.Sprintf("prometheus server started at %s", config.GetConfig().GetAppNetListenAddr()))

if err := routerPrometheus.Start(config.GetConfig().GetMetricNetListenAddr()); err != nil && err != http.ErrServerClosed {
routerPrometheus.Logger.Fatal("shutting down the prometheus server")
}
}()

go func() {
logger.
Info(fmt.Sprintf("app server started at %s", config.GetConfig().GetAppNetListenAddr()))

if err := routerApp.Start(config.GetConfig().GetAppNetListenAddr()); err != nil && err != http.ErrServerClosed {
routerApp.Logger.Fatal("shutting down the app server")
}
}()

// And we serve HTTP until the world ends.
routerApp.Logger.Fatal(routerApp.Start(
config.GetConfig().GetAppNetListenAddr(),
))
// Wait for interrupt signal to gracefully shut down the server with a timeout of 10 seconds.
// Use a buffered channel to avoid missing signals as recommended for signal.Notify
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM, syscall.SIGKILL)
<-quit

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

if err := routerApp.Shutdown(ctx); err != nil {
logger.
Error(fmt.Sprintf("an error occurred when shutdown app server, err: %s", err))
} else {
logger.
Info("gracefully shut downed app server")
}

if err := routerPrometheus.Shutdown(ctx); err != nil {
logger.
Error(fmt.Sprintf("an error occurred when shutdown prometheus server, err: %s", err))
} else {
logger.
Info("gracefully shut downed prometheus server")
}
}
6 changes: 4 additions & 2 deletions cmd/aznum2words-webapp/config/config.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (

// Config holds a configuration variables.
type Config struct {
AppName string `arg:"-n, env:APP_NAME" default:"aznum2words-webapp"`
AppHost string `arg:"-h, env:APP_HOST" default:"0.0.0.0"`
AppPort string `arg:"-p, env:APP_PORT" default:"8080"`
MetricPort string `arg:"-m, env:METRIC_PORT" default:"9090"`
DeployEnv string `arg:"-d, env:DEPLOY_ENV" default:"local"`
}

func (this *Config) GetAppNetListenAddr() string {
Expand All @@ -35,9 +37,9 @@ func (this *Config) GetMetricNetListenAddr() string {
var conf *Config = &Config{}

// LoadConfig ...
func LoadConfig() {
func init() {
// Local development purpose
if env := os.Getenv(constant.DEPLOY_ENV_KEY); env == "" {
if env := os.Getenv(constant.DEPLOY_ENV_KEY); env == constant.LOCAL_ENVIRONMENT {
errLoad := godotenv.Load(".env.local")

if errLoad != nil {
Expand Down
4 changes: 4 additions & 0 deletions cmd/aznum2words-webapp/constant/config.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ package constant
const (
DEPLOY_ENV_KEY = "DEPLOY_ENV"
)

const (
LOCAL_ENVIRONMENT = "local"
)
Empty file modified cmd/aznum2words-webapp/constant/error.go
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/handler/handler.go
100644 → 100755
Empty file.
Empty file.
Empty file modified cmd/aznum2words-webapp/it/aznum2wordsclient/client.cfg.yaml
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/it/aznum2wordsclient/doc.go
100644 → 100755
Empty file.
Empty file modified cmd/aznum2words-webapp/it/e2e_test.go
100644 → 100755
Empty file.
47 changes: 47 additions & 0 deletions cmd/aznum2words-webapp/logger/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package logger

import (
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/config"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/constant"
"log"
"time"

"go.uber.org/zap"

Check failure on line 9 in cmd/aznum2words-webapp/logger/logging.go

View workflow job for this annotation

GitHub Actions / ubuntu-latest Go 1.19 Tests

no required module provides package go.uber.org/zap; to add it:

Check failure on line 9 in cmd/aznum2words-webapp/logger/logging.go

View workflow job for this annotation

GitHub Actions / macOS-latest Go 1.19 Tests

no required module provides package go.uber.org/zap; to add it:

Check failure on line 9 in cmd/aznum2words-webapp/logger/logging.go

View workflow job for this annotation

GitHub Actions / windows-latest Go 1.19 Tests

no required module provides package go.uber.org/zap; to add it:
"go.uber.org/zap/zapcore"

Check failure on line 10 in cmd/aznum2words-webapp/logger/logging.go

View workflow job for this annotation

GitHub Actions / ubuntu-latest Go 1.19 Tests

no required module provides package go.uber.org/zap/zapcore; to add it:

Check failure on line 10 in cmd/aznum2words-webapp/logger/logging.go

View workflow job for this annotation

GitHub Actions / macOS-latest Go 1.19 Tests

no required module provides package go.uber.org/zap/zapcore; to add it:

Check failure on line 10 in cmd/aznum2words-webapp/logger/logging.go

View workflow job for this annotation

GitHub Actions / windows-latest Go 1.19 Tests

no required module provides package go.uber.org/zap/zapcore; to add it:
)

var logger *zap.Logger

func init() {
encoderConfig := zap.NewProductionConfig()
encoderConfig.Level.SetLevel(zapcore.InfoLevel)
if config.GetConfig().DeployEnv == constant.LOCAL_ENVIRONMENT {
encoderConfig.Level.SetLevel(zapcore.DebugLevel)
}
encoderConfig.EncoderConfig.TimeKey = "timestamp"
encoderConfig.EncoderConfig.MessageKey = "message"
encoderConfig.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout(time.RFC3339Nano)

zapLogger, err := encoderConfig.Build()
if err != nil {
log.Fatalf("fail to build log. err: %s", err)
}

for k, v := range GetDefaultLogEntries() {
zapLogger = zapLogger.
With(zap.Any(k, v))
}

logger = zapLogger
}

func Logger() *zap.Logger {
return logger
}

func GetDefaultLogEntries() map[string]string {
return map[string]string{
"app": config.GetConfig().AppName,
"env": config.GetConfig().DeployEnv,
}
}
46 changes: 36 additions & 10 deletions cmd/aznum2words-webapp/router/router.go
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
package router

import (
"fmt"
"github.com/egasimov/aznum2words/cmd/aznum2words-webapp/logger"
"github.com/labstack/echo-contrib/prometheus"
"github.com/labstack/echo/v4"
echomiddleware "github.com/labstack/echo/v4/middleware"
"strings"
)

var loggerConfig = echomiddleware.LoggerConfig{
Format: `{"time":"${time_rfc3339_nano}","x-correlation-id":"${header:x-correlation-id}","remote_ip":"${remote_ip}",` +
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"}` + "\n",
CustomTimeFormat: "2006-01-02 15:04:05.00000",
}

func NewMainServer() *echo.Echo {
echoMainServer := echo.New()
echoMainServer.HideBanner = true
echoMainServer.HidePort = true

echoMainServer.Use(echomiddleware.CORS())
echoMainServer.Use(echomiddleware.RequestIDWithConfig(
echomiddleware.RequestIDConfig{
TargetHeader: echo.HeaderXCorrelationID,
}))
// Log all requests
echoMainServer.Use(echomiddleware.LoggerWithConfig(loggerConfig))
echoMainServer.Use(echomiddleware.LoggerWithConfig(getLoggerConfig()))

return echoMainServer
}
Expand All @@ -32,14 +29,14 @@ func NewPrometheusServer(echoMainServer *echo.Echo) *echo.Echo {
// Create Prometheus server and Middleware
echoPrometheus := echo.New()
echoPrometheus.HideBanner = true

echoPrometheus.HidePort = true
echoPrometheus.Use(echomiddleware.CORS())
echoPrometheus.Use(echomiddleware.RequestIDWithConfig(
echomiddleware.RequestIDConfig{
TargetHeader: echo.HeaderXCorrelationID,
}))
// Log all requests
echoPrometheus.Use(echomiddleware.LoggerWithConfig(loggerConfig))
echoPrometheus.Use(echomiddleware.LoggerWithConfig(getLoggerConfig()))

prom := prometheus.NewPrometheus("echo", nil)

Expand All @@ -51,3 +48,32 @@ func NewPrometheusServer(echoMainServer *echo.Echo) *echo.Echo {

return echoPrometheus
}

func getLoggerConfig() echomiddleware.LoggerConfig {
return echomiddleware.LoggerConfig{
Format: `{"time":"${time_rfc3339_nano}","x-correlation-id":"${header:x-correlation-id}","remote_ip":"${remote_ip}",` +
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
addDefaultLogEntries() +
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"}` + "\n",
CustomTimeFormat: "2006-01-02 15:04:05.00000",
}
}

func addDefaultLogEntries() string {
var logEntries []string
for k, v := range logger.GetDefaultLogEntries() {
logEntries = append(logEntries, addKeyValPair(k, v, true))
}

return strings.Join(logEntries, "")
}

func addKeyValPair(keyName string, valName string, appendComma bool) string {
var result string
if appendComma {
result = fmt.Sprintf(`"%s":"%s",`, keyName, valName)
} else {
result = fmt.Sprintf(`"%s":"%s"`, keyName, valName)
}
return result
}

0 comments on commit 4d8b911

Please sign in to comment.