fix: defer component logger creation past package init

Package-level slog.With() vars are evaluated at init time, before
observability.InitLogger() runs in main. They captured the original default
slog handler and bypassed the countingHandler that increments
log_messages_total — so warn/error calls from these packages weren't being
counted.

Convert to functions so each call resolves slog.Default() at use time and
inherits the active counting handler.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 22:26:03 +02:00
parent 1c10cf4390
commit 9a5fff81bf
4 changed files with 34 additions and 34 deletions

View File

@@ -16,7 +16,7 @@ import (
"terminalShop/pkg/utils"
)
var addressLog = slog.With("component", "address")
func addressLog() *slog.Logger { return slog.With("component", "address") }
type AddressHandler struct {
shippoKey string
@@ -93,7 +93,7 @@ func (h *AddressHandler) CreateAddress(w http.ResponseWriter, r *http.Request) {
Phone: req.Phone,
})
if err != nil {
addressLog.Warn("shippo address validation failed", "error", err, "country", "US")
addressLog().Warn("shippo address validation failed", "error", err, "country", "US")
utils.RespondError(w, http.StatusBadRequest, "INVALID_ADDRESS", "address is invalid", nil)
return
}
@@ -124,7 +124,7 @@ func (h *AddressHandler) CreateAddress(w http.ResponseWriter, r *http.Request) {
Phone: req.Phone,
})
if err != nil {
addressLog.Warn("bring address validation failed", "error", err, "country", "NO")
addressLog().Warn("bring address validation failed", "error", err, "country", "NO")
utils.RespondError(w, http.StatusBadRequest, "INVALID_ADDRESS", "address is invalid", nil)
return
}

View File

@@ -14,7 +14,7 @@ import (
"gorm.io/gorm"
)
var authLog = slog.With("component", "auth")
func authLog() *slog.Logger { return slog.With("component", "auth") }
// AuthHandler handles authentication-related requests
type AuthHandler struct {
@@ -58,7 +58,7 @@ func (h *AuthHandler) GetToken(w http.ResponseWriter, r *http.Request) {
// validate shared secret
if req.ClientSecret != h.authFingerprintKey {
middleware.RecordAuthAttempt("invalid_secret")
authLog.Warn("invalid client secret", "remote", r.RemoteAddr)
authLog().Warn("invalid client secret", "remote", r.RemoteAddr)
utils.RespondError(w, http.StatusUnauthorized, "INVALID_SECRET", "invalid client secret", nil)
return
}
@@ -76,7 +76,7 @@ func (h *AuthHandler) GetToken(w http.ResponseWriter, r *http.Request) {
if err == gorm.ErrRecordNotFound {
if req.SSHPublicKey == "" {
middleware.RecordAuthAttempt("user_not_found")
authLog.Warn("user not found and no public key for registration", "fingerprint", req.Fingerprint)
authLog().Warn("user not found and no public key for registration", "fingerprint", req.Fingerprint)
utils.RespondError(w, http.StatusUnauthorized, "USER_NOT_FOUND", "no user found for this fingerprint", nil)
return
}
@@ -86,15 +86,15 @@ func (h *AuthHandler) GetToken(w http.ResponseWriter, r *http.Request) {
}
if err := db.Create(&user).Error; err != nil {
middleware.RecordAuthAttempt("error")
authLog.Error("failed to create user", "error", err)
authLog().Error("failed to create user", "error", err)
utils.RespondError(w, http.StatusInternalServerError, "DATABASE_ERROR", "failed to create user", nil)
return
}
middleware.RecordAuthAttempt("registered")
authLog.Info("user registered", "user_id", user.ID)
authLog().Info("user registered", "user_id", user.ID)
} else if err != nil {
middleware.RecordAuthAttempt("error")
authLog.Error("failed to look up user", "error", err)
authLog().Error("failed to look up user", "error", err)
utils.RespondError(w, http.StatusInternalServerError, "DATABASE_ERROR", "failed to look up user", nil)
return
}
@@ -102,7 +102,7 @@ func (h *AuthHandler) GetToken(w http.ResponseWriter, r *http.Request) {
token, err := h.jwtManager.GenerateToken(user.ID, user.Email, user.Name)
if err != nil {
middleware.RecordAuthAttempt("error")
authLog.Error("failed to generate token", "user_id", user.ID, "error", err)
authLog().Error("failed to generate token", "user_id", user.ID, "error", err)
utils.RespondError(w, http.StatusInternalServerError, "TOKEN_ERROR", "failed to generate token", nil)
return
}

View File

@@ -13,7 +13,7 @@ import (
"gorm.io/gorm"
)
var reconcileLog = slog.With("component", "reconcile")
func reconcileLog() *slog.Logger { return slog.With("component", "reconcile") }
// ReconcileOrders finds pending orders with no Stripe ID older than 10 minutes
// and checks whether Stripe has a succeeded PaymentIntent for them. This fixes
@@ -32,7 +32,7 @@ func ReconcileOrders(stripeKey string) {
return
}
reconcileLog.Info("checking pending orders", "count", len(orders))
reconcileLog().Info("checking pending orders", "count", len(orders))
for _, order := range orders {
params := &stripe.PaymentIntentSearchParams{
@@ -44,7 +44,7 @@ func ReconcileOrders(stripeKey string) {
iter := paymentintent.Search(params)
for iter.Next() {
pi := iter.PaymentIntent()
reconcileLog.Warn("found succeeded payment intent for unrecorded order — patching",
reconcileLog().Warn("found succeeded payment intent for unrecorded order — patching",
"order_id", order.ID, "pi", pi.ID)
txErr := db.Transaction(func(tx *gorm.DB) error {
@@ -67,11 +67,11 @@ func ReconcileOrders(stripeKey string) {
}).Error
})
if txErr != nil {
reconcileLog.Error("failed to patch order", "order_id", order.ID, "error", txErr)
reconcileLog().Error("failed to patch order", "order_id", order.ID, "error", txErr)
}
}
if err := iter.Err(); err != nil {
reconcileLog.Error("stripe search error", "order_id", order.ID, "error", err)
reconcileLog().Error("stripe search error", "order_id", order.ID, "error", err)
}
}
}

View File

@@ -19,7 +19,7 @@ import (
"terminalShop/pkg/utils"
)
var webhookLog = slog.With("component", "webhook")
func webhookLog() *slog.Logger { return slog.With("component", "webhook") }
// WebhookHandler handles inbound Stripe webhook events.
type WebhookHandler struct {
@@ -48,13 +48,13 @@ func (h *WebhookHandler) HandleStripe(w http.ResponseWriter, r *http.Request) {
// Verify the Stripe signature. Reject anything that doesn't match.
if h.webhookSecret == "" {
// No secret configured — log a loud warning but still process in dev.
webhookLog.Warn("stripe webhook secret not set; skipping signature verification")
webhookLog().Warn("stripe webhook secret not set; skipping signature verification")
} else {
sig := r.Header.Get("Stripe-Signature")
if _, err := webhook.ConstructEventWithOptions(body, sig, h.webhookSecret, webhook.ConstructEventOptions{
IgnoreAPIVersionMismatch: true,
}); err != nil {
webhookLog.Warn("signature verification failed", "error", err)
webhookLog().Warn("signature verification failed", "error", err)
utils.RespondError(w, http.StatusUnauthorized, "INVALID_SIGNATURE", "webhook signature verification failed", nil)
return
}
@@ -87,7 +87,7 @@ func (h *WebhookHandler) HandleStripe(w http.ResponseWriter, r *http.Request) {
"payment_method.detached",
"payment_method.updated":
// Handled by the reconcile job and direct API calls; log only.
webhookLog.Info("payment method event received", "event_type", string(event.Type))
webhookLog().Info("payment method event received", "event_type", string(event.Type))
}
// Always return 200 so Stripe doesn't retry.
@@ -97,7 +97,7 @@ func (h *WebhookHandler) HandleStripe(w http.ResponseWriter, r *http.Request) {
func (h *WebhookHandler) handlePaymentIntentSucceeded(event stripe.Event) {
var pi stripe.PaymentIntent
if err := json.Unmarshal(event.Data.Raw, &pi); err != nil {
webhookLog.Error("failed to unmarshal payment_intent", "error", err, "event", "payment_intent.succeeded")
webhookLog().Error("failed to unmarshal payment_intent", "error", err, "event", "payment_intent.succeeded")
return
}
@@ -110,7 +110,7 @@ func (h *WebhookHandler) handlePaymentIntentSucceeded(event stripe.Event) {
db := database.GetDB()
var order models.Order
if err := db.Where("id = ?", orderIDStr).First(&order).Error; err != nil {
webhookLog.Warn("order not found for payment intent", "order_id", orderIDStr, "pi", pi.ID)
webhookLog().Warn("order not found for payment intent", "order_id", orderIDStr, "pi", pi.ID)
return
}
@@ -140,19 +140,19 @@ func (h *WebhookHandler) handlePaymentIntentSucceeded(event stripe.Event) {
}).Error
})
if txErr != nil {
webhookLog.Error("failed to record paid status",
webhookLog().Error("failed to record paid status",
"order_id", orderIDStr, "pi", pi.ID, "error", txErr)
return
}
audit.OrderPaid(order.UserID, order.ID, int(pi.Amount), pi.ID)
webhookLog.Info("order marked paid", "order_id", orderIDStr, "pi", pi.ID)
webhookLog().Info("order marked paid", "order_id", orderIDStr, "pi", pi.ID)
}
func (h *WebhookHandler) handlePaymentIntentFailed(event stripe.Event) {
var pi stripe.PaymentIntent
if err := json.Unmarshal(event.Data.Raw, &pi); err != nil {
webhookLog.Error("failed to unmarshal payment_intent", "error", err, "event", "payment_intent.payment_failed")
webhookLog().Error("failed to unmarshal payment_intent", "error", err, "event", "payment_intent.payment_failed")
return
}
@@ -164,20 +164,20 @@ func (h *WebhookHandler) handlePaymentIntentFailed(event stripe.Event) {
db := database.GetDB()
if err := db.Model(&models.Order{}).Where("id = ? AND status = ?", orderIDStr, models.OrderStatusPending).
Update("status", models.OrderStatusFailed).Error; err != nil {
webhookLog.Error("failed to mark order failed", "order_id", orderIDStr, "error", err)
webhookLog().Error("failed to mark order failed", "order_id", orderIDStr, "error", err)
}
errMsg := "unknown"
if pi.LastPaymentError != nil {
errMsg = pi.LastPaymentError.Msg
}
webhookLog.Warn("payment failed", "order_id", orderIDStr, "pi", pi.ID, "reason", errMsg)
webhookLog().Warn("payment failed", "order_id", orderIDStr, "pi", pi.ID, "reason", errMsg)
}
func (h *WebhookHandler) handleCheckoutSessionCompleted(event stripe.Event) {
var sess stripe.CheckoutSession
if err := json.Unmarshal(event.Data.Raw, &sess); err != nil {
webhookLog.Error("failed to unmarshal checkout session", "error", err)
webhookLog().Error("failed to unmarshal checkout session", "error", err)
return
}
@@ -186,7 +186,7 @@ func (h *WebhookHandler) handleCheckoutSessionCompleted(event stripe.Event) {
}
if sess.SetupIntent == nil || sess.Customer == nil {
webhookLog.Warn("checkout session missing setup_intent or customer")
webhookLog().Warn("checkout session missing setup_intent or customer")
return
}
@@ -194,25 +194,25 @@ func (h *WebhookHandler) handleCheckoutSessionCompleted(event stripe.Event) {
si, err := setupintent.Get(sess.SetupIntent.ID, nil)
if err != nil {
webhookLog.Error("failed to get setup intent", "setup_intent", sess.SetupIntent.ID, "error", err)
webhookLog().Error("failed to get setup intent", "setup_intent", sess.SetupIntent.ID, "error", err)
return
}
if si.PaymentMethod == nil {
webhookLog.Warn("setup intent has no payment method", "setup_intent", si.ID)
webhookLog().Warn("setup intent has no payment method", "setup_intent", si.ID)
return
}
pm, err := paymentmethod.Get(si.PaymentMethod.ID, nil)
if err != nil {
webhookLog.Error("failed to get payment method", "payment_method", si.PaymentMethod.ID, "error", err)
webhookLog().Error("failed to get payment method", "payment_method", si.PaymentMethod.ID, "error", err)
return
}
db := database.GetDB()
var user models.User
if err := db.Where("stripe_customer_id = ?", sess.Customer.ID).First(&user).Error; err != nil {
webhookLog.Warn("user not found for customer", "customer", sess.Customer.ID, "error", err)
webhookLog().Warn("user not found for customer", "customer", sess.Customer.ID, "error", err)
return
}
@@ -232,10 +232,10 @@ func (h *WebhookHandler) handleCheckoutSessionCompleted(event stripe.Event) {
}
if err := db.Create(&card).Error; err != nil {
webhookLog.Error("failed to save card", "user_id", user.ID, "error", err)
webhookLog().Error("failed to save card", "user_id", user.ID, "error", err)
return
}
audit.CardAdded(user.ID, card.ID, card.Brand, card.Last4)
webhookLog.Info("card saved via checkout.session.completed", "user_id", user.ID)
webhookLog().Info("card saved via checkout.session.completed", "user_id", user.ID)
}