feat(config,loggin): Defining global config loading and multi-level logging mechanism

Config (backend/internal/confi)
- Defined the global config loading in internal/config/config.go
- - Defined DatabaseSN() and RedisDSN() to expose their respective connection strings
- Defined the env example to refer to while creating the .env file in your local
Logging (backend/pkg/logger)
- Defined the global loggin mechanism ( logger.go )
- Created and validated the logging through tests ( logger_test.go )written and validated
- Added a reference ( example_test.go ) to refer to understand about usage of logging mechanims

This commit establishes the foundation for all future development.

Story: E1-002 - Backend Project Initialization (Partial)
This commit is contained in:
Rezon Philip 2025-11-29 16:40:25 +05:30
parent 6187036bc5
commit 96651fceea
8 changed files with 947 additions and 0 deletions

55
backend/.env.example Normal file
View File

@ -0,0 +1,55 @@
# ==============================================================================
# APPLICATION
# ==============================================================================
APP_ENV=development
SERVER_PORT=8080
SERVER_READ_TIMEOUT=10s
SERVER_WRITE_TIMEOUT=10s
# ==============================================================================
# DATABASE (PostgreSQL)
# ==============================================================================
DB_HOST=localhost
DB_PORT=5432
DB_USER=aurganize
DB_PASSWORD=aurganize_dev_pass_change_in_production
DB_NAME=aurganize_v62
DB_SSLMODE=disable
# Connection Pool
DB_MAX_OPEN_CONNS=25
DB_MAX_IDLE_CONNS=5
DB_CONN_MAX_LIFETIME=5m
# ==============================================================================
# JWT AUTHENTICATION
# ==============================================================================
# IMPORTANT: Change these secrets in production!
# Generate with: openssl rand -base64 32
JWT_ACCESS_SECRET=your-super-secret-access-key-min-32-chars-change-in-production
JWT_REFRESH_SECRET=your-super-secret-refresh-key-min-32-chars-must-be-different
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=168h
JWT_ISSUER=aurganize-v62
# ==============================================================================
# REDIS (Caching & Sessions)
# ==============================================================================
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# ==============================================================================
# NATS (Event Messaging)
# ==============================================================================
NATS_URL=nats://localhost:4222
# ==============================================================================
# MINIO (S3-Compatible Storage)
# ==============================================================================
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=aurganize
MINIO_USE_SSL=false

7
backend/cmd/api/main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Println("Aurganize")
}

46
backend/go.mod Normal file
View File

@ -0,0 +1,46 @@
module github.com/creativenoz/aurganize-v62/backend
go 1.25.2
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.28.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/labstack/echo/v4 v4.13.4 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/nats-io/nats.go v1.47.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/redis/go-redis/v9 v9.17.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/gorm v1.31.1 // indirect
)

103
backend/go.sum Normal file
View File

@ -0,0 +1,103 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
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/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.17.1 h1:7tl732FjYPRT9H9aNfyTwKg9iTETjWjGKEJ2t/5iWTs=
github.com/redis/go-redis/v9 v9.17.1/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

View File

@ -0,0 +1,209 @@
package config
import (
"fmt"
"os"
"strconv"
"time"
"github.com/joho/godotenv"
)
type Config struct {
Server ServerConfig
Database DatabaseConfig
JWT JWTConfig
Redis RedisConfig
NATS NATSConfig
Storage StorageConfig
}
// ServerConfig type holds the information about the http server settings
type ServerConfig struct {
Port string // HTTP port to listen on
Environment string // can be development, staging, production
ReadTimeout time.Duration // Max time to read request
WriteTimeout time.Duration // Max time to write response
}
// DatabaseConfig contains postgresSQL connection settings
type DatabaseConfig struct {
Host string // Database host
Port string // Database port
User string // Database user
Password string // Database password
DBName string // Database name
SSLMode string // SSL mode : disable, require, verify-full ? not sure what this field is set for
MaxOpenConns int // Maximum open connections
MaxIdleConns int // Maximum idle connections
ConnMaxLifetime time.Duration // Maximum connection lifetime
}
// JWT Config contains JWT token settings
type JWTConfig struct {
AccessSecret string // Secret for access tokens
RefreshSecret string // Secret for refresh tokens
AccessExpiry time.Duration // Accees token expiry (15 minutes)
RefreshExpiry time.Duration // Refresh token expiry (7 days)
Issuer string // Token issuer claim
}
type RedisConfig struct {
Host string // Redis host
Port string // Redis port
Password string // Redis password (set to empty if no auth is set)
DB int // Redis database number
}
// NATSConfig contains NATS messaging settings
type NATSConfig struct {
URL string // NATS server URL
}
// StorageCongfig contains MinIO (s3) settings
type StorageConfig struct {
Endpoint string // MinIO endpoint
AccessKeyID string // Access key
SecretAccessKey string // Secret key
BucketName string // Bucket name
UseSSL bool // User HTTPS
}
func Load() (*Config, error) {
if os.Getenv("APP_ENV") != "production" {
if err := godotenv.Load(); err != nil {
fmt.Println("Warning: .env file not found, using environment variables")
}
}
cfg := &Config{
Server: ServerConfig{
Port: getEnv("SERVER_PORT", "8080"),
Environment: getEnv("APP_ENV", "development"),
ReadTimeout: parseDuration(getEnv("SERVER_READ_TIMEOUT", "10s")),
WriteTimeout: parseDuration(getEnv("SERVER_WRITE_TIMEOUT", "10s")),
},
Database: DatabaseConfig{
Host: getEnv("DB_HOST", "localhost"),
Port: getEnv("DB_PORT", "5432"),
User: getEnv("DB_USER", "aurganize"),
Password: getEnv("DB_PASSWORD", ""),
DBName: getEnv("DB_NAME", "aruganize_db_1"),
SSLMode: getEnv("DB_SSLMODE", "disable"),
MaxOpenConns: parseInt(getEnv("DB_MAX_OPEN_CONNECTIONS", "25")),
MaxIdleConns: parseInt(getEnv("DB_MAX_IDLE_CONNECTIONS", "5")),
ConnMaxLifetime: parseDuration(getEnv("DB_CONNECTION_MAX_LIFETIME", "5m")),
},
JWT: JWTConfig{
AccessSecret: getEnv("JWT_ACCESS_SECRET", ""),
RefreshSecret: getEnv("JWT_REFRESH_SECRET", ""),
AccessExpiry: parseDuration(getEnv("JWT_ACCESS_EXPIRY", "15m")),
RefreshExpiry: parseDuration(getEnv("JWT_REFRESH_EXPIRY", "168h")),
Issuer: getEnv("JWT_ISSUER", "aurganize-v62"),
},
Redis: RedisConfig{
Host: getEnv("REDIST_HOST", "localhost"),
Port: getEnv("REDIS_PORT", "6379"),
Password: getEnv("REDIS_PASSWORD", ""),
DB: parseInt(getEnv("REDIS_DB", "0")),
},
NATS: NATSConfig{
URL: getEnv("NATS_URL", "nats://localhost:4222"),
},
Storage: StorageConfig{
Endpoint: getEnv("MINIO_ENDPOINT", "localhost:9000"),
AccessKeyID: getEnv("MINIO_ACCESS_KEY", "minioadmin"),
SecretAccessKey: getEnv("MINIO_SECRET_KEY", "miniosecretkey"),
BucketName: getEnv("MINIO_BUCKET", "aurganize_bucket_1"),
UseSSL: parseBool(getEnv("MINIO_USE_SSL", "false")),
},
}
if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("configuration validation failure [%w]", err)
}
return cfg, nil
}
// Validate checks if all required configuration is present and valid
func (c *Config) Validate() error {
// Database password required in production
if c.Database.Password == "" && c.Server.Environment == "production" {
return fmt.Errorf("DB_PASSWORD is required in production")
}
// JWT secrets are required always
if c.JWT.AccessSecret == "" {
return fmt.Errorf("JWT_ACCESS_SECRET is required")
}
if c.JWT.RefreshSecret == "" {
return fmt.Errorf("JWT_REFRESH_SECRET is required")
}
// JWT secrets should be different
if c.JWT.AccessSecret == c.JWT.RefreshSecret {
return fmt.Errorf("JWT_ACCESS_SECRET and JWT_REFRESH_SECRET must be different")
}
validEnvs := map[string]bool{
"development": true,
"test": true,
"staging": true,
"UAT": true,
"production": true,
}
if !validEnvs[c.Server.Environment] {
return fmt.Errorf("invalid environment configured in enviroment")
}
return nil
}
// Helper Functions
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func parseDuration(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
return 0
}
return d
}
func parseInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
return 0
}
return i
}
func parseBool(s string) bool {
b, err := strconv.ParseBool(s)
if err != nil {
return false
}
return b
}
// DatabaseDSN returns the PostgreSQL connection string
func (c *Config) DatabaseDSN() string {
return fmt.Sprintf(
"host=%s post %s user=%s password=%s dbname=%s sslmode=%s",
c.Database.Host,
c.Database.Port,
c.Database.User,
c.Database.Password,
c.Database.DBName,
c.Database.SSLMode,
)
}
// RedisDSN returns the Redis connection string
func (c *Config) RedisDSN() string {
return fmt.Sprintf("%s:%s", c.Redis.Host, c.Redis.Port)
}

View File

@ -0,0 +1,107 @@
package logger_test
import (
"time"
"github.com/creativenoz/aurganize-v62/backend/pkg/logger"
"github.com/rs/zerolog/log"
)
// Example_basicUsage demonstrates basic logger usage
func Example_basicUsage() {
// Initialize logger
logger.Init("development")
// Log at different levels
log.Debug().Msg("This is debug information")
log.Info().Msg("This is informational")
log.Warn().Msg("This is a warning")
log.Error().Msg("This is an error")
// Output depends on log level
}
// Example_structuredLogging demonstrates structured logging with fields
func Example_structuredLogging() {
logger.Init("development")
// Log with structured fields
log.Info().
Str("user_id", "12345").
Str("action", "login").
Bool("success", true).
Dur("duration", 150*time.Millisecond).
Msg("User login attempt")
// JSON output:
// {
// "level": "info",
// "user_id": "12345",
// "action": "login",
// "success": true,
// "duration": 150,
// "message": "User login attempt"
// }
}
// Example_contextLogger demonstrates creating logger with context
func Example_contextLogger() {
logger.Init("development")
// Create logger with request context
requestLogger := logger.WithContext(map[string]interface{}{
"request_id": "req-abc-123",
"user_id": "user-456",
"ip": "192.168.1.1",
})
// All logs from this logger include context
requestLogger.Info().Msg("Request started")
requestLogger.Info().Msg("Processing payment")
requestLogger.Info().Msg("Request completed")
// All three logs include request_id, user_id, and ip
}
// Example_errorLogging demonstrates logging errors
func Example_errorLogging() {
logger.Init("development")
// Simulate an error
err := someFunction()
if err != nil {
log.Error().
Err(err). // Add error
Str("user_id", "123").
Str("operation", "database_query").
Msg("Failed to fetch user")
}
}
// Example_subLogger demonstrates module-specific loggers
func Example_subLogger() {
logger.Init("development")
// Create a sub-logger for authentication module
authLogger := log.With().
Str("module", "auth").
Str("version", "v1").
Logger()
authLogger.Info().Msg("Auth module initialized")
authLogger.Debug().Msg("Loading auth configuration")
// Create a sub-logger for database module
dbLogger := log.With().
Str("module", "database").
Logger()
dbLogger.Info().Msg("Database connection established")
// Now you can filter logs by module in production
}
// Helper function for example
func someFunction() error {
return nil
}

View File

@ -0,0 +1,208 @@
package logger
import (
"io"
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
const (
LevelDebug = "debug"
LevelInfo = "info"
LevelWarn = "warn"
LevelError = "error"
)
// Init initializes the global logger with environment-specific settings.
// Call this once at application startup.
//
// Environment determines output format:
// - development: Pretty console output with colors
// - staging/production: JSON output for log aggregation
//
// Example:
//
// logger.Init("development")
// log.Info().Msg("Application started")
func Init(enviroment string) {
// Configuring the time format for loggin
// Unix style timestamp in production for efficiency
// Humar-readable in development
if enviroment == "production" {
// This is the unixtimestamp format used in production for efficiency : 1732632645
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
} else {
zerolog.TimeFieldFormat = time.RFC3339
// This is format for human readable format : 2025-11-26T10:30:45-05:00
}
var output io.Writer = os.Stdout
if enviroment == "development" {
output = zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: time.RFC3339, // 2025-11-26T10:30:45-05:00
NoColor: false, // Enabling Colours
}
}
// Production uses default JSON output to stdout
// Set global logger
// Caller() : this caused adtional overhead as runtime.Caller() is called, which is worth
// for debugging value, should disable in production
log.Logger = zerolog.New(output).
With().
Timestamp().
Caller(). // This line is to add file and line number information into the log
Logger()
// set global log level
// Debug (most verbose)
// ↓
// Info
// ↓
// Warn
// ↓
// Error
// ↓
// Fatal (least verbose)
switch enviroment {
case "production":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "UAT":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "staging":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "test":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case "development":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
// Log initialization
log.Info().
Str("environment", enviroment).
Str("log_level", zerolog.GlobalLevel().String()).
Msg("logger initialized")
}
func InitWithLevel(environment, level string) {
Init(environment)
switch level {
case LevelDebug:
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case LevelInfo:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case LevelWarn:
zerolog.SetGlobalLevel(zerolog.WarnLevel)
case LevelError:
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
default:
log.Warn().
Str("provided_level", level).
Str("using_level", zerolog.GlobalLevel().String()).
Msg("Invalid log level, using default")
}
log.Info().
Str("environment", environment).
Str("log_level", zerolog.GlobalLevel().String()).
Msg("Logger initialized with custom level")
}
// GetLogger returns the global logger instance.
// Use this to get a logger with additional context.
//
// Example :
//
// logger := logger.GetLogger().
// With().
// Str("module","auth").
// Logger()
// logger.Info().Msg("Auth module started")
func GetLogger() *zerolog.Logger {
return &log.Logger
}
// WithContext returns a logger with additional context fields.
// Useful for adding request-specific context.
//
// Example:
//
// contextLogger := logger.WithContext(map[string]interface{}{
// "request_id": "abc-123",
// "user_id": "user-456",
// })
// contextLogger.Info().Msg("Processing request")
func WithContext(fields map[string]interface{}) *zerolog.Logger {
logger := log.Logger
for key, value := range fields {
logger = logger.With().Interface(key, value).Logger()
}
return &logger
}
// Example Usage log functions (for familiarizing its usage)
// LogDebug logs debug information (these are verbose, development only)
func ExampleDebug() {
log.Debug().
Str("function", "ExampleDebug").
Int("iteration", 1).
Msg("Debug information")
}
func ExampleInfo() {
log.Info().
Str("user_id", "123").
Str("reason", "invalid_token").
Msg("User logged in successfully")
}
func ExampleError() {
log.Error().
Err(nil). // we add the actual error here
Str("user_id", "123").
Str("operation", "database_query").
Msg("Failed to fetch user data")
}
// Log Fatal logs fatal erros and exits the application
// user sparingly - only for unrecoverable errors
func ExmapleFatal() {
// log.Fatal().
// Err(err).
// Msg("Cannot connect to database")
// -- Application exits after this
}
// Log with fields demonstrated logging mutliple fields
func ExampleWithFields() {
log.Info().
Str("user_id", "123").
Str("email", "user@example.com").
Int("login_attemtps", 3).
Bool("success", true).
Dur("duration", 150*time.Millisecond).
Msg("login completed")
}
// Log with SubLogger demonstrates creating sub-loggers
func ExampleSubLogger() {
// create a sub-logger for a specific module
authLogger := log.With().
Str("module", "auth").
Str("version", "v1").
Logger()
authLogger.Info().Msg("Auth module initialized")
authLogger.Debug().Msg("Loading auth configuration")
}

View File

@ -0,0 +1,212 @@
package logger
import (
"bytes"
"testing"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
)
// TestInit_Developement tests logger initialization in development mode
func TestInit_Development(t *testing.T) {
// Arrange
environment := "development"
// Act
Init(environment)
// Assert
assert.Equal(t, zerolog.DebugLevel, zerolog.GlobalLevel(), "Development should use Debug level")
}
// TestInit_Production tests logger initialization in production mode
func TestInit_Production(t *testing.T) {
// Arrange
environment := "production"
// Act
Init(environment)
// Assert
assert.Equal(t, zerolog.InfoLevel, zerolog.GlobalLevel(), "Production should use Info level")
}
func TestInitWithLevel(t *testing.T) {
tests := []struct {
name string
environment string
level string
expectedLevel zerolog.Level
}{
{
name: "Debug level",
environment: "production",
level: LevelDebug,
expectedLevel: zerolog.DebugLevel,
},
{
name: "Info level",
environment: "production",
level: LevelInfo,
expectedLevel: zerolog.InfoLevel,
},
{
name: "Warn level",
environment: "production",
level: LevelWarn,
expectedLevel: zerolog.WarnLevel,
},
{
name: "Error level",
environment: "production",
level: LevelError,
expectedLevel: zerolog.ErrorLevel,
},
{
name: "Invalid level defaults to Info",
environment: "production",
level: "invalid",
expectedLevel: zerolog.InfoLevel,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Act
InitWithLevel(test.environment, test.level)
// Assert
assert.Equal(t, test.expectedLevel, zerolog.GlobalLevel())
})
}
}
// TestGetLogger tests getting logger instance
func TestGetLogger(t *testing.T) {
// Arrage
Init("development")
// Act
logger := GetLogger()
// Assert
assert.NotNil(t, logger, "GetLogger should return non-nil logger")
}
// TestWithContext tests logging with additional context
func TestWithContext(t *testing.T) {
// Arrange
var buff bytes.Buffer
log.Logger = zerolog.New(&buff)
fields := map[string]interface{}{
"request_id": "test-123",
"user_id": "user-456",
}
// Act
logger := WithContext(fields)
logger.Info().Msg("Test message")
// Assert
output := buff.String()
assert.Contains(t, output, "test-123", "should include the request_id")
assert.Contains(t, output, "user-456", "should include the user_id")
assert.Contains(t, output, "Test message", "should include message")
}
// TestLogLevels tests that log levels fitler correctly
func TestLogLevels(t *testing.T) {
tests := []struct {
name string
setLevel zerolog.Level
logLevel zerolog.Level
shouldAppear bool
}{
{
name: "Debug message appears when level is Debug",
setLevel: zerolog.DebugLevel,
logLevel: zerolog.DebugLevel,
shouldAppear: true,
},
{
name: "Debug message hidden when level is Info",
setLevel: zerolog.InfoLevel,
logLevel: zerolog.DebugLevel,
shouldAppear: false,
},
{
name: "Error message appears when level is Info",
setLevel: zerolog.InfoLevel,
logLevel: zerolog.ErrorLevel,
shouldAppear: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Arrange
var buff bytes.Buffer
zerolog.SetGlobalLevel(test.setLevel)
log.Logger = zerolog.New(&buff)
// Act
switch test.logLevel {
case zerolog.DebugLevel:
log.Debug().Msg("test message")
case zerolog.InfoLevel:
log.Info().Msg("test message")
case zerolog.ErrorLevel:
log.Error().Msg("test message")
}
// Assert
output := buff.String()
if test.shouldAppear {
assert.Contains(t, output, "test message")
} else {
assert.Empty(t, output)
}
})
}
}
// BenchmarkLogger benchmarks logger performance
func BenchmarkLogger(b *testing.B) {
// Setup
var buff bytes.Buffer
log.Logger = zerolog.New(&buff)
b.ResetTimer()
// Run Benchmark
for i := 0; i < b.N; i++ {
log.Info().
Str("key", "value").
Int("number", 42).
Msg("Benchmark message")
}
}
// Benchmark Logger with multiple fields
func BenchmarkLoggerWithFields(b *testing.B) {
// Setup
var buff bytes.Buffer
log.Logger = zerolog.New(&buff)
b.ResetTimer()
// Run Benchmark
for i := 0; i < b.N; i++ {
log.Info().
Str("request_id", "abc-123").
Str("user_id", "user-456").
Str("method", "POST").
Str("path", "/api/v1/users").
Int("status", 200).
Dur("duration", 150).
Msg("Request completed")
}
}