Adapted logging which defaults to JSON encoding
This commit is contained in:
parent
feedfad99f
commit
0194429c80
5 changed files with 145 additions and 79 deletions
|
@ -4,6 +4,7 @@ Changes adhere to [semantic versioning](https://semver.org).
|
||||||
|
|
||||||
## [1.1.0] - UNRELEASED
|
## [1.1.0] - UNRELEASED
|
||||||
|
|
||||||
|
* Adapted logging which defaults to JSON encoding
|
||||||
* ...
|
* ...
|
||||||
|
|
||||||
## [1.0.3] - 2024/01/21
|
## [1.0.3] - 2024/01/21
|
||||||
|
|
14
README.md
14
README.md
|
@ -128,7 +128,7 @@ via web interface or API.
|
||||||
The following environment variables can be used to modify application behavior.
|
The following environment variables can be used to modify application behavior.
|
||||||
|
|
||||||
| Variable | Purpose | Default/Description |
|
| Variable | Purpose | Default/Description |
|
||||||
|:-----------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|
|
|:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `TZ` | The time zone (**recommended** to set it properly, background tasks depend on it) | Defaults to `Europe/Berlin`, can be any time zone according to _tz database_ |
|
| `TZ` | The time zone (**recommended** to set it properly, background tasks depend on it) | Defaults to `Europe/Berlin`, can be any time zone according to _tz database_ |
|
||||||
| `ADMIN_USER` | Admin user name for login | Not set by default, you need to explicitly set it to user name |
|
| `ADMIN_USER` | Admin user name for login | Not set by default, you need to explicitly set it to user name |
|
||||||
| `ADMIN_PASSWORD` | Admin password for login | Not set by default, you need to explicitly set it to a secure random |
|
| `ADMIN_PASSWORD` | Admin password for login | Not set by default, you need to explicitly set it to a secure random |
|
||||||
|
@ -152,7 +152,8 @@ The following environment variables can be used to modify application behavior.
|
||||||
| `CORS_ALLOW_METHODS` | CORS configuration | Defaults to `GET, POST, PUT, PATCH, DELETE, OPTIONS` |
|
| `CORS_ALLOW_METHODS` | CORS configuration | Defaults to `GET, POST, PUT, PATCH, DELETE, OPTIONS` |
|
||||||
| `CORS_ALLOW_HEADERS` | CORS configuration | Defaults to `Authorization, Content-Type` |
|
| `CORS_ALLOW_HEADERS` | CORS configuration | Defaults to `Authorization, Content-Type` |
|
||||||
| | | |
|
| | | |
|
||||||
| `LOGGING_LEVEL` | Logging level. Possible are `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal` | Defaults to `info` |
|
| `LOGGING_LEVEL` | Logging level. Possible are `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal`. Setting to `debug` enables high verbosity output. | Defaults to `info` |
|
||||||
|
| `LOGGING_ENCODING` | Logging encoding. Possible are `console` and `json` | Defaults to `json` |
|
||||||
| | | |
|
| | | |
|
||||||
| `WEBHOOKS_TOKEN_LENGTH` | The length of the token | Defaults to `16`, positive number |
|
| `WEBHOOKS_TOKEN_LENGTH` | The length of the token | Defaults to `16`, positive number |
|
||||||
| | | |
|
| | | |
|
||||||
|
@ -281,7 +282,14 @@ docker build --rm --no-cache -t upda:latest .
|
||||||
|
|
||||||
## Development & contribution
|
## Development & contribution
|
||||||
|
|
||||||
* Ensure to set `LOGGING_LEVEL=debug` for proper debug logs during development.
|
* Ensure to set the following environment variables for proper debug logs during development
|
||||||
|
|
||||||
|
```shell
|
||||||
|
DEVELOPMENT=true
|
||||||
|
LOGGING_ENCODING=console
|
||||||
|
LOGGING_LEVEL=debug
|
||||||
|
```
|
||||||
|
|
||||||
* Code guidelines
|
* Code guidelines
|
||||||
* Each entity has its own repository
|
* Each entity has its own repository
|
||||||
* Each entity is only used in repository and service (otherwise, mapping happens)
|
* Each entity is only used in repository and service (otherwise, mapping happens)
|
||||||
|
|
|
@ -23,8 +23,8 @@ func Start() {
|
||||||
// secure init
|
// secure init
|
||||||
util.AssertAvailablePRNG()
|
util.AssertAvailablePRNG()
|
||||||
|
|
||||||
// set gin mode derived from logging level
|
// set gin mode derived
|
||||||
if zap.L().Level() == zap.DebugLevel {
|
if env.appConfig.isDevelopment {
|
||||||
gin.SetMode(gin.DebugMode)
|
gin.SetMode(gin.DebugMode)
|
||||||
} else {
|
} else {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
envDevelopment = "DEVELOPMENT"
|
||||||
|
|
||||||
|
envLoggingLevel = "LOGGING_LEVEL"
|
||||||
|
loggingLevelDefault = "info"
|
||||||
|
envLoggingEncoding = "LOGGING_ENCODING"
|
||||||
|
loggingEncodingDefault = "json"
|
||||||
|
|
||||||
envTZ = "TZ"
|
envTZ = "TZ"
|
||||||
tzDefault = "Europe/Berlin"
|
tzDefault = "Europe/Berlin"
|
||||||
|
|
||||||
envAdminUser = "ADMIN_USER"
|
envAdminUser = "ADMIN_USER"
|
||||||
envAdminPassword = "ADMIN_PASSWORD"
|
envAdminPassword = "ADMIN_PASSWORD"
|
||||||
|
|
||||||
envLoggingLevel = "LOGGING_LEVEL"
|
|
||||||
|
|
||||||
envServerPort = "SERVER_PORT"
|
envServerPort = "SERVER_PORT"
|
||||||
envServerListen = "SERVER_LISTEN"
|
envServerListen = "SERVER_LISTEN"
|
||||||
envServerTlsEnabled = "SERVER_TLS_ENABLED"
|
envServerTlsEnabled = "SERVER_TLS_ENABLED"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/adrg/xdg"
|
"github.com/adrg/xdg"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -8,6 +9,7 @@ import (
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
"log"
|
"log"
|
||||||
"moul.io/zapgorm2"
|
"moul.io/zapgorm2"
|
||||||
"os"
|
"os"
|
||||||
|
@ -17,6 +19,8 @@ import (
|
||||||
|
|
||||||
type appConfig struct {
|
type appConfig struct {
|
||||||
timeZone string
|
timeZone string
|
||||||
|
isDevelopment bool
|
||||||
|
isDebug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverConfig struct {
|
type serverConfig struct {
|
||||||
|
@ -74,34 +78,77 @@ type Environment struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func bootstrapEnvironment() *Environment {
|
func bootstrapEnvironment() *Environment {
|
||||||
// logging (configured independently)
|
|
||||||
var logger *zap.Logger
|
|
||||||
var err error
|
var err error
|
||||||
level := zap.NewAtomicLevelAt(zapcore.InfoLevel)
|
|
||||||
|
|
||||||
envLoggingLevel := os.Getenv(envLoggingLevel)
|
// bootstrap logging (configured independently and required before any other action)
|
||||||
if envLoggingLevel != "" {
|
loggingLevel := os.Getenv(envLoggingLevel)
|
||||||
if level, err = zap.ParseAtomicLevel(envLoggingLevel); err != nil {
|
if loggingLevel == "" {
|
||||||
|
if err = os.Setenv(envLoggingLevel, loggingLevelDefault); err != nil {
|
||||||
|
log.Fatalf("Cannot set logging level: %v", err)
|
||||||
|
}
|
||||||
|
loggingLevel = os.Getenv(envLoggingLevel)
|
||||||
|
}
|
||||||
|
var level zap.AtomicLevel
|
||||||
|
if level, err = zap.ParseAtomicLevel(loggingLevel); err != nil {
|
||||||
log.Fatalf("Cannot parse logging level: %v", err)
|
log.Fatalf("Cannot parse logging level: %v", err)
|
||||||
}
|
}
|
||||||
|
loggingEncoding := os.Getenv(envLoggingEncoding)
|
||||||
|
if loggingEncoding == "" {
|
||||||
|
if err = os.Setenv(envLoggingEncoding, loggingEncodingDefault); err != nil {
|
||||||
|
log.Fatalf("Cannot set logging encoding: %v", err)
|
||||||
|
}
|
||||||
|
loggingEncoding = os.Getenv(envLoggingEncoding)
|
||||||
|
}
|
||||||
|
if loggingEncoding != "json" && loggingEncoding != "console" {
|
||||||
|
log.Fatalf("Cannot parse logging level: %v", errors.New("only 'json' and 'console' are allowed logging encodings"))
|
||||||
|
}
|
||||||
|
isDebug := level.Level() == zap.DebugLevel
|
||||||
|
isDevelopment := os.Getenv(envDevelopment) == "true"
|
||||||
|
var loggingEncoderConfig zapcore.EncoderConfig
|
||||||
|
if loggingEncoding == "json" {
|
||||||
|
loggingEncoderConfig = zap.NewProductionEncoderConfig()
|
||||||
|
} else {
|
||||||
|
loggingEncoderConfig = zap.NewDevelopmentEncoderConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger, err = zap.NewDevelopment(zap.IncreaseLevel(level))
|
var zapConfig *zap.Config
|
||||||
if err != nil {
|
if isDebug {
|
||||||
log.Fatalf("Can't initialize logger: %v", err)
|
zapConfig = &zap.Config{
|
||||||
|
Level: level,
|
||||||
|
Development: isDevelopment,
|
||||||
|
Encoding: loggingEncoding,
|
||||||
|
EncoderConfig: loggingEncoderConfig,
|
||||||
|
OutputPaths: []string{"stderr"},
|
||||||
|
ErrorOutputPaths: []string{"stderr"},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
zapConfig = &zap.Config{
|
||||||
|
Level: level,
|
||||||
|
Development: isDevelopment,
|
||||||
|
Sampling: &zap.SamplingConfig{
|
||||||
|
Initial: 100,
|
||||||
|
Thereafter: 100,
|
||||||
|
},
|
||||||
|
Encoding: loggingEncoding,
|
||||||
|
EncoderConfig: loggingEncoderConfig,
|
||||||
|
OutputPaths: []string{"stderr"},
|
||||||
|
ErrorOutputPaths: []string{"stderr"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// flushes buffer, if any
|
|
||||||
defer logger.Sync()
|
|
||||||
|
|
||||||
zap.ReplaceGlobals(logger)
|
zapLogger := zap.Must(zapConfig.Build())
|
||||||
|
defer zapLogger.Sync()
|
||||||
|
zap.ReplaceGlobals(zapLogger)
|
||||||
|
|
||||||
// assign defaults from given environment variables and validate
|
// assign defaults from given environment variables and validate
|
||||||
bootstrapFromEnvironmentAndValidate()
|
bootstrapFromEnvironmentAndValidate()
|
||||||
|
|
||||||
// parse environment variables in actual configuration structs
|
// parse environment variables in actual configuration structs
|
||||||
// app config
|
// app config
|
||||||
appConfig := &appConfig{
|
ac := &appConfig{
|
||||||
timeZone: os.Getenv(envTZ),
|
timeZone: os.Getenv(envTZ),
|
||||||
|
isDebug: isDebug,
|
||||||
|
isDevelopment: isDevelopment,
|
||||||
}
|
}
|
||||||
|
|
||||||
// server config
|
// server config
|
||||||
|
@ -137,7 +184,7 @@ func bootstrapEnvironment() *Environment {
|
||||||
corsAllowHeaders: []string{os.Getenv(envCorsAllowHeaders)},
|
corsAllowHeaders: []string{os.Getenv(envCorsAllowHeaders)},
|
||||||
}
|
}
|
||||||
|
|
||||||
authConfig := &authConfig{
|
authC := &authConfig{
|
||||||
adminUser: os.Getenv(envAdminUser),
|
adminUser: os.Getenv(envAdminUser),
|
||||||
adminPassword: os.Getenv(envAdminPassword),
|
adminPassword: os.Getenv(envAdminPassword),
|
||||||
}
|
}
|
||||||
|
@ -179,24 +226,29 @@ func bootstrapEnvironment() *Environment {
|
||||||
zap.L().Sugar().Fatalln("Invalid webhook token length. Reason: must be a positive number")
|
zap.L().Sugar().Fatalln("Invalid webhook token length. Reason: must be a positive number")
|
||||||
}
|
}
|
||||||
|
|
||||||
webhookConfig := &webhookConfig{
|
wc := &webhookConfig{
|
||||||
tokenLength: webhookTokenLength,
|
tokenLength: webhookTokenLength,
|
||||||
}
|
}
|
||||||
|
|
||||||
prometheusConfig := &prometheusConfig{
|
pc := &prometheusConfig{
|
||||||
enabled: os.Getenv(envPrometheusEnabled) == "true",
|
enabled: os.Getenv(envPrometheusEnabled) == "true",
|
||||||
path: os.Getenv(envPrometheusMetricsPath),
|
path: os.Getenv(envPrometheusMetricsPath),
|
||||||
secureTokenEnabled: os.Getenv(envPrometheusSecureTokenEnabled) == "true",
|
secureTokenEnabled: os.Getenv(envPrometheusSecureTokenEnabled) == "true",
|
||||||
secureToken: os.Getenv(envPrometheusSecureToken),
|
secureToken: os.Getenv(envPrometheusSecureToken),
|
||||||
}
|
}
|
||||||
|
|
||||||
if prometheusConfig.enabled && prometheusConfig.secureTokenEnabled {
|
if pc.enabled && pc.secureTokenEnabled {
|
||||||
failIfEnvKeyNotPresent(envPrometheusSecureToken)
|
failIfEnvKeyNotPresent(envPrometheusSecureToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// database setup
|
// database setup
|
||||||
gormLogger := zapgorm2.New(logger)
|
gormConfig := &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}
|
||||||
gormLogger.SetAsDefault()
|
if isDebug && isDevelopment {
|
||||||
|
gormZapLogger := zap.Must(zapConfig.Build())
|
||||||
|
defer gormZapLogger.Sync()
|
||||||
|
gormLogger := zapgorm2.New(gormZapLogger)
|
||||||
|
gormConfig = &gorm.Config{Logger: gormLogger}
|
||||||
|
}
|
||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
zap.L().Sugar().Infof("Using database type '%s'", os.Getenv(envDbType))
|
zap.L().Sugar().Infof("Using database type '%s'", os.Getenv(envDbType))
|
||||||
|
@ -213,7 +265,7 @@ func bootstrapEnvironment() *Environment {
|
||||||
dbFile := os.Getenv(envDbSqliteFile)
|
dbFile := os.Getenv(envDbSqliteFile)
|
||||||
zap.L().Sugar().Infof("Using database file '%s'", dbFile)
|
zap.L().Sugar().Infof("Using database file '%s'", dbFile)
|
||||||
|
|
||||||
if db, err = gorm.Open(sqlite.Open(dbFile), &gorm.Config{Logger: gormLogger}); err != nil {
|
if db, err = gorm.Open(sqlite.Open(dbFile), gormConfig); err != nil {
|
||||||
zap.L().Sugar().Fatalf("Could not setup database: %v", err)
|
zap.L().Sugar().Fatalf("Could not setup database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +289,7 @@ func bootstrapEnvironment() *Environment {
|
||||||
}
|
}
|
||||||
|
|
||||||
dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=disable TimeZone=%v", host, dbUser, dbPass, dbName, port, dbTZ)
|
dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=disable TimeZone=%v", host, dbUser, dbPass, dbName, port, dbTZ)
|
||||||
if db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: gormLogger}); err != nil {
|
if db, err = gorm.Open(postgres.Open(dsn), gormConfig); err != nil {
|
||||||
zap.L().Sugar().Fatalf("Could not setup database: %v", err)
|
zap.L().Sugar().Fatalf("Could not setup database: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -248,13 +300,13 @@ func bootstrapEnvironment() *Environment {
|
||||||
zap.L().Sugar().Fatalf("Could not setup database")
|
zap.L().Sugar().Fatalf("Could not setup database")
|
||||||
}
|
}
|
||||||
|
|
||||||
env := &Environment{appConfig: appConfig,
|
env := &Environment{appConfig: ac,
|
||||||
authConfig: authConfig,
|
authConfig: authC,
|
||||||
serverConfig: sc,
|
serverConfig: sc,
|
||||||
taskConfig: tc,
|
taskConfig: tc,
|
||||||
lockConfig: lc,
|
lockConfig: lc,
|
||||||
webhookConfig: webhookConfig,
|
webhookConfig: wc,
|
||||||
prometheusConfig: prometheusConfig,
|
prometheusConfig: pc,
|
||||||
db: db}
|
db: db}
|
||||||
|
|
||||||
if err = env.db.AutoMigrate(&Update{}, &Webhook{}, &Event{}); err != nil {
|
if err = env.db.AutoMigrate(&Update{}, &Webhook{}, &Event{}); err != nil {
|
||||||
|
|
Loading…
Reference in a new issue