Skip to content

Commit

Permalink
Merge pull request #66 from Frontman-Labs/init-logger
Browse files Browse the repository at this point in the history
feat(logger): add structured logging wrapper for zap
  • Loading branch information
CCOLLOT authored Mar 12, 2023
2 parents 7a6a858 + 2ebd64f commit bbf5445
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 22 deletions.
26 changes: 21 additions & 5 deletions cmd/frontman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ package main

import (
"flag"
"log"
"fmt"
"os"

"github.com/Frontman-Labs/frontman"
"github.com/Frontman-Labs/frontman/config"
"github.com/Frontman-Labs/frontman/log"
)

func main() {

// Define command-line flags
var configFile string
var logLevel string
flag.StringVar(&configFile, "config", "", "path to configuration file")
flag.StringVar(&logLevel, "log-level", "", "set log level to debug")

// Parse command-line flags
flag.Parse()
Expand All @@ -25,15 +29,27 @@ func main() {

config, err := config.LoadConfig(configPath)
if err != nil {
log.Fatalf("failed to load configuration: %v", err)
fmt.Printf("failed to load configuration: %v", err)
os.Exit(1)
}

if config.LoggingConfig.Level != "" && logLevel == "" {
logLevel = config.LoggingConfig.Level
} else {
logLevel = "info"
}
logger, err := log.NewDefaultLogger(log.ParseLevel(logLevel))
if err != nil {
fmt.Println("failed to initialize logger")
os.Exit(1)
}

// Create a new Gateway instance
gateway, err := frontman.NewGateway(config)
gateway, err := frontman.NewGateway(config, logger)
if err != nil {
log.Fatalf("failed to create gateway: %v", err)
logger.Fatalf("failed to create gateway: %v", err)
}

// Start the server
log.Fatal(gateway.Start())
logger.Fatal(gateway.Start())
}
41 changes: 24 additions & 17 deletions gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package frontman
import (
"context"
"crypto/tls"
"log"
"fmt"
"net/http"
"strings"

"github.com/Frontman-Labs/frontman/config"
"github.com/Frontman-Labs/frontman/log"
"github.com/Frontman-Labs/frontman/plugins"
"github.com/Frontman-Labs/frontman/service"
"github.com/gorilla/mux"
Expand All @@ -19,6 +20,7 @@ type Gateway struct {
service *mux.Router
backendServices service.ServiceRegistry
conf *config.Config
log log.Logger
}

func NewServicesRouter(backendServices service.ServiceRegistry) *mux.Router {
Expand All @@ -34,7 +36,7 @@ func NewServicesRouter(backendServices service.ServiceRegistry) *mux.Router {
}

// NewGateway creates a new Gateway instance with a Redis client connection factory
func NewGateway(conf *config.Config) (*Gateway, error) {
func NewGateway(conf *config.Config, log log.Logger) (*Gateway, error) {

// Retrieve the Redis client connection from the factory
ctx := context.Background()
Expand Down Expand Up @@ -78,6 +80,7 @@ func NewGateway(conf *config.Config) (*Gateway, error) {
service: servicesRouter,
backendServices: backendServices,
conf: conf,
log : log,
}, nil
}

Expand All @@ -101,13 +104,13 @@ func (gw *Gateway) Start() error {
return err
}
apiServer := createServer(apiAddr, apiHandler, &cert)
log.Printf("Started Frontman API with SSL on %s\n", apiAddr)
go startServer(apiServer)
gw.log.Infof("Started Frontman API with SSL on %s", apiAddr)
go func(){if err := startServer(apiServer); err!=nil{ gw.log.Fatal(err)}}()
} else {
apiHandler = gw.service
api := createServer(apiAddr, apiHandler, nil)
log.Printf("Started Frontman API on %s\n", apiAddr)
go startServer(api)
gw.log.Infof("Started Frontman API on %s", apiAddr)
go func(){if err := startServer(api); err!=nil{ gw.log.Fatal(err)}}()
}

if gw.conf.GatewayConfig.SSL.Enabled {
Expand All @@ -120,18 +123,22 @@ func (gw *Gateway) Start() error {
// Redirect HTTP traffic to HTTPS
httpAddr := "0.0.0.0:80"
httpRedirect := createRedirectServer(httpAddr, gatewayAddr)
log.Printf("Started HTTP redirect server on %s\n", httpAddr)
go startServer(httpRedirect)
gw.log.Infof("Started HTTP redirect server on %s", httpAddr)
go func(){if err := startServer(httpRedirect); err!=nil{ gw.log.Fatal(err)}}()


gatewayServer := createServer(gatewayAddr, gatewayHandler, &cert)
log.Printf("Started Frontman Gateway with SSL on %s\n", gatewayAddr)
startServer(gatewayServer)
gw.log.Infof("Started Frontman Gateway with SSL on %s", gatewayAddr)
if err := startServer(gatewayServer); err!=nil {
return err
}
} else {
gatewayHandler = gw.router
gateway := createServer(gatewayAddr, gatewayHandler, nil)
log.Printf("Started Frontman Gateway on %s", gatewayAddr)
startServer(gateway)
gw.log.Infof("Started Frontman Gateway on %s", gatewayAddr)
if err := startServer(gateway); err!=nil {
return err
}
}

return nil
Expand All @@ -152,8 +159,7 @@ func createRedirectServer(addr string, redirectAddr string) *http.Server {
func loadCert(certFile, keyFile string) (tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatalf("Failed to load certificate: %v", err)
return tls.Certificate{}, err
return tls.Certificate{}, fmt.Errorf("Failed to load certificate: %w", err)
}
return cert, nil
}
Expand All @@ -171,14 +177,15 @@ func createServer(addr string, handler http.Handler, cert *tls.Certificate) *htt
return server
}

func startServer(server *http.Server) {
func startServer(server *http.Server) error {
if server.TLSConfig != nil {
if err := server.ListenAndServeTLS("", ""); err != nil {
log.Fatalf("Failed to start server with TLS: %v", err)
return fmt.Errorf("Failed to start server with TLS: %w", err)
}
} else {
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Failed to start server without TLS: %v", err)
return fmt.Errorf("Failed to start server without TLS: %w", err)
}
}
return nil
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ require (
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
Expand All @@ -72,6 +73,12 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw=
go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
Expand Down
68 changes: 68 additions & 0 deletions log/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package log

import "fmt"

type logLevel string

const (
InfoLevel logLevel = "info"
DebugLevel logLevel = "debug"
WarnLevel logLevel = "warn"
ErrorLevel logLevel = "error"
)

type Logger interface {
Debug(args ...interface{})
Debugf(format string, args ...interface{})
Info(args ...interface{})
Infof(format string, args ...interface{})
Error(args ...interface{})
Errorf(format string, args ...interface{})
Warn(args ...interface{})
Warnf(format string, args ...interface{})
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
WithFields(level logLevel, msg string, fields ...Field)
}

// Field used for structured logging
type Field struct {
key string
value string
}

func String(key string, value string) Field {
return Field{
key: key,
value: value,
}
}

func Error(value string) Field {
return Field{
key: "err",
value: value,
}
}

func ParseLevel(str string) logLevel {
var lvl logLevel
lvl.unmarshalString(str)
return lvl
}

func (l *logLevel) unmarshalString(str string) {
switch str {
case "debug", "DEBUG":
*l = DebugLevel
case "info", "INFO", "": // make the zero value useful
*l = InfoLevel
case "warn", "WARN":
*l = WarnLevel
case "error", "ERROR":
*l = ErrorLevel
default:
fmt.Println("unknown log level ", str, " proceeding with log levle Info")
*l = InfoLevel
}
}
21 changes: 21 additions & 0 deletions log/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package log

import (
"testing"
)

func TestLogger(t *testing.T) {
logger, err := NewDefaultLogger("debug")
if err != nil {
t.Error(err)
}
// For illustrative purposes
logger.Info("basic ", "logging")
logger.Infof("info formatted log: %s to %d", "I can count", 123)
logger.Debug("basic ", "logging")
logger.Debugf("debug formatted log: %s to %d", "I can count", 123)
logger.Error("basic ", "logging")
logger.Errorf("error formatted log: %s to %d", "I could not count", 123)
logger.WithFields(ErrorLevel, "unexpected traffic received", Error("terrible error message"))
logger.WithFields(InfoLevel, "ingress traffic received", String("url", "https://github.com/Frontman-Labs/frontman"), String("host", "162.1.3.2"), String("port", "32133"))
}
90 changes: 90 additions & 0 deletions log/zap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package log

import (
"os"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

type ZapLogger struct {
zap *zap.Logger
sugarZap *zap.SugaredLogger
}

func (l ZapLogger) Debugf(format string, args ...interface{}) {
l.sugarZap.Debugf(format, args...)
}

func (l ZapLogger) Debug(args ...interface{}) {
l.sugarZap.Debug(args...)
}

func (l ZapLogger) Fatalf(format string, args ...interface{}) {
l.sugarZap.Fatalf(format, args...)
}

func (l ZapLogger) Fatal(args ...interface{}) {
l.sugarZap.Fatal(args...)
}

func (l ZapLogger) Infof(format string, args ...interface{}) {
l.sugarZap.Infof(format, args...)
}

func (l ZapLogger) Info(args ...interface{}) {
l.sugarZap.Info(args...)
}

func (l ZapLogger) Warnf(format string, args ...interface{}) {
l.sugarZap.Warnf(format, args...)
}

func (l ZapLogger) Warn(args ...interface{}) {
l.sugarZap.Warn(args...)
}
func (l ZapLogger) Errorf(format string, args ...interface{}) {
l.sugarZap.Errorf(format, args...)
}

func (l ZapLogger) Error(args ...interface{}) {
l.sugarZap.Error(args...)
}

func fieldsToZap(fields ...Field) (zfields []zapcore.Field) {
for _, field := range fields {
zfields = append(zfields, zap.String(field.key, field.value))
}
return zfields
}

func (l ZapLogger) WithFields(level logLevel, msg string, fields ...Field) {
lvl, err := zapcore.ParseLevel(string(level))
if err != nil {
l.Fatalf("Unknown log level: %s", level)
os.Exit(1)
}
l.zap.Log(lvl, msg, fieldsToZap(fields...)...)
}

func NewZapLogger(level logLevel) (Logger, error) {
cfg := zap.NewProductionConfig()
lvl, err := zapcore.ParseLevel(string(level))
if err != nil {
return nil, err
}
cfg.Level = zap.NewAtomicLevelAt(lvl)
zap, err := cfg.Build(zap.AddCallerSkip(1))
if err != nil {
return nil, err
}
logger := &ZapLogger{
zap: zap,
sugarZap: zap.Sugar(),
}
return logger, nil
}

func NewDefaultLogger(level logLevel) (Logger, error) {
return NewZapLogger(level)
}

0 comments on commit bbf5445

Please sign in to comment.