1
0
mirror of https://github.com/pocket-id/pocket-id.git synced 2026-02-14 17:37:27 +00:00

feat: Support OTel and JSON for logs (via log/slog) (#760)

Co-authored-by: Kyle Mendell <kmendell@ofkm.us>
This commit is contained in:
Alessandro (Ale) Segala
2025-07-27 03:03:52 +02:00
committed by GitHub
parent c8478d75be
commit 78266e3e4c
20 changed files with 432 additions and 204 deletions

View File

@@ -14,7 +14,6 @@ import (
"time"
"github.com/hashicorp/go-uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"

View File

@@ -3,7 +3,6 @@ package service
import (
"context"
"fmt"
"log"
"log/slog"
userAgentParser "github.com/mileusna/useragent"
@@ -11,6 +10,7 @@ import (
"github.com/pocket-id/pocket-id/backend/internal/model"
"github.com/pocket-id/pocket-id/backend/internal/utils"
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
"go.opentelemetry.io/otel/trace"
"gorm.io/gorm"
)
@@ -82,7 +82,7 @@ func (s *AuditLogService) CreateNewSignInWithEmail(ctx context.Context, ipAddres
}
err := stmt.Count(&count).Error
if err != nil {
log.Printf("Failed to count audit logs: %v", err)
slog.ErrorContext(ctx, "Failed to count audit logs", slog.Any("error", err))
return createdAuditLog
}
@@ -91,7 +91,8 @@ func (s *AuditLogService) CreateNewSignInWithEmail(ctx context.Context, ipAddres
// We use a background context here as this is running in a goroutine
//nolint:contextcheck
go func() {
innerCtx := context.Background()
span := trace.SpanFromContext(ctx)
innerCtx := trace.ContextWithSpan(context.Background(), span)
// Note we don't use the transaction here because this is running in background
var user model.User
@@ -101,7 +102,8 @@ func (s *AuditLogService) CreateNewSignInWithEmail(ctx context.Context, ipAddres
First(&user).
Error
if innerErr != nil {
log.Printf("Failed to load user: %v", innerErr)
slog.ErrorContext(innerCtx, "Failed to load user from database to send notification email", slog.Any("error", innerErr))
return
}
innerErr = SendEmail(innerCtx, s.emailService, email.Address{
@@ -115,7 +117,8 @@ func (s *AuditLogService) CreateNewSignInWithEmail(ctx context.Context, ipAddres
DateTime: createdAuditLog.CreatedAt.UTC(),
})
if innerErr != nil {
log.Printf("Failed to send email to '%s': %v", user.Email, innerErr)
slog.ErrorContext(innerCtx, "Failed to send notification email", slog.Any("error", innerErr), slog.String("address", user.Email))
return
}
}()
}

View File

@@ -7,7 +7,7 @@ import (
"errors"
"fmt"
"io"
"log"
"log/slog"
"net"
"net/http"
"net/netip"
@@ -52,13 +52,13 @@ func NewGeoLiteService(httpClient *http.Client) *GeoLiteService {
if common.EnvConfig.MaxMindLicenseKey == "" && common.EnvConfig.GeoLiteDBUrl == common.MaxMindGeoLiteCityUrl {
// Warn the user, and disable the periodic updater
log.Println("MAXMIND_LICENSE_KEY environment variable is empty. The GeoLite2 City database won't be updated.")
slog.Warn("MAXMIND_LICENSE_KEY environment variable is empty: the GeoLite2 City database won't be updated")
service.disableUpdater = true
}
// Initialize IPv6 local ranges
if err := service.initializeIPv6LocalRanges(); err != nil {
log.Printf("Warning: Failed to initialize IPv6 local ranges: %v", err)
slog.Warn("Failed to initialize IPv6 local ranges", slog.Any("error", err))
}
return service
@@ -96,7 +96,7 @@ func (s *GeoLiteService) initializeIPv6LocalRanges() error {
s.localIPv6Ranges = localRanges
if len(localRanges) > 0 {
log.Printf("Initialized %d IPv6 local ranges", len(localRanges))
slog.Info("Initialized IPv6 local ranges", slog.Int("count", len(localRanges)))
}
return nil
}
@@ -186,11 +186,11 @@ func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string
// UpdateDatabase checks the age of the database and updates it if it's older than 14 days.
func (s *GeoLiteService) UpdateDatabase(parentCtx context.Context) error {
if s.isDatabaseUpToDate() {
log.Println("GeoLite2 City database is up-to-date")
slog.Info("GeoLite2 City database is up-to-date")
return nil
}
log.Println("Updating GeoLite2 City database")
slog.Info("Updating GeoLite2 City database")
downloadUrl := fmt.Sprintf(common.EnvConfig.GeoLiteDBUrl, common.EnvConfig.MaxMindLicenseKey)
ctx, cancel := context.WithTimeout(parentCtx, 10*time.Minute)
@@ -217,7 +217,7 @@ func (s *GeoLiteService) UpdateDatabase(parentCtx context.Context) error {
return fmt.Errorf("failed to extract database: %w", err)
}
log.Println("GeoLite2 City database successfully updated.")
slog.Info("GeoLite2 City database successfully updated.")
return nil
}

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"time"
"github.com/lestrrat-go/jwx/v3/jwa"
@@ -64,16 +63,16 @@ type JwtService struct {
jwksEncoded []byte
}
func NewJwtService(db *gorm.DB, appConfigService *AppConfigService) *JwtService {
func NewJwtService(db *gorm.DB, appConfigService *AppConfigService) (*JwtService, error) {
service := &JwtService{}
// Ensure keys are generated or loaded
err := service.init(db, appConfigService, &common.EnvConfig)
if err != nil {
log.Fatalf("Failed to initialize jwt service: %v", err)
return nil, err
}
return service
return service, nil
}
func (s *JwtService) init(db *gorm.DB, appConfigService *AppConfigService, envConfig *common.EnvConfigSchema) (err error) {

View File

@@ -9,6 +9,7 @@ import (
"fmt"
"io"
"log"
"log/slog"
"net/http"
"net/url"
"strings"
@@ -130,7 +131,7 @@ func (s *LdapService) SyncGroups(ctx context.Context, tx *gorm.DB, client *ldap.
// Skip groups without a valid LDAP ID
if ldapId == "" {
log.Printf("Skipping LDAP group without a valid unique identifier (attribute: %s)", dbConfig.LdapAttributeGroupUniqueIdentifier.Value)
slog.Warn("Skipping LDAP group without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeGroupUniqueIdentifier.Value))
continue
}
@@ -250,7 +251,7 @@ func (s *LdapService) SyncGroups(ctx context.Context, tx *gorm.DB, client *ldap.
return fmt.Errorf("failed to delete group '%s': %w", group.Name, err)
}
log.Printf("Deleted group '%s'", group.Name)
slog.Info("Deleted group", slog.String("group", group.Name))
}
return nil
@@ -295,7 +296,7 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
// Skip users without a valid LDAP ID
if ldapId == "" {
log.Printf("Skipping LDAP user without a valid unique identifier (attribute: %s)", dbConfig.LdapAttributeUserUniqueIdentifier.Value)
slog.Warn("Skipping LDAP user without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeUserUniqueIdentifier.Value))
continue
}
@@ -350,7 +351,7 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
if databaseUser.ID == "" {
_, err = s.userService.createUserInternal(ctx, newUser, true, tx)
if errors.Is(err, &common.AlreadyInUseError{}) {
log.Printf("Skipping creating LDAP user '%s': %v", newUser.Username, err)
slog.Warn("Skipping creating LDAP user", slog.String("username", newUser.Username), slog.Any("error", err))
continue
} else if err != nil {
return fmt.Errorf("error creating user '%s': %w", newUser.Username, err)
@@ -358,7 +359,7 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
} else {
_, err = s.userService.updateUserInternal(ctx, databaseUser.ID, newUser, false, true, tx)
if errors.Is(err, &common.AlreadyInUseError{}) {
log.Printf("Skipping updating LDAP user '%s': %v", newUser.Username, err)
slog.Warn("Skipping updating LDAP user", slog.String("username", newUser.Username), slog.Any("error", err))
continue
} else if err != nil {
return fmt.Errorf("error updating user '%s': %w", newUser.Username, err)
@@ -371,7 +372,7 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
err = s.saveProfilePicture(ctx, databaseUser.ID, pictureString)
if err != nil {
// This is not a fatal error
log.Printf("Error saving profile picture for user %s: %v", newUser.Username, err)
slog.Warn("Error saving profile picture for user", slog.String("username", newUser.Username), slog.Any("error", err))
}
}
}
@@ -400,7 +401,7 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
return fmt.Errorf("failed to disable user %s: %w", user.Username, err)
}
log.Printf("Disabled user '%s'", user.Username)
slog.Info("Disabled user", slog.String("username", user.Username))
} else {
err = s.userService.deleteUserInternal(ctx, user.ID, true, tx)
target := &common.LdapUserUpdateError{}
@@ -410,7 +411,7 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
return fmt.Errorf("failed to delete user %s: %w", user.Username, err)
}
log.Printf("Deleted user '%s'", user.Username)
slog.Info("Deleted user", slog.String("username", user.Username))
}
}

View File

@@ -7,12 +7,14 @@ import (
"fmt"
"io"
"log"
"log/slog"
"net/url"
"os"
"strings"
"time"
"github.com/google/uuid"
"go.opentelemetry.io/otel/trace"
"gorm.io/gorm"
"github.com/pocket-id/pocket-id/backend/internal/common"
@@ -393,7 +395,8 @@ func (s *UserService) requestOneTimeAccessEmailInternal(ctx context.Context, use
// We use a background context here as this is running in a goroutine
//nolint:contextcheck
go func() {
innerCtx := context.Background()
span := trace.SpanFromContext(ctx)
innerCtx := trace.ContextWithSpan(context.Background(), span)
link := common.EnvConfig.AppURL + "/lc"
linkWithCode := link + "/" + oneTimeAccessToken
@@ -414,7 +417,8 @@ func (s *UserService) requestOneTimeAccessEmailInternal(ctx context.Context, use
ExpirationString: utils.DurationToString(time.Until(expiration).Round(time.Second)),
})
if errInternal != nil {
log.Printf("Failed to send email to '%s': %v\n", user.Email, errInternal)
slog.ErrorContext(innerCtx, "Failed to send one-time access token email", slog.Any("error", errInternal), slog.String("address", user.Email))
return
}
}()