Skip to content

Commit 5a8ff7d

Browse files
authored
chore(api): improve API logging (#3124)
* chore(api): improve API logging * chore: fix rebase issue * chore: cursor feedback
1 parent 565cd22 commit 5a8ff7d

File tree

8 files changed

+95
-31
lines changed

8 files changed

+95
-31
lines changed

api/internal/managers/airgap/util.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"strings"
77
)
88

9-
func (m *airgapManager) addLogs(format string, v ...interface{}) {
9+
func (m *airgapManager) addLogs(format string, v ...any) {
1010
msg := fmt.Sprintf(format, v...)
1111
if err := m.airgapStore.AddLogs(msg); err != nil {
1212
m.logger.WithError(err).Error("add log")
@@ -28,6 +28,7 @@ func (lw *logWriter) Write(p []byte) (n int, err error) {
2828
output := strings.TrimSpace(string(p))
2929
if output != "" {
3030
lw.manager.addLogs("%s", output)
31+
lw.manager.logger.WithField("component", "kots").Debug(output)
3132
}
3233
return len(p), nil
3334
}

api/internal/managers/app/install/status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func (m *appInstallManager) setStatus(state types.State, description string) err
1919
})
2020
}
2121

22-
func (m *appInstallManager) addLogs(format string, v ...interface{}) {
22+
func (m *appInstallManager) addLogs(format string, v ...any) {
2323
msg := fmt.Sprintf(format, v...)
2424
if err := m.appInstallStore.AddLogs(msg); err != nil {
2525
m.logger.WithError(err).Error("add log")

api/internal/managers/app/install/util.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func (lw *logWriter) Write(p []byte) (n int, err error) {
2222
output := strings.TrimSpace(string(p))
2323
if output != "" {
2424
lw.manager.addLogs("[kots] %s", output)
25+
lw.manager.logger.WithField("component", "kots").Debug(output)
2526
}
2627
return len(p), nil
2728
}

api/internal/managers/app/upgrade/util.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,17 @@ func (lw *logWriter) Write(p []byte) (n int, err error) {
2828
}
2929

3030
// log logs a message to the structured logger and adds it to the logs store
31-
func (m *appUpgradeManager) log(fields interface{}, format string, v ...interface{}) {
31+
func (m *appUpgradeManager) log(fields any, format string, v ...any) {
32+
logger := m.logger.WithField("component", "kots")
3233
if fields != nil {
3334
f, err := json.Marshal(fields)
3435
if err == nil {
35-
m.logger.WithField("fields", string(f)).Debugf(format, v...)
36+
logger.WithField("fields", string(f)).Debugf(format, v...)
3637
} else {
37-
m.logger.Debugf(format, v...)
38+
logger.Debugf(format, v...)
3839
}
3940
} else {
40-
m.logger.Debugf(format, v...)
41+
logger.Debugf(format, v...)
4142
}
4243
m.addLogs(format, v...)
4344
}

api/internal/managers/linux/infra/util.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package infra
33
import (
44
"context"
55
"fmt"
6-
"io"
7-
"strings"
86

97
"github.com/replicatedhq/embedded-cluster/api/internal/clients"
108
ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1"
@@ -15,27 +13,6 @@ import (
1513
"sigs.k8s.io/controller-runtime/pkg/client"
1614
)
1715

18-
// logWriter is an io.Writer that captures output and feeds it to the logs
19-
type logWriter struct {
20-
manager *infraManager
21-
component string
22-
}
23-
24-
func (m *infraManager) newLogWriter(component string) io.Writer {
25-
return &logWriter{
26-
manager: m,
27-
component: component,
28-
}
29-
}
30-
31-
func (lw *logWriter) Write(p []byte) (n int, err error) {
32-
output := strings.TrimSpace(string(p))
33-
if output != "" {
34-
lw.manager.addLogs(lw.component, "%s", output)
35-
}
36-
return len(p), nil
37-
}
38-
3916
func (m *infraManager) waitForNode(ctx context.Context, kcli client.Client) error {
4017
nodename, err := nodeutil.GetHostname("")
4118
if err != nil {

api/pkg/logger/logger.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ func NewLogger() (*logrus.Logger, error) {
2020
}
2121

2222
logger := logrus.New()
23+
// Set to debug by default to capture all the manager and execution logs
24+
logger.SetLevel(logrus.DebugLevel)
2325
logger.SetOutput(logfile)
2426

2527
logger.Infof("versions: embedded-cluster=%s, k0s=%s", versions.Version, versions.K0sVersion)

api/pkg/logger/middleware.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package logger
2+
3+
import (
4+
"net/http"
5+
"time"
6+
7+
"github.com/gorilla/mux"
8+
"github.com/sirupsen/logrus"
9+
)
10+
11+
// responseWriter wraps http.ResponseWriter to capture status code and response size
12+
type responseWriter struct {
13+
http.ResponseWriter
14+
statusCode int
15+
written int64
16+
}
17+
18+
// WriteHeader captures the status code before writing it
19+
func (rw *responseWriter) WriteHeader(code int) {
20+
rw.statusCode = code
21+
rw.ResponseWriter.WriteHeader(code)
22+
}
23+
24+
// Write captures the number of bytes written
25+
func (rw *responseWriter) Write(b []byte) (int, error) {
26+
n, err := rw.ResponseWriter.Write(b)
27+
rw.written += int64(n)
28+
return n, err
29+
}
30+
31+
// HTTPLoggingMiddleware creates a middleware that logs HTTP requests with structured fields.
32+
// It logs after the request is handled, capturing the response status code and duration.
33+
func HTTPLoggingMiddleware(logger logrus.FieldLogger) mux.MiddlewareFunc {
34+
return func(next http.Handler) http.Handler {
35+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36+
start := time.Now()
37+
38+
// Wrap the response writer to capture status code and bytes written
39+
wrapped := &responseWriter{
40+
ResponseWriter: w,
41+
statusCode: http.StatusOK, // default if WriteHeader is not called
42+
}
43+
44+
// Call the next handler
45+
next.ServeHTTP(wrapped, r)
46+
47+
// Log after the request is handled
48+
duration := time.Since(start)
49+
50+
// Build structured log fields
51+
fields := logrus.Fields{
52+
"component": "request-logger",
53+
"method": r.Method,
54+
"path": r.URL.Path,
55+
"status": wrapped.statusCode,
56+
"duration_ms": duration.Milliseconds(),
57+
"response_bytes": wrapped.written,
58+
}
59+
60+
// Add query parameters if present
61+
if r.URL.RawQuery != "" {
62+
fields["query"] = r.URL.RawQuery
63+
}
64+
65+
// Determine log level based on status code
66+
entry := logger.WithFields(fields)
67+
switch {
68+
case wrapped.statusCode >= 500:
69+
entry.Error("HTTP request")
70+
case wrapped.statusCode >= 400:
71+
entry.Warn("HTTP request")
72+
default:
73+
entry.Info("HTTP request")
74+
}
75+
})
76+
}
77+
}

api/routes.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/gorilla/mux"
77
"github.com/replicatedhq/embedded-cluster/api/docs"
8+
"github.com/replicatedhq/embedded-cluster/api/pkg/logger"
89
"github.com/replicatedhq/embedded-cluster/api/types"
910
httpSwagger "github.com/swaggo/http-swagger/v2"
1011
)
@@ -23,9 +24,13 @@ func (a *API) RegisterRoutes(router *mux.Router) {
2324
}).Methods("GET")
2425
router.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler)
2526

26-
router.HandleFunc("/auth/login", a.handlers.auth.PostLogin).Methods("POST")
27+
// Routes with logging middleware
28+
routerWithLogging := router.PathPrefix("/").Subrouter()
29+
routerWithLogging.Use(logger.HTTPLoggingMiddleware(a.logger))
2730

28-
authenticatedRouter := router.PathPrefix("/").Subrouter()
31+
routerWithLogging.HandleFunc("/auth/login", a.handlers.auth.PostLogin).Methods("POST")
32+
33+
authenticatedRouter := routerWithLogging.PathPrefix("/").Subrouter()
2934
authenticatedRouter.Use(a.handlers.auth.Middleware)
3035

3136
if a.cfg.InstallTarget == types.InstallTargetLinux {

0 commit comments

Comments
 (0)