refactor: framework modules log through diary components
- Each module logs through diary.For(<package>): cabana, lagoon, attach, cooler, postcard, beachcomber, lighthouse, centrifugo, flare, phrasebook, wristband, bouncer - App-published *slog.Logger overrides stay first; messages and attributes unchanged - sunscreen: InstallDefault documented as the compatibility entry point; diary wraps every channel - READMEs list diary as a dependency; logging docs: plugins use diary.For
This commit is contained in:
@@ -37,7 +37,7 @@ Use cases:
|
||||
- Column types: `lagoon.Encrypted` stores AES-256-GCM ciphertext under a key derived from `app.key`, decrypts with previous keys during rotation, and always redacts itself in JSON and string output; `lagoon.Jsonable` stores JSON as TEXT and keeps SQL NULL distinct from an empty value.
|
||||
- Lifecycle and relations: hook interfaces matching GORM's native method names (`lagoon.HasBeforeCreate`, `lagoon.HasBeforeSave`, `lagoon.HasBeforeDelete`, `lagoon.HasAfterDelete`) plus `lagoon.HasBeforeValidate`; `lagoon.WithSoftDeleteCascade` runs a cascade inside the parent delete; `lagoon.RegisterJoinTable` wires pivot models with business columns.
|
||||
- Deferred binding: WinterCMS's `deferred_bindings` table holds the uploads and related-record changes of a form whose record is not saved yet. Every operation takes a `lagoon.DeferredKey` (the form's session key, the owning backend admin's id and the master record's morph type from `lagoon.MorphType`) and never reads or changes another admin's rows, since each row stores `backend_user_id`. `lagoon.DeferredBind` and `lagoon.DeferredUnbind` port WinterCMS's duplicate and cancel rules: a repeated bind writes nothing, and an unbind of a slave with a pending bind deletes that bind and returns it so the caller can remove what it created. `lagoon.DeferredBindings` reads and locks a session's bindings for the save that commits them, `lagoon.DeferredForget` deletes them once applied, and `lagoon.DeferredSlaves` is the subquery a list uses to include pending rows. A child created under deferral carries the `lagoon.DeferredEnvelope` (`{"created":true,"pivot":{...},"upload_id":"..."}`) in `pivot_data`. `lagoon.PurgeDeferred` removes expired bindings: it deletes an unattached `system_files` row a bind points at, and its blobs only after the commit, deletes a child only when its binding carries the created envelope, keeps records that were only linked, and locks each batch with `FOR UPDATE SKIP LOCKED`.
|
||||
- Logging: GORM's lines no longer go to stdout through GORM's own logger. They arrive through [diary](../diary/README.md) as component `db`: slow queries (over 200 ms) and errors at warn, while a missing record (`gorm.ErrRecordNotFound`) is no longer logged. At verbosity 3 (`-vvv`) every query is traced at info, with placeholders instead of the bound values.
|
||||
- Logging: GORM's lines no longer go to stdout through GORM's own logger. They arrive through [diary](../diary/README.md) as component `db`: slow queries (over 200 ms) and errors at warn, while a missing record (`gorm.ErrRecordNotFound`) is no longer logged. At verbosity 3 (`-vvv`) every query is traced at info, with placeholders instead of the bound values. lagoon's own warnings log as component `lagoon`, and `attach` as component `attach`.
|
||||
- Imports from Laravel: `lagoon.DecryptLaravelPayload` decrypts Laravel `encrypted` payloads with the old application key, for one-off data imports.
|
||||
- Attachments (`attach`): the `attach.File` model for `system_files` rows, WinterCMS-compatible partitioned storage keys (`attach.BlobKey`, `attach.PartitionDirectory`), an S3 bucket option behind the `attach.S3BuildTag` build tag (an untagged binary refuses an `s3://` bucket URL at boot with an error naming the tag), public URLs (`attach.PublicURL` for any key, under `storage.uploads.public_url` when a public bucket or CDN serves the files (`attach.PublicURLBase`), `attach.File.URL` for an original, matching WinterCMS's `File::getPath()` under the WinterCMS layout), on-demand thumbnails through `attach.File.Thumb` for JPEG, PNG, GIF and WebP originals (a WebP original's thumbnail is JPEG bytes under its `.webp` name, since WebP cannot be encoded; a missing, undecodable or oversized original gets WinterCMS's broken-image picture, `attach.BrokenImagePNG`, as its thumbnail, as `File::makeThumb` does), storing uploads through `attach.Store` (a server-generated disk name whose extension follows the sniffed content through `attach.DiskExtension`, so a PNG named `x.html` is stored as `.png`, an extension allow-list with `attach.DefaultImageExtensions` and `attach.DefaultFileExtensions` as defaults, a MIME filter, a size limit enforced while streaming, in image mode the `attach.IsAllowedImage` content guard, and the refusal of protected uploads while `storage.uploads.public_url` serves the bucket publicly, `attach.ErrProtectedNeedsPrivateBucket`), attachment relation declarations (`attach.Relation`, `attach.HasRelations`), static serving with an optional `is_public` gate (`attach.StaticHandlerPublic`), and a two-phase delete that removes blobs only after the database transaction commits (`attach.DeleteForOwner`, `attach.DeleteKeys`).
|
||||
|
||||
|
||||
@@ -10,11 +10,16 @@ import (
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"git.golem15.com/golem15/summercms/modules/diary"
|
||||
"gocloud.dev/blob"
|
||||
_ "gocloud.dev/blob/fileblob"
|
||||
_ "gocloud.dev/blob/memblob"
|
||||
)
|
||||
|
||||
// moduleLog is attach's logger: records carry component=attach and follow
|
||||
// log.components.attach (diary).
|
||||
var moduleLog = diary.For("attach")
|
||||
|
||||
const defaultPublicPathPrefix = "/storage/uploads"
|
||||
|
||||
// S3BuildTag is the Go build tag that links the gocloud S3 driver
|
||||
@@ -150,7 +155,7 @@ func OpenBucket(ctx context.Context, cfg *compass.Config) (*blob.Bucket, error)
|
||||
return bucket, nil
|
||||
}
|
||||
setPublicURLBase(publicURL.String())
|
||||
slog.Default().WarnContext(ctx, "attach: storage.uploads.public_url is set; every file and thumbnail URL points at this host, "+
|
||||
moduleLog.WarnContext(ctx, "attach: storage.uploads.public_url is set; every file and thumbnail URL points at this host, "+
|
||||
"which serves any key without the is_public check, so the bucket behind it must hold only public files: "+
|
||||
"protected (is_public false) uploads are refused",
|
||||
slog.String("host", publicURL.Host))
|
||||
|
||||
@@ -259,7 +259,7 @@ var BrokenImagePNG = func() []byte {
|
||||
// brokenThumb is the catch branch of WinterCMS's File::makeThumb: log the
|
||||
// reason, store BrokenImagePNG under the thumbnail key and return that key.
|
||||
func brokenThumb(ctx context.Context, bucket *blob.Bucket, f *File, thumbKey string, reason error) (string, error) {
|
||||
slog.Default().WarnContext(ctx, "attach: thumbnail original is unusable, storing the broken-image picture",
|
||||
moduleLog.WarnContext(ctx, "attach: thumbnail original is unusable, storing the broken-image picture",
|
||||
slog.Uint64("file_id", uint64(f.ID)), slog.String("error", reason.Error()))
|
||||
if err := bucket.WriteAll(ctx, thumbKey, BrokenImagePNG, &blob.WriterOptions{ContentType: "image/png"}); err != nil {
|
||||
return "", fmt.Errorf("attach: broken-image thumb: %w", err)
|
||||
|
||||
@@ -16,6 +16,10 @@ import (
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// moduleLog is lagoon's logger: records carry component=lagoon and follow
|
||||
// log.components.lagoon (diary).
|
||||
var moduleLog = diary.For("lagoon")
|
||||
|
||||
// Open pings dsn through pgx stdlib and returns that exact *sql.DB plus a
|
||||
// GORM handle opened on it.
|
||||
//
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -84,7 +83,7 @@ func logDroppedKeyOnce(production bool, typeName, key string) {
|
||||
if _, loaded := droppedKeys.LoadOrStore(k, struct{}{}); loaded {
|
||||
return
|
||||
}
|
||||
slog.Warn("lagoon: dropped non-fillable key", "type", typeName, "key", key)
|
||||
moduleLog.Warn("lagoon: dropped non-fillable key", "type", typeName, "key", key)
|
||||
}
|
||||
|
||||
func fieldByColumn(rv reflect.Value, rt reflect.Type, column string) (reflect.Value, bool) {
|
||||
|
||||
@@ -125,7 +125,7 @@ func PurgeDeferred(ctx context.Context, db *gorm.DB, bucket *blob.Bucket, opts P
|
||||
if len(keys) > 0 {
|
||||
AfterCommit(ctx, tx, func(ctx context.Context, _ *gorm.DB) {
|
||||
if err := attach.DeleteKeys(context.WithoutCancel(ctx), bucket, keys); err != nil {
|
||||
slog.Default().WarnContext(ctx, "lagoon: purge could not delete file blobs", slog.String("error", err.Error()))
|
||||
moduleLog.WarnContext(ctx, "lagoon: purge could not delete file blobs", slog.String("error", err.Error()))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func purgeBinding(ctx context.Context, tx *gorm.DB, bucket *blob.Bucket, opts Pu
|
||||
}
|
||||
env, err := row.Envelope()
|
||||
if err != nil {
|
||||
slog.Default().WarnContext(ctx, "lagoon: purge treats an unreadable deferred binding as a plain link",
|
||||
moduleLog.WarnContext(ctx, "lagoon: purge treats an unreadable deferred binding as a plain link",
|
||||
slog.Uint64("binding_id", uint64(row.ID)), slog.String("error", err.Error()))
|
||||
return true, nil
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func purgeBinding(ctx context.Context, tx *gorm.DB, bucket *blob.Bucket, opts Pu
|
||||
if !ok || model == nil {
|
||||
if !warned[row.SlaveType] {
|
||||
warned[row.SlaveType] = true
|
||||
slog.Default().WarnContext(ctx, "lagoon: purge cannot resolve the model of a deferred child; its bindings are kept",
|
||||
moduleLog.WarnContext(ctx, "lagoon: purge cannot resolve the model of a deferred child; its bindings are kept",
|
||||
slog.String("slave_type", row.SlaveType))
|
||||
}
|
||||
res.Skipped++
|
||||
|
||||
@@ -3,7 +3,6 @@ package lagoon
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -127,7 +126,7 @@ func AfterCommit(ctx context.Context, db *gorm.DB, fn func(ctx context.Context,
|
||||
return
|
||||
}
|
||||
if transactionalHandle(db) {
|
||||
slog.Default().Warn("lagoon: after-commit callback skipped inside unmanaged transaction")
|
||||
moduleLog.Warn("lagoon: after-commit callback skipped inside unmanaged transaction")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -196,7 +195,7 @@ func runAfterCommit(ctx context.Context, db *gorm.DB, fns []func(context.Context
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
slog.Default().Warn("lagoon: after-commit callback panicked", "panic", fmt.Sprint(r))
|
||||
moduleLog.Warn("lagoon: after-commit callback panicked", "panic", fmt.Sprint(r))
|
||||
}
|
||||
}()
|
||||
fn(ctx, db)
|
||||
|
||||
Reference in New Issue
Block a user