Skip to content

Commit a853879

Browse files
committed
chore: add rich logging and middlewares
1 parent 75657a5 commit a853879

File tree

8 files changed

+231
-4
lines changed

8 files changed

+231
-4
lines changed

.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ APP_CORS_ALLOW_CREDENTIALS
1616

1717
APP_FALLBACK_REDIRECT
1818

19+
CONSOLE_LOG_ENABLED=true
20+
21+
FILE_LOG_ENABLED=false
22+
FILE_LOG_DIRECTORY=logs
23+
FILE_LOG_NAME=server.log
24+
FILE_LOG_MAX_SIZE=1
25+
FILE_LOG_MAX_BACKUPS=100
26+
FILE_LOG_MAX_AGE=7
27+
FILE_LOG_LOCALTIME=false
28+
FILE_LOG_COMPRESSED=true
29+
1930
DB_HOST
2031
DB_PORT
2132
DB_USERNAME

cmd/http.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

33
import (
44
"context"
5-
"log"
65
"net/http"
76
"os"
87
"os/signal"
@@ -15,6 +14,7 @@ import (
1514
"github.com/mocha-bot/mochus/core/module"
1615
http_handler "github.com/mocha-bot/mochus/handler/http"
1716
http_middleware "github.com/mocha-bot/mochus/handler/http/middleware"
17+
infrastructure_logger "github.com/mocha-bot/mochus/infrastructure/logger"
1818
discord_repository "github.com/mocha-bot/mochus/repository/discord"
1919
zLog "github.com/rs/zerolog/log"
2020
"github.com/spf13/cobra"
@@ -35,8 +35,16 @@ func serveHTTP(cmd *cobra.Command, args []string) error {
3535
return err
3636
}
3737

38+
zLog.Logger = infrastructure_logger.NewLogger(
39+
infrastructure_logger.WithConsole(cfg.Logger.ConsoleLogEnabled),
40+
infrastructure_logger.WithFile(cfg.Logger.FileLogEnabled),
41+
infrastructure_logger.WithLoggerConfig(&cfg.Logger),
42+
)
43+
3844
e := echo.New()
39-
e.Logger.SetLevel(log.LstdFlags)
45+
e.HideBanner = true
46+
e.HidePort = true
47+
e.Logger.SetOutput(zLog.Logger)
4048

4149
CORS := http_middleware.CORS(
4250
http_middleware.WithAllowOrigins(cfg.App.CORSAllowOrigins),
@@ -45,9 +53,12 @@ func serveHTTP(cmd *cobra.Command, args []string) error {
4553
)
4654

4755
e.Use(
56+
middleware.Recover(),
57+
middleware.RequestID(),
58+
middleware.Secure(),
59+
middleware.RequestLoggerWithConfig(http_middleware.RequestLoggerWithZerolog()),
4860
CORS,
4961
http_middleware.FallbackRedirect(cfg.App.FallbackRedirect),
50-
middleware.RequestID(),
5162
)
5263

5364
discordRepository := discord_repository.NewDiscordRepository(cfg.Discord)
@@ -84,7 +95,7 @@ func serveHTTP(cmd *cobra.Command, args []string) error {
8495
err = e.Start(cfg.App.GetAddress())
8596
}
8697

87-
if err != nil {
98+
if err != nil && err != http.ErrServerClosed {
8899
zLog.Fatal().Err(err).Msg("error starting server")
89100
}
90101
}()
@@ -97,5 +108,7 @@ func serveHTTP(cmd *cobra.Command, args []string) error {
97108
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
98109
defer cancel()
99110

111+
zLog.Info().Msg("Shutting down server")
112+
100113
return e.Shutdown(ctx)
101114
}

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type Config struct {
66
Database DatabaseConfig
77
Discord DiscordConfig
88
Redis RedisConfig
9+
Logger LoggerConfig
910
App AppConfig
1011
}
1112

config/logger.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package config
2+
3+
import "path"
4+
5+
type LoggerConfig struct {
6+
// Log level for zerolog
7+
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
8+
9+
// FileLogEnabled to log using stdout or stderr console
10+
ConsoleLogEnabled bool `env:"CONSOLE_LOG_ENABLED" envDefault:"true"`
11+
12+
// FileLogEnabled to log using file
13+
FileLogEnabled bool `env:"FILE_LOG_ENABLED" envDefault:"false"`
14+
15+
// Directory to log to to when filelogging is enabled
16+
Directory string `env:"FILE_LOG_DIRECTORY" envDefault:"logs"`
17+
18+
// Filename is the name of the logfile which will be placed inside the directory
19+
Filename string `env:"FILE_LOG_NAME" envDefault:"server.log"`
20+
21+
// MaxSize the max size in MB of the logfile before it's rolled
22+
MaxSize int `env:"FILE_LOG_MAX_SIZE" envDefault:"100"`
23+
24+
// MaxBackups the max number of rolled files to keep
25+
MaxBackups int `env:"FILE_LOG_MAX_BACKUPS" envDefault:"0"`
26+
27+
// MaxAge the max age in days to keep a logfile
28+
MaxAge int `env:"FILE_LOG_MAX_AGE" envDefault:"7"`
29+
30+
// LocalTime choose the log time format locally or not
31+
LocalTime bool `env:"FILE_LOG_LOCALTIME"`
32+
33+
// Compress the log file
34+
Compress bool `env:"FILE_LOG_COMPRESSED"`
35+
}
36+
37+
func (l LoggerConfig) ToLumberjackFileConfig() LumberjackFileConfig {
38+
return LumberjackFileConfig{
39+
Filename: path.Join(l.Directory, l.Filename),
40+
MaxBackups: l.MaxBackups,
41+
MaxSize: l.MaxSize,
42+
MaxAge: l.MaxAge,
43+
LocalTime: l.LocalTime,
44+
Compress: l.Compress,
45+
}
46+
}
47+
48+
type LumberjackFileConfig struct {
49+
Filename string
50+
MaxBackups int
51+
MaxSize int
52+
MaxAge int
53+
LocalTime bool
54+
Compress bool
55+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ require (
2121
github.com/go-playground/validator/v10 v10.25.0 // indirect
2222
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2323
github.com/leodido/go-urn v1.4.0 // indirect
24+
github.com/pkg/errors v0.9.1 // indirect
2425
github.com/spf13/pflag v1.0.6 // indirect
26+
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
2527
)
2628

2729
require (

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg
6767
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
6868
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
6969
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
70+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
7071
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7172
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7273
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -127,6 +128,8 @@ golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw
127128
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
128129
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
129130
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
131+
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
132+
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
130133
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
131134
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
132135
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

handler/http/middleware/logger.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package http_middleware
2+
3+
import (
4+
"github.com/labstack/echo/v4"
5+
"github.com/labstack/echo/v4/middleware"
6+
zLog "github.com/rs/zerolog/log"
7+
)
8+
9+
func RequestLoggerWithZerolog() middleware.RequestLoggerConfig {
10+
return middleware.RequestLoggerConfig{
11+
LogURI: true,
12+
LogStatus: true,
13+
LogMethod: true,
14+
LogUserAgent: true,
15+
LogLatency: true,
16+
LogError: true,
17+
LogRemoteIP: true,
18+
LogProtocol: true,
19+
LogHost: true,
20+
LogRequestID: true,
21+
LogReferer: true,
22+
LogContentLength: true,
23+
LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
24+
zLog.Info().
25+
Str("method", v.Method).
26+
Int("status", v.Status).
27+
Str("host", v.Host).
28+
Str("uri", v.URI).
29+
Str("referer", v.Referer).
30+
Str("user_agent", v.UserAgent).
31+
Dur("latency", v.Latency).
32+
Str("remote_ip", v.RemoteIP).
33+
Str("protocol", v.Protocol).
34+
Str("request_id", v.RequestID).
35+
Str("content_length", v.ContentLength).
36+
Msg("Request received")
37+
return nil
38+
},
39+
}
40+
}

infrastructure/logger/log.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package infrastructure_logger
2+
3+
import (
4+
"io"
5+
"os"
6+
"sync"
7+
"time"
8+
9+
"github.com/mocha-bot/mochus/config"
10+
"github.com/rs/zerolog"
11+
"github.com/rs/zerolog/pkgerrors"
12+
lumberjack "gopkg.in/natefinch/lumberjack.v2"
13+
)
14+
15+
type LoggerOptions struct {
16+
WithConsole bool
17+
WithFile bool
18+
LoggerConfig *config.LoggerConfig
19+
}
20+
21+
type LoggerOption func(*LoggerOptions)
22+
23+
func WithConsole(isEnabled bool) LoggerOption {
24+
return func(o *LoggerOptions) {
25+
o.WithConsole = isEnabled
26+
}
27+
}
28+
29+
func WithFile(isEnabled bool) LoggerOption {
30+
return func(o *LoggerOptions) {
31+
o.WithFile = isEnabled
32+
}
33+
}
34+
35+
func WithLoggerConfig(conf *config.LoggerConfig) LoggerOption {
36+
return func(o *LoggerOptions) {
37+
o.LoggerConfig = conf
38+
}
39+
}
40+
41+
func DefaultLoggerOptions() *LoggerOptions {
42+
return &LoggerOptions{
43+
WithConsole: true,
44+
WithFile: false,
45+
LoggerConfig: nil,
46+
}
47+
}
48+
49+
var once = new(sync.Once)
50+
51+
func NewLogger(opts ...LoggerOption) zerolog.Logger {
52+
options := DefaultLoggerOptions()
53+
for _, opt := range opts {
54+
opt(options)
55+
}
56+
57+
var log zerolog.Logger
58+
59+
once.Do(func() {
60+
logWriters := make([]io.Writer, 0)
61+
62+
if options.WithConsole {
63+
logWriters = append(logWriters, NewConsoleLogger())
64+
}
65+
66+
if options.WithFile && options.LoggerConfig != nil {
67+
logWriters = append(logWriters, NewLumberjackFileLogger(options.LoggerConfig.ToLumberjackFileConfig()))
68+
}
69+
70+
zerolog.TimestampFunc = time.Now
71+
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
72+
73+
mw := zerolog.MultiLevelWriter(logWriters...)
74+
75+
log = zerolog.
76+
New(mw).
77+
With().
78+
Timestamp().
79+
Logger()
80+
})
81+
82+
return log
83+
}
84+
85+
func NewConsoleLogger() io.Writer {
86+
return zerolog.ConsoleWriter{
87+
Out: os.Stdout,
88+
NoColor: true,
89+
TimeFormat: time.RFC3339,
90+
}
91+
}
92+
93+
func NewLumberjackFileLogger(conf config.LumberjackFileConfig) io.Writer {
94+
return &lumberjack.Logger{
95+
Filename: conf.Filename,
96+
MaxBackups: conf.MaxBackups, // files
97+
MaxSize: conf.MaxSize, // megabytes
98+
MaxAge: conf.MaxAge, // days
99+
LocalTime: conf.LocalTime,
100+
Compress: conf.Compress,
101+
}
102+
}

0 commit comments

Comments
 (0)