Skip to content

Commit 7b2cb63

Browse files
committed
refactor(logger): migrate from glog to slog with structured logging
1 parent 473fa4f commit 7b2cb63

31 files changed

Lines changed: 1003 additions & 395 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ This project uses [pre-commit](https://pre-commit.io/) for code quality checks.
155155
- **[Database](docs/database.md)** - Schema, migrations, and data model
156156
- **[Deployment](docs/deployment.md)** - Container images, Kubernetes deployment, and configuration
157157
- **[Authentication](docs/authentication.md)** - Development and production auth
158+
- **[Logging](docs/logging.md)** - Structured logging, OpenTelemetry integration, and data masking
158159

159160
### Additional Resources
160161

cmd/hyperfleet-api/environments/framework.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package environments
22

33
import (
4+
"context"
45
"os"
56
"strings"
67

7-
"github.com/golang/glog"
88
"github.com/spf13/pflag"
99

1010
"github.com/openshift-hyperfleet/hyperfleet-api/cmd/hyperfleet-api/environments/registry"
1111
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/client/ocm"
1212
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/config"
1313
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/errors"
14+
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/logger"
1415
)
1516

1617
func init() {
@@ -64,38 +65,45 @@ func (e *Env) AddFlags(flags *pflag.FlagSet) error {
6465
// This should be called after the e.Config has been set appropriately though AddFlags and pasing, done elsewhere
6566
// The environment does NOT handle flag parsing
6667
func (e *Env) Initialize() error {
67-
glog.Infof("Initializing %s environment", e.Name)
68+
ctx := context.Background()
69+
logger.Infof(ctx, "Initializing %s environment", e.Name)
6870

6971
envImpl, found := environments[e.Name]
7072
if !found {
71-
glog.Fatalf("Unknown runtime environment: %s", e.Name)
73+
logger.Errorf(ctx, "Unknown runtime environment: %s", e.Name)
74+
os.Exit(1)
7275
}
7376

7477
if err := envImpl.OverrideConfig(e.Config); err != nil {
75-
glog.Fatalf("Failed to configure ApplicationConfig: %s", err)
78+
logger.Errorf(ctx, "Failed to configure ApplicationConfig: %s", err)
79+
os.Exit(1)
7680
}
7781

7882
messages := environment.Config.ReadFiles()
7983
if len(messages) != 0 {
80-
glog.Fatalf("unable to read configuration files:\n%s", strings.Join(messages, "\n"))
84+
logger.Errorf(ctx, "unable to read configuration files:\n%s", strings.Join(messages, "\n"))
85+
os.Exit(1)
8186
}
8287

8388
// each env will set db explicitly because the DB impl has a `once` init section
8489
if err := envImpl.OverrideDatabase(&e.Database); err != nil {
85-
glog.Fatalf("Failed to configure Database: %s", err)
90+
logger.Errorf(ctx, "Failed to configure Database: %s", err)
91+
os.Exit(1)
8692
}
8793

8894
err := e.LoadClients()
8995
if err != nil {
9096
return err
9197
}
9298
if err := envImpl.OverrideClients(&e.Clients); err != nil {
93-
glog.Fatalf("Failed to configure Clients: %s", err)
99+
logger.Errorf(ctx, "Failed to configure Clients: %s", err)
100+
os.Exit(1)
94101
}
95102

96103
e.LoadServices()
97104
if err := envImpl.OverrideServices(&e.Services); err != nil {
98-
glog.Fatalf("Failed to configure Services: %s", err)
105+
logger.Errorf(ctx, "Failed to configure Services: %s", err)
106+
os.Exit(1)
99107
}
100108

101109
seedErr := e.Seed()
@@ -104,7 +112,8 @@ func (e *Env) Initialize() error {
104112
}
105113

106114
if err := envImpl.OverrideHandlers(&e.Handlers); err != nil {
107-
glog.Fatalf("Failed to configure Handlers: %s", err)
115+
logger.Errorf(ctx, "Failed to configure Handlers: %s", err)
116+
os.Exit(1)
108117
}
109118

110119
return nil
@@ -123,6 +132,7 @@ func (e *Env) LoadServices() {
123132
}
124133

125134
func (e *Env) LoadClients() error {
135+
ctx := context.Background()
126136
var err error
127137

128138
ocmConfig := ocm.Config{
@@ -136,36 +146,38 @@ func (e *Env) LoadClients() error {
136146

137147
// Create OCM Authz client
138148
if e.Config.OCM.EnableMock {
139-
glog.Infof("Using Mock OCM Authz Client")
149+
logger.Info(ctx, "Using Mock OCM Authz Client")
140150
e.Clients.OCM, err = ocm.NewClientMock(ocmConfig)
141151
} else {
142152
e.Clients.OCM, err = ocm.NewClient(ocmConfig)
143153
}
144154
if err != nil {
145-
glog.Errorf("Unable to create OCM Authz client: %s", err.Error())
155+
logger.Errorf(ctx, "Unable to create OCM Authz client: %s", err)
146156
return err
147157
}
148158

149159
return nil
150160
}
151161

152162
func (e *Env) Teardown() {
163+
ctx := context.Background()
153164
if e.Database.SessionFactory != nil {
154165
if err := e.Database.SessionFactory.Close(); err != nil {
155-
glog.Errorf("Error closing database session factory: %s", err.Error())
166+
logger.Errorf(ctx, "Error closing database session factory: %s", err)
156167
}
157168
}
158169
if e.Clients.OCM != nil {
159170
if err := e.Clients.OCM.Close(); err != nil {
160-
glog.Errorf("Error closing OCM client: %v", err)
171+
logger.Errorf(ctx, "Error closing OCM client: %v", err)
161172
}
162173
}
163174
}
164175

165176
func setConfigDefaults(flags *pflag.FlagSet, defaults map[string]string) error {
177+
ctx := context.Background()
166178
for name, value := range defaults {
167179
if err := flags.Set(name, value); err != nil {
168-
glog.Errorf("Error setting flag %s: %v", name, err)
180+
logger.Error(ctx, "Error setting flag", "flag", name, "error", err)
169181
return err
170182
}
171183
}

cmd/hyperfleet-api/main.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
6+
"log/slog"
7+
"os"
58

6-
"github.com/golang/glog"
79
"github.com/spf13/cobra"
810

911
"github.com/openshift-hyperfleet/hyperfleet-api/cmd/hyperfleet-api/migrate"
1012
"github.com/openshift-hyperfleet/hyperfleet-api/cmd/hyperfleet-api/servecmd"
13+
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/logger"
1114

1215
// Import plugins to trigger their init() functions
1316
// _ "github.com/openshift-hyperfleet/hyperfleet-api/plugins/events" // REMOVED: Events plugin no longer exists
@@ -20,18 +23,14 @@ import (
2023
// nolint
2124

2225
func main() {
23-
// This is needed to make `glog` believe that the flags have already been parsed, otherwise
24-
// every log messages is prefixed by an error message stating the the flags haven't been
25-
// parsed.
26-
if err := flag.CommandLine.Parse([]string{}); err != nil {
27-
glog.Fatalf("Failed to parse flags: %v", err)
28-
}
29-
30-
//pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
26+
// Initialize logger first (before any logging occurs)
27+
initDefaultLogger()
28+
ctx := context.Background()
3129

32-
// Always log to stderr by default
33-
if err := flag.Set("logtostderr", "true"); err != nil {
34-
glog.Infof("Unable to set logtostderr to true")
30+
// Parse flags (needed for cobra compatibility)
31+
if err := flag.CommandLine.Parse([]string{}); err != nil {
32+
logger.Error(ctx, "Failed to parse flags", "error", err)
33+
os.Exit(1)
3534
}
3635

3736
rootCmd := &cobra.Command{
@@ -47,6 +46,29 @@ func main() {
4746
rootCmd.AddCommand(migrateCmd, serveCmd)
4847

4948
if err := rootCmd.Execute(); err != nil {
50-
glog.Fatalf("error running command: %v", err)
49+
logger.Error(ctx, "Error running command", "error", err)
50+
os.Exit(1)
51+
}
52+
}
53+
54+
// initDefaultLogger initializes a default logger with INFO level
55+
// This ensures logging works before environment/config is loaded
56+
func initDefaultLogger() {
57+
cfg := &logger.LogConfig{
58+
Level: slog.LevelInfo,
59+
Format: logger.FormatJSON,
60+
Output: os.Stdout,
61+
Component: "hyperfleet-api",
62+
Version: "unknown",
63+
Hostname: getHostname(),
64+
}
65+
logger.InitGlobalLogger(cfg)
66+
}
67+
68+
func getHostname() string {
69+
hostname, err := os.Hostname()
70+
if err != nil {
71+
return "unknown"
5172
}
73+
return hostname
5274
}

cmd/hyperfleet-api/migrate/cmd.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package migrate
33
import (
44
"context"
55
"flag"
6+
"os"
67

7-
"github.com/golang/glog"
8-
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/db/db_session"
98
"github.com/spf13/cobra"
109

1110
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/config"
1211
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/db"
12+
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/db/db_session"
13+
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/logger"
1314
)
1415

1516
var dbConfig = config.NewDatabaseConfig()
@@ -29,13 +30,16 @@ func NewMigrateCommand() *cobra.Command {
2930
}
3031

3132
func runMigrate(_ *cobra.Command, _ []string) {
33+
ctx := context.Background()
3234
err := dbConfig.ReadFiles()
3335
if err != nil {
34-
glog.Fatal(err)
36+
logger.Error(ctx, "Fatal error", "error", err)
37+
os.Exit(1)
3538
}
3639

3740
connection := db_session.NewProdFactory(dbConfig)
38-
if err := db.Migrate(connection.New(context.Background())); err != nil {
39-
glog.Fatal(err)
41+
if err := db.Migrate(connection.New(ctx)); err != nil {
42+
logger.Error(ctx, "Fatal error", "error", err)
43+
os.Exit(1)
4044
}
4145
}

cmd/hyperfleet-api/servecmd/cmd.go

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import (
55
"log/slog"
66
"os"
77

8-
"github.com/golang/glog"
98
"github.com/spf13/cobra"
109

1110
"github.com/openshift-hyperfleet/hyperfleet-api/cmd/hyperfleet-api/environments"
1211
"github.com/openshift-hyperfleet/hyperfleet-api/cmd/hyperfleet-api/server"
12+
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/api"
1313
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/logger"
14+
"github.com/openshift-hyperfleet/hyperfleet-api/pkg/telemetry"
1415
)
1516

1617
func NewServeCommand() *cobra.Command {
18+
ctx := context.Background()
1719
cmd := &cobra.Command{
1820
Use: "serve",
1921
Short: "Serve the hyperfleet",
@@ -22,31 +24,54 @@ func NewServeCommand() *cobra.Command {
2224
}
2325
err := environments.Environment().AddFlags(cmd.PersistentFlags())
2426
if err != nil {
25-
glog.Fatalf("Unable to add environment flags to serve command: %s", err.Error())
27+
logger.Error(ctx, "Unable to add environment flags to serve command", "error", err)
28+
os.Exit(1)
2629
}
2730

2831
return cmd
2932
}
3033

3134
func runServe(cmd *cobra.Command, args []string) {
35+
ctx := context.Background()
36+
37+
// Initialize environment (loads configuration)
3238
err := environments.Environment().Initialize()
3339
if err != nil {
34-
glog.Fatalf("Unable to initialize environment: %s", err.Error())
40+
logger.Error(ctx, "Unable to initialize environment", "error", err)
41+
os.Exit(1)
3542
}
3643

37-
// Initialize slog logger (demonstration only, full migration in PR 3)
38-
ctx := context.Background()
39-
hostname, _ := os.Hostname()
40-
logConfig := &logger.LogConfig{
41-
Level: slog.LevelInfo,
42-
Format: logger.FormatJSON,
43-
Output: os.Stdout,
44-
Component: "api",
45-
Version: "dev",
46-
Hostname: hostname,
44+
// Bind environment variables for advanced configuration (OTel, Masking)
45+
environments.Environment().Config.Logging.BindEnv()
46+
47+
// Initialize slog logger
48+
initLogger()
49+
50+
// Initialize OpenTelemetry (if enabled)
51+
if environments.Environment().Config.Logging.OTel.Enabled {
52+
samplingRate := environments.Environment().Config.Logging.OTel.SamplingRate
53+
tp, err := telemetry.InitTraceProvider("hyperfleet-api", api.Version, samplingRate)
54+
if err != nil {
55+
logger.Warn(ctx, "Failed to initialize OpenTelemetry", "error", err)
56+
} else {
57+
defer func() {
58+
if err := telemetry.Shutdown(context.Background(), tp); err != nil {
59+
logger.Error(ctx, "Failed to shutdown OpenTelemetry", "error", err)
60+
}
61+
}()
62+
logger.Info(ctx, "OpenTelemetry initialized", "sampling_rate", samplingRate)
63+
}
64+
} else {
65+
logger.Info(ctx, "OpenTelemetry disabled", "otel_enabled", false)
4766
}
48-
logger.InitGlobalLogger(logConfig)
49-
logger.Info(ctx, "New slog logger initialized (example)", "log_level", "info", "log_format", "json")
67+
68+
// Log configuration
69+
logger.Info(ctx, "Logger initialized",
70+
"log_level", environments.Environment().Config.Logging.Level,
71+
"log_format", environments.Environment().Config.Logging.Format,
72+
"log_output", environments.Environment().Config.Logging.Output,
73+
"masking_enabled", environments.Environment().Config.Logging.Masking.Enabled,
74+
)
5075

5176
// Run the servers
5277
go func() {
@@ -66,3 +91,46 @@ func runServe(cmd *cobra.Command, args []string) {
6691

6792
select {}
6893
}
94+
95+
// initLogger initializes the global slog logger from configuration
96+
func initLogger() {
97+
ctx := context.Background()
98+
cfg := environments.Environment().Config.Logging
99+
100+
// Parse log level
101+
level, err := logger.ParseLogLevel(cfg.Level)
102+
if err != nil {
103+
logger.Warn(ctx, "Invalid log level, using default", "level", cfg.Level, "error", err)
104+
level = slog.LevelInfo // Default to info
105+
}
106+
107+
// Parse log format
108+
format, err := logger.ParseLogFormat(cfg.Format)
109+
if err != nil {
110+
logger.Warn(ctx, "Invalid log format, using default", "format", cfg.Format, "error", err)
111+
format = logger.FormatJSON // Default to JSON
112+
}
113+
114+
// Parse log output
115+
output, err := logger.ParseLogOutput(cfg.Output)
116+
if err != nil {
117+
logger.Warn(ctx, "Invalid log output, using default", "output", cfg.Output, "error", err)
118+
output = os.Stdout // Default to stdout
119+
}
120+
121+
// Get hostname
122+
hostname, _ := os.Hostname()
123+
124+
// Create logger config
125+
logConfig := &logger.LogConfig{
126+
Level: level,
127+
Format: format,
128+
Output: output,
129+
Component: "api",
130+
Version: api.Version,
131+
Hostname: hostname,
132+
}
133+
134+
// Initialize global logger
135+
logger.InitGlobalLogger(logConfig)
136+
}

0 commit comments

Comments
 (0)