infra: add logging wrapping
Resolves issues with gcp cloud logging quirks such as field names
This commit is contained in:
@@ -3,11 +3,15 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/flowy-live/llink/internal/utils/flog"
|
||||
|
||||
"cloud.google.com/go/firestore"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
pbaero "github.com/flowy-live/llink/genproto/aero"
|
||||
pbpusher "github.com/flowy-live/llink/genproto/llink/pusher"
|
||||
"github.com/flowy-live/llink/internal/db"
|
||||
@@ -15,12 +19,10 @@ import (
|
||||
"github.com/flowy-live/llink/internal/network"
|
||||
"github.com/flowy-live/llink/internal/particle"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
const (
|
||||
maxActivityAge = 24 * time.Hour // ignore streams idle longer than this
|
||||
maxActivityAge = 24 * time.Hour // ignore streams idle longer than this
|
||||
unreadThreshold = 10 * time.Minute // grace window before a message is "unread"
|
||||
emailCooldown = 12 * time.Hour // min gap between emails to the same user
|
||||
)
|
||||
@@ -34,7 +36,7 @@ func main() {
|
||||
gcpProject := utils.MustGetEnv("GCP_PROJECT")
|
||||
firestoreClient, err := firestore.NewClient(ctx, gcpProject)
|
||||
if err != nil {
|
||||
slog.Error("failed to create Firestore client", "error", err)
|
||||
flog.Error("failed to create Firestore client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer firestoreClient.Close()
|
||||
@@ -42,7 +44,7 @@ func main() {
|
||||
aeroAddr := utils.MustGetEnv("AERO_ADDR")
|
||||
aeroConn, err := grpc.NewClient(aeroAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
slog.Error("failed to connect to aero", "error", err)
|
||||
flog.Error("failed to connect to aero", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer aeroConn.Close()
|
||||
@@ -51,7 +53,7 @@ func main() {
|
||||
pusherAddr := utils.MustGetEnv("PUSHER_GRPC_ADDR")
|
||||
pusherConn, err := grpc.NewClient(pusherAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
slog.Error("failed to connect to pusher", "error", err)
|
||||
flog.Error("failed to connect to pusher", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer pusherConn.Close()
|
||||
@@ -60,12 +62,12 @@ func main() {
|
||||
humanSvc := human.NewService(db.Pool())
|
||||
networkSvc := network.NewReader(db.Pool())
|
||||
|
||||
slog.Info("starting email notification cycle")
|
||||
flog.Info("starting email notification cycle")
|
||||
if err := runNotificationCycle(ctx, firestoreClient, aeroSvc, pusherSvc, humanSvc, networkSvc); err != nil {
|
||||
slog.Error("notification cycle failed", "error", err)
|
||||
flog.Error("notification cycle failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("email notification cycle complete")
|
||||
flog.Info("email notification cycle complete")
|
||||
}
|
||||
|
||||
func runNotificationCycle(
|
||||
@@ -100,7 +102,7 @@ func runNotificationCycle(
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get online humans: %w", err)
|
||||
}
|
||||
slog.Info("gathered online presence", "onlineCount", len(onlineResp.HumanIds), "humanIds", onlineResp.HumanIds)
|
||||
flog.Info("gathered online presence", "onlineCount", len(onlineResp.HumanIds), "humanIds", onlineResp.HumanIds)
|
||||
for _, id := range onlineResp.HumanIds {
|
||||
allOnline[id] = true
|
||||
}
|
||||
@@ -108,7 +110,7 @@ func runNotificationCycle(
|
||||
for _, net := range networks {
|
||||
streams, err := getOpenStreams(ctx, fsClient, net.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to query streams", "networkId", net.ID, "error", err)
|
||||
flog.Error("failed to query streams", "networkId", net.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -141,7 +143,7 @@ func runNotificationCycle(
|
||||
sentCount := 0
|
||||
for humanId, count := range behindCounts {
|
||||
if allOnline[humanId] {
|
||||
slog.Info("human online...skipping email", "humanId", humanId)
|
||||
flog.Info("human online...skipping email", "humanId", humanId)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -164,18 +166,18 @@ func runNotificationCycle(
|
||||
}
|
||||
|
||||
if err := sendNotificationEmail(ctx, aeroSvc, h, count); err != nil {
|
||||
slog.Error("failed to send email", "humanId", humanId, "error", err)
|
||||
flog.Error("failed to send email", "humanId", humanId, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := humanSvc.UpdateLastEmailNotificationSentAt(ctx, humanId, now); err != nil {
|
||||
slog.Error("failed to update last_email_notification_sent_at", "humanId", humanId, "error", err)
|
||||
flog.Error("failed to update last_email_notification_sent_at", "humanId", humanId, "error", err)
|
||||
}
|
||||
|
||||
sentCount++
|
||||
}
|
||||
|
||||
slog.Info("notification cycle summary",
|
||||
flog.Info("notification cycle summary",
|
||||
"networks", len(networks),
|
||||
"humansBehind", len(behindCounts),
|
||||
"emailsSent", sentCount,
|
||||
@@ -198,7 +200,7 @@ func getOpenStreams(ctx context.Context, client *firestore.Client, networkId str
|
||||
for _, doc := range docs {
|
||||
var s particle.FirestoreStreamParticle
|
||||
if err := doc.DataTo(&s); err != nil {
|
||||
slog.Warn("failed to unmarshal stream particle", "docId", doc.Ref.ID, "error", err)
|
||||
flog.Warn("failed to unmarshal stream particle", "docId", doc.Ref.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
streams = append(streams, s)
|
||||
|
||||
@@ -6,17 +6,19 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/flowy-live/llink/internal/utils/flog"
|
||||
|
||||
"cloud.google.com/go/firestore"
|
||||
"google.golang.org/api/iterator"
|
||||
|
||||
"github.com/flowy-live/llink/internal/db"
|
||||
"github.com/flowy-live/llink/internal/human"
|
||||
"github.com/flowy-live/llink/internal/network"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -28,7 +30,7 @@ func main() {
|
||||
gcpProject := utils.MustGetEnv("GCP_PROJECT")
|
||||
fs, err := firestore.NewClient(ctx, gcpProject)
|
||||
if err != nil {
|
||||
slog.Error("failed to create Firestore client", "error", err)
|
||||
flog.Error("failed to create Firestore client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer fs.Close()
|
||||
@@ -39,10 +41,10 @@ func main() {
|
||||
started := time.Now()
|
||||
written, scanned, err := reconcile(ctx, fs, humanSvc, networkSvc)
|
||||
if err != nil {
|
||||
slog.Error("reconciliation failed", "error", err, "elapsed", time.Since(started))
|
||||
flog.Error("reconciliation failed", "error", err, "elapsed", time.Since(started))
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("reconciliation complete",
|
||||
flog.Info("reconciliation complete",
|
||||
"humans_scanned", scanned,
|
||||
"humans_written", written,
|
||||
"elapsed", time.Since(started),
|
||||
@@ -109,7 +111,7 @@ func snapshotMirror(ctx context.Context, fs *firestore.Client) (map[string][]str
|
||||
Networks []string `firestore:"networks"`
|
||||
}
|
||||
if err := doc.DataTo(&data); err != nil {
|
||||
slog.Warn("skipping malformed mirror doc", "id", doc.Ref.ID, "error", err)
|
||||
flog.Warn("skipping malformed mirror doc", "id", doc.Ref.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
out[doc.Ref.ID] = data.Networks
|
||||
|
||||
+15
-13
@@ -3,14 +3,19 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/flowy-live/llink/internal/utils/flog"
|
||||
|
||||
"cloud.google.com/go/firestore"
|
||||
"cloud.google.com/go/storage"
|
||||
firebase "firebase.google.com/go/v4"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
pbaero "github.com/flowy-live/llink/genproto/aero"
|
||||
"github.com/flowy-live/llink/internal"
|
||||
"github.com/flowy-live/llink/internal/auth"
|
||||
@@ -27,9 +32,6 @@ import (
|
||||
"github.com/flowy-live/llink/internal/particle"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
"github.com/flowy-live/llink/internal/waitlist"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func redisForAuth() *redis.Client {
|
||||
@@ -48,19 +50,19 @@ func main() {
|
||||
ctx := context.Background()
|
||||
storageClient, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
slog.Error("failed to create GCS client", "error", err)
|
||||
flog.Error("failed to create GCS client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer storageClient.Close()
|
||||
|
||||
aeroAddr := utils.MustGetEnv("AERO_ADDR")
|
||||
if aeroAddr == "" {
|
||||
slog.Error("must provide AERO_ADDR")
|
||||
flog.Error("must provide AERO_ADDR")
|
||||
os.Exit(1)
|
||||
}
|
||||
aeroServer, err := grpc.NewClient(aeroAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
slog.Error("connection to aero server invalid", "error", err)
|
||||
flog.Error("connection to aero server invalid", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer aeroServer.Close()
|
||||
@@ -69,12 +71,12 @@ func main() {
|
||||
gcpProject := utils.MustGetEnv("GCP_PROJECT")
|
||||
fbApp, err := firebase.NewApp(ctx, &firebase.Config{ProjectID: gcpProject})
|
||||
if err != nil {
|
||||
slog.Error("failed to init Firebase Admin app", "error", err)
|
||||
flog.Error("failed to init Firebase Admin app", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fbAuth, err := fbApp.Auth(ctx)
|
||||
if err != nil {
|
||||
slog.Error("failed to create Firebase auth client", "error", err)
|
||||
flog.Error("failed to create Firebase auth client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -90,13 +92,13 @@ func main() {
|
||||
CancelURL: utils.MustGetEnv("BILLING_CANCEL_URL"),
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("failed to initialize billing service", "error", err)
|
||||
flog.Error("failed to initialize billing service", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
firestoreClient, err := firestore.NewClient(ctx, gcpProject)
|
||||
if err != nil {
|
||||
slog.Error("failed to create Firestore client", "error", err)
|
||||
flog.Error("failed to create Firestore client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer firestoreClient.Close()
|
||||
@@ -202,9 +204,9 @@ func main() {
|
||||
muxWithCors := middleware.CORS(allowedOrigins)(mux)
|
||||
|
||||
addr := fmt.Sprintf("0.0.0.0:%s", port)
|
||||
slog.Info("running server", "addr", addr)
|
||||
flog.Info("running server", "addr", addr)
|
||||
if err := http.ListenAndServe(addr, muxWithCors); err != nil {
|
||||
slog.Error("server failed", "error", err)
|
||||
flog.Error("server failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/flowy-live/llink/internal/utils/flog"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/flowy-live/llink/internal/billing"
|
||||
"github.com/flowy-live/llink/internal/db"
|
||||
"github.com/flowy-live/llink/internal/depot"
|
||||
@@ -18,8 +22,6 @@ import (
|
||||
"github.com/flowy-live/llink/internal/particle"
|
||||
"github.com/flowy-live/llink/internal/speech"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"cloud.google.com/go/firestore"
|
||||
"cloud.google.com/go/storage"
|
||||
@@ -48,7 +50,7 @@ func main() {
|
||||
|
||||
storageClient, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
slog.Error("failed to create GCS client", "error", err)
|
||||
flog.Error("failed to create GCS client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer storageClient.Close()
|
||||
@@ -83,7 +85,7 @@ func main() {
|
||||
panic(fmt.Errorf("error: %w", err))
|
||||
}
|
||||
if err != nil {
|
||||
slog.Error("error in processing snapshot", "error", err)
|
||||
flog.Error("error in processing snapshot", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -100,15 +102,15 @@ func main() {
|
||||
|
||||
processed, err := processingRepo.IsProcessed(ctx, particleID)
|
||||
if err != nil {
|
||||
slog.Error("failed to check processing status", "particleID", particleID, "error", err)
|
||||
flog.Error("failed to check processing status", "particleID", particleID, "error", err)
|
||||
continue
|
||||
}
|
||||
if processed {
|
||||
slog.Debug("skipping already processed particle", "particleID", particleID)
|
||||
flog.Debug("skipping already processed particle", "particleID", particleID)
|
||||
continue
|
||||
}
|
||||
|
||||
slog.Debug("processing particle", "particleID", particleID, "data", change.Doc.Data())
|
||||
flog.Debug("processing particle", "particleID", particleID, "data", change.Doc.Data())
|
||||
|
||||
// Side effects below are best-effort — failures don't prevent
|
||||
// marking the particle as processed.
|
||||
@@ -120,7 +122,7 @@ func main() {
|
||||
notifyForParticle(ctx, notifier, humanSvc, change.Doc, parentDoc, transcript)
|
||||
|
||||
if err := processingRepo.MarkProcessed(ctx, particleID); err != nil {
|
||||
slog.Error("failed to mark particle as processed", "particleID", particleID, "error", err)
|
||||
flog.Error("failed to mark particle as processed", "particleID", particleID, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,30 +134,30 @@ func transcribeMediaParticle(ctx context.Context, depotSvc depot.Service, speech
|
||||
var mediaParticle particle.FirestoreMediaParticle
|
||||
err := doc.DataTo(&mediaParticle)
|
||||
if err != nil {
|
||||
slog.Error("unable to marshal particle data", "error", err)
|
||||
flog.Error("unable to marshal particle data", "error", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
particleType, err := particle.ParseParticleType(mediaParticle.Type)
|
||||
if err != nil {
|
||||
slog.Error("invalid particle type", "error", err)
|
||||
flog.Error("invalid particle type", "error", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if particleType != particle.TypeMedia {
|
||||
slog.Info("received a particle of type", "particle type", particleType)
|
||||
flog.Info("received a particle of type", "particle type", particleType)
|
||||
return ""
|
||||
}
|
||||
|
||||
downloadURL, err := depotSvc.GetDownloadURL(ctx, mediaParticle.Properties.ObjectId)
|
||||
if err != nil {
|
||||
slog.Error("failed to get download URL", "error", err, "object_id", mediaParticle.Properties.ObjectId)
|
||||
flog.Error("failed to get download URL", "error", err, "object_id", mediaParticle.Properties.ObjectId)
|
||||
return ""
|
||||
}
|
||||
|
||||
result, err := speechSvc.Transcribe(ctx, downloadURL)
|
||||
if err != nil {
|
||||
slog.Error("failed to transcribe media", "error", err, "particleID", doc.Ref.ID)
|
||||
flog.Error("failed to transcribe media", "error", err, "particleID", doc.Ref.ID)
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -167,11 +169,11 @@ func transcribeMediaParticle(ctx context.Context, depotSvc depot.Service, speech
|
||||
},
|
||||
}, firestore.MergeAll)
|
||||
if err != nil {
|
||||
slog.Error("failed to update transcript in firestore", "error", err, "particleID", doc.Ref.ID)
|
||||
flog.Error("failed to update transcript in firestore", "error", err, "particleID", doc.Ref.ID)
|
||||
return ""
|
||||
}
|
||||
|
||||
slog.Info("transcribed media particle", "particleID", doc.Ref.ID)
|
||||
flog.Info("transcribed media particle", "particleID", doc.Ref.ID)
|
||||
return transcript.Transcript
|
||||
}
|
||||
|
||||
@@ -215,17 +217,17 @@ func toFirestoreTranscript(result *speech.TranscriptResult) particle.FirestoreTr
|
||||
func recordFreemiumUsage(ctx context.Context, billingSvc billing.Service, doc *firestore.DocumentSnapshot) {
|
||||
rawType, err := doc.DataAt("type")
|
||||
if err != nil {
|
||||
slog.Error("failed to read particle type", "error", err, "particleID", doc.Ref.ID)
|
||||
flog.Error("failed to read particle type", "error", err, "particleID", doc.Ref.ID)
|
||||
return
|
||||
}
|
||||
typeStr, ok := rawType.(string)
|
||||
if !ok {
|
||||
slog.Error("particle type is not a string", "particleID", doc.Ref.ID, "type", rawType)
|
||||
flog.Error("particle type is not a string", "particleID", doc.Ref.ID, "type", rawType)
|
||||
return
|
||||
}
|
||||
particleType, err := particle.ParseParticleType(typeStr)
|
||||
if err != nil {
|
||||
slog.Error("invalid particle type", "error", err, "particleID", doc.Ref.ID)
|
||||
flog.Error("invalid particle type", "error", err, "particleID", doc.Ref.ID)
|
||||
return
|
||||
}
|
||||
// Containers don't count toward the daily message cap.
|
||||
@@ -235,12 +237,12 @@ func recordFreemiumUsage(ctx context.Context, billingSvc billing.Service, doc *f
|
||||
|
||||
networkID, err := particle.NetworkIDFromParticlePath(doc.Ref.Path)
|
||||
if err != nil {
|
||||
slog.Error("failed to derive network id", "error", err, "path", doc.Ref.Path)
|
||||
flog.Error("failed to derive network id", "error", err, "path", doc.Ref.Path)
|
||||
return
|
||||
}
|
||||
|
||||
if err := billingSvc.IncrementDailyUsage(ctx, networkID, doc.CreateTime); err != nil {
|
||||
slog.Error("failed to increment daily usage", "error", err, "networkID", networkID, "particleID", doc.Ref.ID)
|
||||
flog.Error("failed to increment daily usage", "error", err, "networkID", networkID, "particleID", doc.Ref.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,12 +254,12 @@ func loadParentParticle(ctx context.Context, doc *firestore.DocumentSnapshot) *f
|
||||
}
|
||||
parentParticleDocRef := parentChildrenCollectionRef.Parent
|
||||
if parentParticleDocRef == nil {
|
||||
slog.Error("particle has no parent document", "particleID", doc.Ref.ID)
|
||||
flog.Error("particle has no parent document", "particleID", doc.Ref.ID)
|
||||
return nil
|
||||
}
|
||||
parentParticleDoc, err := parentParticleDocRef.Get(ctx)
|
||||
if err != nil {
|
||||
slog.Error("failed to get parent particle", "error", err, "particleID", doc.Ref.ID)
|
||||
flog.Error("failed to get parent particle", "error", err, "particleID", doc.Ref.ID)
|
||||
return nil
|
||||
}
|
||||
return parentParticleDoc
|
||||
@@ -272,13 +274,13 @@ func updateParentLastChildCreatedAt(ctx context.Context, doc *firestore.Document
|
||||
|
||||
var streamParticle particle.FirestoreStreamParticle
|
||||
if err := parent.DataTo(&streamParticle); err != nil {
|
||||
slog.Error("failed to parse stream particle", "error", err)
|
||||
flog.Error("failed to parse stream particle", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
particleType, err := particle.ParseParticleType(streamParticle.Type)
|
||||
if err != nil {
|
||||
slog.Error("invalid particle type", "error", err)
|
||||
flog.Error("invalid particle type", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -288,7 +290,7 @@ func updateParentLastChildCreatedAt(ctx context.Context, doc *firestore.Document
|
||||
|
||||
childCreatedAt, err := doc.DataAt("created_at")
|
||||
if err != nil {
|
||||
slog.Error("failed to read child created_at", "error", err, "particleID", doc.Ref.ID)
|
||||
flog.Error("failed to read child created_at", "error", err, "particleID", doc.Ref.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -299,7 +301,7 @@ func updateParentLastChildCreatedAt(ctx context.Context, doc *firestore.Document
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("unable to update parent particle `last_child_created_at`", "error", err)
|
||||
flog.Error("unable to update parent particle `last_child_created_at`", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +317,7 @@ func notifyForParticle(
|
||||
transcript string,
|
||||
) {
|
||||
if parent == nil {
|
||||
slog.Info("notify: skip — no parent", "particleID", doc.Ref.ID)
|
||||
flog.Info("notify: skip — no parent", "particleID", doc.Ref.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -323,31 +325,31 @@ func notifyForParticle(
|
||||
typeName, _ := typeStr.(string)
|
||||
pType, err := particle.ParseParticleType(typeName)
|
||||
if err != nil {
|
||||
slog.Info("notify: skip — unparseable particle type",
|
||||
flog.Info("notify: skip — unparseable particle type",
|
||||
"particleID", doc.Ref.ID, "type", typeName, "error", err)
|
||||
return
|
||||
}
|
||||
if pType == particle.TypeStream || pType == particle.TypeFolder {
|
||||
slog.Info("notify: skip — container particle",
|
||||
flog.Info("notify: skip — container particle",
|
||||
"particleID", doc.Ref.ID, "type", pType)
|
||||
return
|
||||
}
|
||||
|
||||
var parentStream particle.FirestoreStreamParticle
|
||||
if err := parent.DataTo(&parentStream); err != nil {
|
||||
slog.Error("notify: failed to parse parent stream", "error", err)
|
||||
flog.Error("notify: failed to parse parent stream", "error", err)
|
||||
return
|
||||
}
|
||||
parentType, err := particle.ParseParticleType(parentStream.Type)
|
||||
if err != nil || parentType != particle.TypeStream {
|
||||
slog.Info("notify: skip — parent isn't a stream",
|
||||
flog.Info("notify: skip — parent isn't a stream",
|
||||
"particleID", doc.Ref.ID, "parentType", parentType, "parseErr", err)
|
||||
return
|
||||
}
|
||||
|
||||
networkID, err := particle.NetworkIDFromParticlePath(doc.Ref.Path)
|
||||
if err != nil {
|
||||
slog.Error("notify: failed to derive network id", "error", err, "path", doc.Ref.Path)
|
||||
flog.Error("notify: failed to derive network id", "error", err, "path", doc.Ref.Path)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -370,7 +372,7 @@ func notifyForParticle(
|
||||
if sender, err := humanSvc.GetByID(ctx, senderHumanID); err == nil {
|
||||
senderEmailPrefix = sender.EmailPrefix
|
||||
} else {
|
||||
slog.Warn("notify: failed to look up sender", "error", err, "humanID", senderHumanID)
|
||||
flog.Warn("notify: failed to look up sender", "error", err, "humanID", senderHumanID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +387,7 @@ func notifyForParticle(
|
||||
StreamVisibleTo: parentStream.VisibleTo,
|
||||
Body: previewForParticle(pType, doc, transcript),
|
||||
}); err != nil {
|
||||
slog.Error("notify: dispatch failed", "error", err, "particleID", doc.Ref.ID, "networkID", networkID)
|
||||
flog.Error("notify: dispatch failed", "error", err, "particleID", doc.Ref.ID, "networkID", networkID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/flowy-live/llink/internal/utils/flog"
|
||||
|
||||
"github.com/flowy-live/llink/internal"
|
||||
"github.com/flowy-live/llink/internal/auth"
|
||||
"github.com/flowy-live/llink/internal/db"
|
||||
@@ -17,8 +18,9 @@ import (
|
||||
"github.com/flowy-live/llink/internal/pusher"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
|
||||
pbpusher "github.com/flowy-live/llink/genproto/llink/pusher"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
pbpusher "github.com/flowy-live/llink/genproto/llink/pusher"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -56,15 +58,15 @@ func main() {
|
||||
// --- gRPC server (internal presence queries) ---
|
||||
grpcListener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", grpcPort))
|
||||
if err != nil {
|
||||
slog.Error("failed to listen for gRPC", "port", grpcPort, "error", err)
|
||||
flog.Error("failed to listen for gRPC", "port", grpcPort, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
grpcServer := grpc.NewServer()
|
||||
pbpusher.RegisterPusherServiceServer(grpcServer, server)
|
||||
go func() {
|
||||
slog.Info("gRPC server listening", "port", grpcPort)
|
||||
flog.Info("gRPC server listening", "port", grpcPort)
|
||||
if err := grpcServer.Serve(grpcListener); err != nil {
|
||||
slog.Error("gRPC server failed", "error", err)
|
||||
flog.Error("gRPC server failed", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -79,9 +81,9 @@ func main() {
|
||||
httpServer := &http.Server{Addr: httpAddr, Handler: mux}
|
||||
|
||||
go func() {
|
||||
slog.Info("HTTP server listening", "addr", httpAddr)
|
||||
flog.Info("HTTP server listening", "addr", httpAddr)
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("HTTP server failed", "error", err)
|
||||
flog.Error("HTTP server failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
@@ -91,10 +93,10 @@ func main() {
|
||||
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
|
||||
<-sigCh
|
||||
|
||||
slog.Info("shutting down...")
|
||||
flog.Info("shutting down...")
|
||||
cancel()
|
||||
|
||||
grpcServer.GracefulStop()
|
||||
httpServer.Shutdown(context.Background())
|
||||
slog.Info("shutdown complete")
|
||||
flog.Info("shutdown complete")
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/flowy-live/llink/internal/utils/flog"
|
||||
|
||||
"cloud.google.com/go/firestore"
|
||||
"cloud.google.com/go/storage"
|
||||
"google.golang.org/api/iterator"
|
||||
@@ -27,7 +28,7 @@ func main() {
|
||||
|
||||
storageClient, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
slog.Error("failed to create GCS client", "error", err)
|
||||
flog.Error("failed to create GCS client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer storageClient.Close()
|
||||
@@ -40,7 +41,7 @@ func main() {
|
||||
gcpProject := utils.MustGetEnv("GCP_PROJECT")
|
||||
fs, err := firestore.NewClient(ctx, gcpProject)
|
||||
if err != nil {
|
||||
slog.Error("failed to create Firestore client", "error", err)
|
||||
flog.Error("failed to create Firestore client", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer fs.Close()
|
||||
@@ -49,7 +50,7 @@ func main() {
|
||||
stats, err := run(ctx, fs, depotSvc)
|
||||
elapsed := time.Since(started)
|
||||
|
||||
slog.Info("transcode_backfill_summary",
|
||||
flog.Info("transcode_backfill_summary",
|
||||
"scanned", stats.scanned,
|
||||
"media", stats.media,
|
||||
"transcoded", stats.transcoded,
|
||||
@@ -61,7 +62,7 @@ func main() {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("transcode backfill aborted", "error", err)
|
||||
flog.Error("transcode backfill aborted", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if stats.failures > 0 {
|
||||
@@ -116,7 +117,7 @@ func run(ctx context.Context, fs *firestore.Client, depotSvc depot.Service) (sta
|
||||
switch {
|
||||
case result.Err != nil:
|
||||
s.failures++
|
||||
slog.Error("transcode_backfill_failure",
|
||||
flog.Error("transcode_backfill_failure",
|
||||
"particleID", doc.Ref.ID,
|
||||
"path", doc.Ref.Path,
|
||||
"error", result.Err,
|
||||
@@ -129,7 +130,7 @@ func run(ctx context.Context, fs *firestore.Client, depotSvc depot.Service) (sta
|
||||
s.skippedNonMedia++
|
||||
default:
|
||||
s.transcoded++
|
||||
slog.Info("transcode_backfill_progress",
|
||||
flog.Info("transcode_backfill_progress",
|
||||
"particleID", doc.Ref.ID,
|
||||
"transcodedObjectID", result.TranscodedObjectID,
|
||||
"outputMime", result.OutputMimeType,
|
||||
|
||||
Reference in New Issue
Block a user