Skip to content

Commit cefd9d1

Browse files
committed
Add structured logging using zap (#2)
1 parent cb134a8 commit cefd9d1

File tree

11 files changed

+231
-19
lines changed

11 files changed

+231
-19
lines changed

.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
internal/server
2-
api/openapi.yaml
3-
.history/
41
zero-notification-service
5-
2+
.history/
3+
api/openapi.yaml
4+
internal/server/*
5+
!internal/server/logger.go

.openapi-generator-ignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ main.go
2626
go.mod
2727
README.md
2828
Dockerfile
29+
internal/server/logger.go

.openapi-generator/FILES

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ internal/server/api_notification.go
88
internal/server/api_notification_service.go
99
internal/server/helpers.go
1010
internal/server/impl.go
11-
internal/server/logger.go
1211
internal/server/model_error.go
1312
internal/server/model_mail_message.go
1413
internal/server/model_notification_message.go

cmd/server/main.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ package main
1212
import (
1313
"context"
1414
"fmt"
15-
"log"
1615
"net/http"
1716
"os"
1817
"os/signal"
1918
"syscall"
2019
"time"
2120

2221
"github.com/commitdev/zero-notification-service/internal/config"
22+
"github.com/commitdev/zero-notification-service/internal/log"
2323
"github.com/commitdev/zero-notification-service/internal/server"
2424
"github.com/commitdev/zero-notification-service/internal/service"
25+
"go.uber.org/zap"
2526
)
2627

2728
var (
@@ -30,13 +31,16 @@ var (
3031
)
3132

3233
func main() {
33-
fmt.Printf("zero-notification-service version: %v, build: %v \n", appVersion, appBuild)
34+
config := config.GetConfig()
35+
36+
log.Init(config)
37+
defer zap.S().Sync() // Flush logs when the process ends
38+
39+
zap.S().Infow("zero-notification-service", "version", appVersion, "build", appBuild)
3440

3541
// Heartbeat for liveness check
3642
go heartbeat()
3743

38-
config := config.GetConfig()
39-
4044
EmailApiService := service.NewEmailApiService(config)
4145
EmailApiController := server.NewEmailApiController(EmailApiService)
4246

@@ -57,34 +61,34 @@ func main() {
5761

5862
// Run the server in a goroutine
5963
go func() {
60-
log.Printf("Serving at http://%s/", serverAddress)
64+
zap.S().Infof("Serving at http://%s/", serverAddress)
6165
err := server.ListenAndServe()
6266
if err != http.ErrServerClosed {
63-
log.Fatalf("Fatal error while serving HTTP: %v\n", err)
67+
zap.S().Fatalf("Fatal error while serving HTTP: %v\n", err)
6468
close(stop)
6569
}
6670
}()
6771

6872
// Block while reading from the channel until we receive a signal
6973
sig := <-stop
70-
log.Printf("Received signal %s, starting graceful shutdown", sig)
74+
zap.S().Infof("Received signal %s, starting graceful shutdown", sig)
7175

7276
// Give connections some time to drain
7377
ctx, cancel := context.WithTimeout(context.Background(), config.GracefulShutdownTimeout*time.Second)
7478
defer cancel()
7579
err := server.Shutdown(ctx)
7680
if err != nil {
77-
log.Fatalf("Error during shutdown, client requests have been terminated: %v\n", err)
81+
zap.S().Fatalf("Error during shutdown, client requests have been terminated: %v\n", err)
7882
} else {
79-
log.Println("Graceful shutdown complete")
83+
zap.S().Infof("Graceful shutdown complete")
8084
}
8185
}
8286

8387
func heartbeat() {
8488
for range time.Tick(4 * time.Second) {
8589
fh, err := os.Create("/tmp/service-alive")
8690
if err != nil {
87-
log.Println("Unable to write file for liveness check!")
91+
zap.S().Warnf("Unable to write file for liveness check!")
8892
} else {
8993
fh.Close()
9094
}

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ go 1.15
44

55
require (
66
github.com/gorilla/mux v1.7.3
7+
github.com/pkg/errors v0.9.1 // indirect
78
github.com/sendgrid/rest v2.6.2+incompatible
89
github.com/sendgrid/sendgrid-go v3.7.2+incompatible
910
github.com/spf13/viper v1.7.1
10-
github.com/stretchr/testify v1.3.0
11-
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
12-
golang.org/x/text v0.3.3 // indirect
11+
github.com/stretchr/testify v1.4.0
12+
go.uber.org/multierr v1.6.0 // indirect
13+
go.uber.org/zap v1.16.0
14+
golang.org/x/tools v0.0.0-20201208002638-66f931576d67 // indirect
1315
)

go.sum

Lines changed: 43 additions & 0 deletions
Large diffs are not rendered by default.

internal/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type Config struct {
1111
Port int
1212
SendgridAPIKey string
1313
GracefulShutdownTimeout time.Duration
14+
StructuredLogging bool
1415
}
1516

1617
var config *Config
@@ -20,6 +21,7 @@ const (
2021
Port
2122
SendgridAPIKey
2223
GracefulShutdownTimeout
24+
StructuredLogging
2325
)
2426

2527
// GetConfig returns a pointer to the singleton Config object
@@ -40,10 +42,14 @@ func loadConfig() *Config {
4042
viper.SetDefault(GracefulShutdownTimeout, "10")
4143
viper.BindEnv(GracefulShutdownTimeout, "GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS")
4244

45+
viper.SetDefault(StructuredLogging, "false")
46+
viper.BindEnv(StructuredLogging, "STRUCTURED_LOGGING")
47+
4348
config := Config{
4449
Port: viper.GetInt(Port),
4550
SendgridAPIKey: viper.GetString(SendgridAPIKey),
4651
GracefulShutdownTimeout: viper.GetDuration(GracefulShutdownTimeout),
52+
StructuredLogging: viper.GetBool(StructuredLogging),
4753
}
4854

4955
return &config

internal/log/ecs.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Helpers for ECS log formatting
2+
// https://www.elastic.co/guide/en/ecs/master/index.html
3+
package log
4+
5+
import (
6+
"time"
7+
8+
"go.uber.org/zap/zapcore"
9+
)
10+
11+
// ECSHTTP represents a subset of the fields of the ECS HTTP object
12+
type ECSHTTP struct {
13+
Request ECSRequest
14+
Response ECSResponse
15+
}
16+
17+
func (o ECSHTTP) MarshalLogObject(enc zapcore.ObjectEncoder) error {
18+
enc.AddObject("request", o.Request)
19+
return enc.AddObject("response", o.Response)
20+
}
21+
22+
// ECSResponse represents a subset of the fields of the ECS HTTP Response object
23+
type ECSResponse struct {
24+
StatusCode int
25+
}
26+
27+
func (o ECSResponse) MarshalLogObject(enc zapcore.ObjectEncoder) error {
28+
enc.AddInt("status_code", o.StatusCode)
29+
return nil
30+
}
31+
32+
// ECSRequest represents a subset of the fields of the ECS HTTP Request object
33+
type ECSRequest struct {
34+
Method string
35+
}
36+
37+
func (o ECSRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
38+
enc.AddString("method", o.Method)
39+
return nil
40+
}
41+
42+
// ECSURL represents a subset of the fields of the ECS URL object
43+
type ECSURL struct {
44+
Original string
45+
}
46+
47+
func (o ECSURL) MarshalLogObject(enc zapcore.ObjectEncoder) error {
48+
enc.AddString("original", o.Original)
49+
return nil
50+
}
51+
52+
// ECSEvent represents a subset of the fields of the ECS Event object
53+
type ECSEvent struct {
54+
Action string
55+
Duration time.Duration
56+
}
57+
58+
func (o ECSEvent) MarshalLogObject(enc zapcore.ObjectEncoder) error {
59+
enc.AddString("action", o.Action)
60+
enc.AddInt64("duration", int64(o.Duration))
61+
return nil
62+
}

internal/log/log.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package log
2+
3+
import (
4+
"log"
5+
6+
"github.com/commitdev/zero-notification-service/internal/config"
7+
"go.uber.org/zap"
8+
"go.uber.org/zap/zapcore"
9+
)
10+
11+
// Init sets up logging based on the current environment
12+
func Init(config *config.Config) {
13+
var rawLogger *zap.Logger
14+
var err error
15+
if config.StructuredLogging {
16+
// Info level, JSON output
17+
zapConfig := zap.NewProductionConfig()
18+
zapConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
19+
zapConfig.EncoderConfig.MessageKey = "message"
20+
rawLogger, err = zapConfig.Build()
21+
} else {
22+
// Debug level, pretty output
23+
zapConfig := zap.NewDevelopmentConfig()
24+
zapConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
25+
rawLogger, err = zapConfig.Build()
26+
}
27+
28+
if err != nil {
29+
log.Fatalf("can't initialize zap logger: %v", err)
30+
}
31+
32+
zap.ReplaceGlobals(rawLogger)
33+
}

internal/mail/mail.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func SendBulkMail(toList []server.Recipient, from server.Sender, message server.
3232
wg.Done()
3333
}(to)
3434
}
35-
// Wait on the all responses to close the channel to signal that the operation is complete
35+
// Wait on all the responses to close the channel to signal that the operation is complete
3636
go func() {
3737
wg.Wait()
3838
close(responseChannel)

0 commit comments

Comments
 (0)