{"record":{"id":"5ba3e8d288525d0a","repo":"crowdsecurity/crowdsec","slug":"storing-metrics-snapshot-for-s-at-s-w","errorCode":null,"errorMessage":"storing metrics snapshot for '%s' at %s: %w","messagePattern":"storing metrics snapshot for '(.+?)' at (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/database/metrics.go","lineNumber":22,"sourceCode":"\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/crowdsecurity/crowdsec/pkg/database/ent\"\n\t\"github.com/crowdsecurity/crowdsec/pkg/database/ent/metric\"\n)\n\nfunc (c *Client) CreateMetric(ctx context.Context, generatedType metric.GeneratedType, generatedBy string, receivedAt time.Time, payload string) (*ent.Metric, error) {\n\tmetric, err := c.Ent.Metric.\n\t\tCreate().\n\t\tSetGeneratedType(generatedType).\n\t\tSetGeneratedBy(generatedBy).\n\t\tSetReceivedAt(receivedAt).\n\t\tSetPayload(payload).\n\t\tSave(ctx)\n\tif err != nil {\n\t\tc.Log.Warningf(\"CreateMetric: %s\", err)\n\t\treturn nil, fmt.Errorf(\"storing metrics snapshot for '%s' at %s: %w\", generatedBy, receivedAt, InsertFail)\n\t}\n\n\treturn metric, nil\n}\n\nfunc (c *Client) GetLPUsageMetricsByMachineID(ctx context.Context, machineId string, toSend bool) ([]*ent.Metric, error) {\n\tquery := c.Ent.Metric.Query().\n\t\tWhere(\n\t\t\tmetric.GeneratedTypeEQ(metric.GeneratedTypeLP),\n\t\t\tmetric.GeneratedByEQ(machineId),\n\t\t)\n\n\tif toSend {\n\t\tquery = query.Where(metric.PushedAtIsNil())\n\t}\n\n\tmetrics, err := query.All(ctx)\n\tif err != nil {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/database/metrics.go#L4-L40","documentation":"CreateMetric inserts a usage-metrics snapshot row (generatedBy, receivedAt, payload) into the local SQLite DB via ent. When the ent Save() fails, the original error is logged as a warning and a wrapped InsertFail ('unable to insert row') sentinel is returned, so callers only see the generic insert failure, not the underlying cause (check logs for details).","triggerScenarios":"Calling CreateMetric when the underlying database INSERT fails: DB file locked by another writer, disk full, schema out of date (missing metric table), corrupted SQLite file, or context cancellation during Save.","commonSituations":"Multiple crowdsec/cscli processes contending for the SQLite write lock; disk quota exhausted on /var/lib/crowdsec; upgrading crowdsec without running the schema migration so the metrics table is stale.","solutions":["Check the accompanying Warningf log line 'CreateMetric: <err>' for the real cause (e.g. 'database is locked', 'no such table').","Ensure only one crowdsec instance uses the same DB and increase contention tolerance (WAL mode) or reduce writers.","Run 'cscli db migrate' / restart crowdsec so ent schema migrations create/update the metrics table.","Free disk space and verify the DB file ('cscli db doctor' or sqlite3 integrity_check).","Retry the operation; CreateMetric is called from the periodic usage-metrics loop, so a transient failure self-heals on the next tick."],"exampleFix":"// before: only generic sentinel returned, real cause only in logs\nif err != nil {\n    return nil, fmt.Errorf(\"storing metrics snapshot for '%s' at %s: %w\", generatedBy, receivedAt, InsertFail)\n}\n// after (caller side): check the underlying causes with errors.Is\nvar insertFail = db.InsertFail\nif errors.Is(err, insertFail) {\n    log.Warnf(\"metrics insert failed, will retry next cycle: %v\", err)\n}","handlingStrategy":"try-catch","validationCode":"// pre-check DB writability before the insert\nif _, err := os.Stat(dbPath); err != nil { return err }\nif fi, err := os.Stat(dbPath); err == nil && fi.Size() > 0 {\n    if f, err := os.OpenFile(dbPath, os.O_WRONLY, 0); err == nil { f.Close() } else { return err }\n}","typeGuard":null,"tryCatchPattern":"metric, err := client.CreateMetric(ctx, generatedBy, receivedAt, payload)\nif err != nil {\n    if errors.Is(err, database.InsertFail) {\n        log.Warnf(\"metric insert failed (see warning log for cause), retrying next cycle: %v\", err)\n        return nil // non-fatal; metrics are periodic\n    }\n    return err\n}","preventionTips":["Read the paired Warningf log line — the real DB cause is there, not in the wrapped sentinel.","Keep one writer per SQLite DB (or enable WAL mode).","Monitor free disk space on the crowdsec data volume.","Keep ent schema migrations running on upgrade so the metrics table exists."],"tags":["database","sqlite","insert-failure","metrics"],"backgroundTag":"database-write-failed","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}