feat: configurable component-level logging (#575)

* Refactor logging to use centralized logger utility

- Removed direct usage of zerolog in multiple files and replaced it with a centralized logging utility in the `utils` package.
- Introduced `Loggers` struct to manage different loggers (Audit, HTTP, App) with configurable levels and outputs.
- Updated all relevant files to utilize the new logging structure, ensuring consistent logging practices across the application.
- Enhanced error handling and logging messages for better traceability and debugging.

* refactor: update logging implementation to use new logger structure

* Refactor logging to use tlog package

- Replaced instances of utils logging with tlog in various controllers, services, and middleware.
- Introduced audit logging for login success, login failure, and logout events.
- Created tlog package with structured logging capabilities using zerolog.
- Added tests for the new tlog logger functionality.

* refactor: update logging configuration in environment files

* fix: adding coderabbit suggestions

* fix: ensure correct audit caller

* fix: include reason in audit login failure logs
This commit is contained in:
Pushpinder Singh
2026-01-15 08:57:19 -05:00
committed by GitHub
parent ba2d732415
commit 53bd413046
28 changed files with 486 additions and 214 deletions

View File

@@ -3,13 +3,10 @@ package main
import (
"errors"
"fmt"
"os"
"strings"
"time"
"github.com/charmbracelet/huh"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/traefik/paerser/cli"
"golang.org/x/crypto/bcrypt"
)
@@ -43,7 +40,7 @@ func createUserCmd() *cli.Command {
Configuration: tCfg,
Resources: loaders,
Run: func(_ []string) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
tlog.NewSimpleLogger().Init()
if tCfg.Interactive {
form := huh.NewForm(
@@ -77,7 +74,7 @@ func createUserCmd() *cli.Command {
return errors.New("username and password cannot be empty")
}
log.Info().Str("username", tCfg.Username).Msg("Creating user")
tlog.App.Info().Str("username", tCfg.Username).Msg("Creating user")
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
if err != nil {
@@ -90,7 +87,7 @@ func createUserCmd() *cli.Command {
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
}
log.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
tlog.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
return nil
},

View File

@@ -5,15 +5,13 @@ import (
"fmt"
"os"
"strings"
"time"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/charmbracelet/huh"
"github.com/mdp/qrterminal/v3"
"github.com/pquerna/otp/totp"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/traefik/paerser/cli"
)
@@ -42,7 +40,7 @@ func generateTotpCmd() *cli.Command {
Configuration: tCfg,
Resources: loaders,
Run: func(_ []string) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
tlog.NewSimpleLogger().Init()
if tCfg.Interactive {
form := huh.NewForm(
@@ -91,9 +89,9 @@ func generateTotpCmd() *cli.Command {
secret := key.Secret()
log.Info().Str("secret", secret).Msg("Generated TOTP secret")
tlog.App.Info().Str("secret", secret).Msg("Generated TOTP secret")
log.Info().Msg("Generated QR code")
tlog.App.Info().Msg("Generated QR code")
config := qrterminal.Config{
Level: qrterminal.L,
@@ -112,7 +110,7 @@ func generateTotpCmd() *cli.Command {
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
}
log.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
tlog.App.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
return nil
},

View File

@@ -9,8 +9,7 @@ import (
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/traefik/paerser/cli"
)
@@ -27,7 +26,7 @@ func healthcheckCmd() *cli.Command {
Resources: nil,
AllowArg: true,
Run: func(args []string) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
tlog.NewSimpleLogger().Init()
appUrl := os.Getenv("TINYAUTH_APPURL")
@@ -39,7 +38,7 @@ func healthcheckCmd() *cli.Command {
return errors.New("TINYAUTH_APPURL is not set and no argument was provided")
}
log.Info().Str("app_url", appUrl).Msg("Performing health check")
tlog.App.Info().Str("app_url", appUrl).Msg("Performing health check")
client := http.Client{
Timeout: 30 * time.Second,
@@ -77,7 +76,7 @@ func healthcheckCmd() *cli.Command {
return fmt.Errorf("failed to decode response: %w", err)
}
log.Info().Interface("response", healthResp).Msg("Tinyauth is healthy")
tlog.App.Info().Interface("response", healthResp).Msg("Tinyauth is healthy")
return nil
},

View File

@@ -2,22 +2,18 @@ package main
import (
"fmt"
"os"
"strings"
"time"
"github.com/steveiliop56/tinyauth/internal/bootstrap"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/utils/loaders"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/traefik/paerser/cli"
)
func NewTinyauthCmdConfiguration() *config.Config {
return &config.Config{
LogLevel: "info",
ResourcesDir: "./resources",
DatabasePath: "./tinyauth.db",
Server: config.ServerConfig{
@@ -39,6 +35,24 @@ func NewTinyauthCmdConfiguration() *config.Config {
Insecure: false,
SearchFilter: "(uid=%s)",
},
Log: config.LogConfig{
Level: "info",
Json: false,
Streams: config.LogStreams{
HTTP: config.LogStreamConfig{
Enabled: true,
Level: "",
},
App: config.LogStreamConfig{
Enabled: true,
Level: "",
},
Audit: config.LogStreamConfig{
Enabled: false,
Level: "",
},
},
},
Experimental: config.ExperimentalConfig{
ConfigFile: "",
},
@@ -102,25 +116,14 @@ func main() {
}
func runCmd(cfg config.Config) error {
logLevel, err := zerolog.ParseLevel(strings.ToLower(cfg.LogLevel))
logger := tlog.NewLogger(cfg.Log)
logger.Init()
if err != nil {
log.Error().Err(err).Msg("Invalid or missing log level, defaulting to info")
} else {
zerolog.SetGlobalLevel(logLevel)
}
log.Logger = log.With().Caller().Logger()
if !cfg.LogJSON {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
}
log.Info().Str("version", config.Version).Msg("Starting tinyauth")
tlog.App.Info().Str("version", config.Version).Msg("Starting tinyauth")
app := bootstrap.NewBootstrapApp(cfg)
err = app.Setup()
err := app.Setup()
if err != nil {
return fmt.Errorf("failed to bootstrap app: %w", err)

View File

@@ -3,15 +3,12 @@ package main
import (
"errors"
"fmt"
"os"
"time"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
"github.com/charmbracelet/huh"
"github.com/pquerna/otp/totp"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/traefik/paerser/cli"
"golang.org/x/crypto/bcrypt"
)
@@ -47,7 +44,7 @@ func verifyUserCmd() *cli.Command {
Configuration: tCfg,
Resources: loaders,
Run: func(_ []string) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
tlog.NewSimpleLogger().Init()
if tCfg.Interactive {
form := huh.NewForm(
@@ -101,9 +98,9 @@ func verifyUserCmd() *cli.Command {
if user.TotpSecret == "" {
if tCfg.Totp != "" {
log.Warn().Msg("User does not have TOTP secret")
tlog.App.Warn().Msg("User does not have TOTP secret")
}
log.Info().Msg("User verified")
tlog.App.Info().Msg("User verified")
return nil
}
@@ -113,7 +110,7 @@ func verifyUserCmd() *cli.Command {
return fmt.Errorf("TOTP code incorrect")
}
log.Info().Msg("User verified")
tlog.App.Info().Msg("User verified")
return nil
},