Added custom logging support using zerolog
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.env
|
||||
.logs/
|
||||
tmp/
|
||||
projectreshoot
|
||||
static/css/output.css
|
||||
|
||||
2
Makefile
2
Makefile
@@ -16,7 +16,7 @@ dev:
|
||||
|
||||
test:
|
||||
go mod tidy && \
|
||||
go run . --port 3232 --test
|
||||
go run . --port 3232 --test --loglevel trace
|
||||
|
||||
clean:
|
||||
go clean
|
||||
|
||||
@@ -2,7 +2,6 @@ package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -27,7 +26,13 @@ func (user *User) SetPassword(conn *sql.DB, password string) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "conn.Exec")
|
||||
}
|
||||
fmt.Println(result)
|
||||
ra, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "result.RowsAffected")
|
||||
}
|
||||
if ra != 1 {
|
||||
return errors.New("Password was not updated")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
4
go.mod
4
go.mod
@@ -7,6 +7,7 @@ require (
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d
|
||||
golang.org/x/crypto v0.33.0
|
||||
)
|
||||
@@ -14,5 +15,8 @@ require (
|
||||
require (
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/coder/websocket v1.8.12 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
)
|
||||
|
||||
17
go.sum
17
go.sum
@@ -4,17 +4,34 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
||||
80
logging/logger.go
Normal file
80
logging/logger.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/pkgerrors"
|
||||
)
|
||||
|
||||
// Takes a log level as string and converts it to a zerolog.Level interface.
|
||||
// If the string is not a valid input it will return zerolog.InfoLevel
|
||||
func GetLogLevel(level string) zerolog.Level {
|
||||
levels := map[string]zerolog.Level{
|
||||
"trace": zerolog.TraceLevel,
|
||||
"debug": zerolog.DebugLevel,
|
||||
"info": zerolog.InfoLevel,
|
||||
"warn": zerolog.WarnLevel,
|
||||
"error": zerolog.ErrorLevel,
|
||||
"fatal": zerolog.FatalLevel,
|
||||
"panic": zerolog.PanicLevel,
|
||||
}
|
||||
logLevel, valid := levels[level]
|
||||
if !valid {
|
||||
return zerolog.InfoLevel
|
||||
}
|
||||
return logLevel
|
||||
}
|
||||
|
||||
// Returns a pointer to a new log file with the specified path.
|
||||
// Remember to call file.Close() when finished writing to the log file
|
||||
func GetLogFile(path string) (*os.File, error) {
|
||||
logPath := filepath.Join(path, "server.log")
|
||||
file, err := os.OpenFile(
|
||||
logPath,
|
||||
os.O_APPEND|os.O_CREATE|os.O_WRONLY,
|
||||
0663,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "os.OpenFile")
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Get a pointer to a new zerolog.Logger with the specified level and output
|
||||
// Can provide a file, writer or both. Must provide at least one of the two
|
||||
func GetLogger(
|
||||
logLevel zerolog.Level,
|
||||
w io.Writer,
|
||||
logFile *os.File,
|
||||
logDir string,
|
||||
) (*zerolog.Logger, error) {
|
||||
if w == nil && logFile == nil {
|
||||
return nil, errors.New("No Writer provided for log output.")
|
||||
}
|
||||
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
||||
|
||||
var consoleWriter zerolog.ConsoleWriter
|
||||
if w != nil {
|
||||
consoleWriter = zerolog.ConsoleWriter{Out: w}
|
||||
}
|
||||
|
||||
var output io.Writer
|
||||
if logFile != nil {
|
||||
if w != nil {
|
||||
output = zerolog.MultiLevelWriter(logFile, consoleWriter)
|
||||
} else {
|
||||
output = logFile
|
||||
}
|
||||
} else {
|
||||
output = consoleWriter
|
||||
}
|
||||
logger := zerolog.New(output).With().Timestamp().Caller().Logger().Level(logLevel)
|
||||
|
||||
return &logger, nil
|
||||
}
|
||||
54
main.go
54
main.go
@@ -15,13 +15,14 @@ import (
|
||||
"time"
|
||||
|
||||
"projectreshoot/db"
|
||||
"projectreshoot/logging"
|
||||
"projectreshoot/server"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Initializes and runs the server
|
||||
func run(ctx context.Context, w io.Writer, args []string) error {
|
||||
func run(ctx context.Context, w io.Writer, args map[string]string) error {
|
||||
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
@@ -30,10 +31,35 @@ func run(ctx context.Context, w io.Writer, args []string) error {
|
||||
return errors.Wrap(err, "server.GetConfig")
|
||||
}
|
||||
|
||||
var logfile *os.File = nil
|
||||
if config.LogOutput == "both" || config.LogOutput == "file" {
|
||||
logfile, err = logging.GetLogFile(config.LogDir)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "logging.GetLogFile")
|
||||
}
|
||||
defer logfile.Close()
|
||||
}
|
||||
|
||||
var consoleWriter io.Writer
|
||||
if config.LogOutput == "both" || config.LogOutput == "console" {
|
||||
consoleWriter = w
|
||||
}
|
||||
|
||||
logger, err := logging.GetLogger(
|
||||
config.LogLevel,
|
||||
consoleWriter,
|
||||
logfile,
|
||||
config.LogDir,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "logging.GetLogger")
|
||||
}
|
||||
|
||||
conn, err := db.ConnectToDatabase(&config.TursoDBName, &config.TursoToken)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.ConnectToDatabase")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
srv := server.NewServer(config, conn)
|
||||
httpServer := &http.Server{
|
||||
@@ -41,9 +67,9 @@ func run(ctx context.Context, w io.Writer, args []string) error {
|
||||
Handler: srv,
|
||||
}
|
||||
|
||||
// TEST: runs function for testing in dev if --test flag true
|
||||
if args[1] == "true" {
|
||||
test(config, conn, httpServer)
|
||||
// Runs function for testing in dev if --test flag true
|
||||
if args["test"] == "true" {
|
||||
test(config, logger, conn, httpServer)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,10 +103,24 @@ var static embed.FS
|
||||
// Start of runtime. Parse commandline arguments & flags, Initializes context
|
||||
// and starts the server
|
||||
func main() {
|
||||
port := flag.String("port", "", "Override port")
|
||||
test := flag.Bool("test", false, "Run test function")
|
||||
// Parse commandline args
|
||||
host := flag.String("host", "", "Override host to listen on")
|
||||
port := flag.String("port", "", "Override port to listen on")
|
||||
test := flag.Bool("test", false, "Run test function instead of main program")
|
||||
loglevel := flag.String("loglevel", "", "Set log level")
|
||||
logoutput := flag.String("logoutput", "", "Set log destination (file, console or both)")
|
||||
flag.Parse()
|
||||
args := []string{*port, strconv.FormatBool(*test)}
|
||||
|
||||
// Map the args for easy access
|
||||
args := map[string]string{
|
||||
"host": *host,
|
||||
"port": *port,
|
||||
"test": strconv.FormatBool(*test),
|
||||
"loglevel": *loglevel,
|
||||
"logoutput": *logoutput,
|
||||
}
|
||||
|
||||
// Start the server
|
||||
ctx := context.Background()
|
||||
if err := run(ctx, os.Stdout, args); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
|
||||
@@ -4,38 +4,78 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"projectreshoot/logging"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string // Host to listen on
|
||||
Port string // Port to listen on
|
||||
TrustedHost string // Domain/Hostname to accept as trusted
|
||||
TursoDBName string // DB Name for Turso DB/Branch
|
||||
TursoToken string // Bearer token for Turso DB/Branch
|
||||
SecretKey string // Secret key for signing tokens
|
||||
AccessTokenExpiry int64 // Access token expiry in minutes
|
||||
RefreshTokenExpiry int64 // Refresh token expiry in minutes
|
||||
TokenFreshTime int64 // Time for tokens to stay fresh in minutes
|
||||
Host string // Host to listen on
|
||||
Port string // Port to listen on
|
||||
TrustedHost string // Domain/Hostname to accept as trusted
|
||||
TursoDBName string // DB Name for Turso DB/Branch
|
||||
TursoToken string // Bearer token for Turso DB/Branch
|
||||
SecretKey string // Secret key for signing tokens
|
||||
AccessTokenExpiry int64 // Access token expiry in minutes
|
||||
RefreshTokenExpiry int64 // Refresh token expiry in minutes
|
||||
TokenFreshTime int64 // Time for tokens to stay fresh in minutes
|
||||
LogLevel zerolog.Level // Log level for global logging. Defaults to info
|
||||
LogOutput string // "file", "console", or "both". Defaults to console
|
||||
LogDir string // Path to create log files
|
||||
}
|
||||
|
||||
// Load the application configuration and get a pointer to the Config object
|
||||
func GetConfig(args []string) (*Config, error) {
|
||||
func GetConfig(args map[string]string) (*Config, error) {
|
||||
err := godotenv.Load(".env")
|
||||
if err != nil {
|
||||
fmt.Println(".env file not found.")
|
||||
}
|
||||
var port string
|
||||
var (
|
||||
host string
|
||||
port string
|
||||
logLevel zerolog.Level
|
||||
logOutput string
|
||||
valid bool
|
||||
)
|
||||
|
||||
if args[0] != "" {
|
||||
port = args[0]
|
||||
if args["host"] != "" {
|
||||
host = args["host"]
|
||||
} else {
|
||||
host = GetEnvDefault("HOST", "127.0.0.1")
|
||||
}
|
||||
if args["port"] != "" {
|
||||
port = args["port"]
|
||||
} else {
|
||||
port = GetEnvDefault("PORT", "3333")
|
||||
}
|
||||
if args["loglevel"] != "" {
|
||||
logLevel = logging.GetLogLevel(args["loglevel"])
|
||||
} else {
|
||||
logLevel = logging.GetLogLevel(GetEnvDefault("LOG_LEVEL", "info"))
|
||||
}
|
||||
if args["logoutput"] != "" {
|
||||
opts := map[string]string{
|
||||
"both": "both",
|
||||
"file": "file",
|
||||
"console": "console",
|
||||
}
|
||||
logOutput, valid = opts[args["logoutput"]]
|
||||
if !valid {
|
||||
logOutput = "console"
|
||||
fmt.Println(
|
||||
"Log output type was not parsed correctly. Defaulting to console only",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
logOutput = GetEnvDefault("LOG_OUTPUT", "console")
|
||||
}
|
||||
if logOutput != "both" && logOutput != "console" && logOutput != "file" {
|
||||
logOutput = "console"
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Host: GetEnvDefault("HOST", "127.0.0.1"),
|
||||
Host: host,
|
||||
Port: port,
|
||||
TrustedHost: os.Getenv("TRUSTED_HOST"),
|
||||
TursoDBName: os.Getenv("TURSO_DB_NAME"),
|
||||
@@ -44,6 +84,9 @@ func GetConfig(args []string) (*Config, error) {
|
||||
AccessTokenExpiry: GetEnvInt64("ACCESS_TOKEN_EXPIRY", 5),
|
||||
RefreshTokenExpiry: GetEnvInt64("REFRESH_TOKEN_EXPIRY", 1440), // defaults to 1 day
|
||||
TokenFreshTime: GetEnvInt64("TOKEN_FRESH_TIME", 5),
|
||||
LogLevel: logLevel,
|
||||
LogOutput: logOutput,
|
||||
LogDir: GetEnvDefault("LOG_DIR", ""),
|
||||
}
|
||||
|
||||
if config.TrustedHost == "" {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"projectreshoot/server"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// This function will only be called if the --test commandline flag is set.
|
||||
@@ -13,5 +15,10 @@ import (
|
||||
// conflicts on the default 3333. Useful for testing things out during dev.
|
||||
// If you add code here, remember to run:
|
||||
// `git update-index --assume-unchanged tester.go` to avoid tracking changes
|
||||
func test(config *server.Config, conn *sql.DB, srv *http.Server) {
|
||||
func test(
|
||||
config *server.Config,
|
||||
logger *zerolog.Logger,
|
||||
conn *sql.DB,
|
||||
srv *http.Server,
|
||||
) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user