fix: findOpsDB falla con error en vez de crear operations.db en la raíz
Antes, si no encontraba operations.db subiendo directorios, hacía fallback silencioso a ./operations.db — lo que creaba la BD en la raíz violando la regla de db_locations. Ahora retorna error explícito indicando que se debe ejecutar fn ops init en el directorio correcto. También elimina operations.db espuria de la raíz (2 executions de metabase_registry creadas por el fallback). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+202
-6
@@ -37,6 +37,8 @@ func cmdOps(args []string) {
|
|||||||
cmdOpsExecution(args[1:])
|
cmdOpsExecution(args[1:])
|
||||||
case "assertion":
|
case "assertion":
|
||||||
cmdOpsAssertion(args[1:])
|
cmdOpsAssertion(args[1:])
|
||||||
|
case "log":
|
||||||
|
cmdOpsLog(args[1:])
|
||||||
case "help", "-h", "--help":
|
case "help", "-h", "--help":
|
||||||
printOpsUsage()
|
printOpsUsage()
|
||||||
default:
|
default:
|
||||||
@@ -87,7 +89,16 @@ Assertion commands:
|
|||||||
fn ops assertion delete <id> Elimina assertion
|
fn ops assertion delete <id> Elimina assertion
|
||||||
fn ops assertion eval --entity-id <id> Evalua assertions activas
|
fn ops assertion eval --entity-id <id> Evalua assertions activas
|
||||||
fn ops assertion result add <flags> Registra resultado manual
|
fn ops assertion result add <flags> Registra resultado manual
|
||||||
fn ops assertion result list [--assertion-id <id>]`)
|
fn ops assertion result list [--assertion-id <id>]
|
||||||
|
|
||||||
|
Log commands:
|
||||||
|
fn ops log add <flags> Registra log entry
|
||||||
|
fn ops log list [--level <lvl>] [--source <src>] [--limit N]
|
||||||
|
fn ops log show <id> Muestra log entry
|
||||||
|
|
||||||
|
Log flags:
|
||||||
|
--id <id> --level <debug|info|warn|error> --source <src>
|
||||||
|
--entity-id <id> --execution-id <id> --message <text> --metadata <json>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ops init ---
|
// --- ops init ---
|
||||||
@@ -714,12 +725,12 @@ func requireRegistryDB() *registry.DB {
|
|||||||
|
|
||||||
// --- helpers ---
|
// --- helpers ---
|
||||||
|
|
||||||
func findOpsDB() string {
|
func findOpsDB() (string, error) {
|
||||||
dir, _ := os.Getwd()
|
dir, _ := os.Getwd()
|
||||||
for {
|
for {
|
||||||
path := filepath.Join(dir, opsDBName)
|
path := filepath.Join(dir, opsDBName)
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
return path
|
return path, nil
|
||||||
}
|
}
|
||||||
parent := filepath.Dir(dir)
|
parent := filepath.Dir(dir)
|
||||||
if parent == dir {
|
if parent == dir {
|
||||||
@@ -727,15 +738,18 @@ func findOpsDB() string {
|
|||||||
}
|
}
|
||||||
dir = parent
|
dir = parent
|
||||||
}
|
}
|
||||||
return filepath.Join(".", opsDBName)
|
return "", fmt.Errorf("operations.db not found (searched from %s up to /)\nRun 'fn ops init <path>' to create one inside an app directory", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openOpsDB() *ops.DB {
|
func openOpsDB() *ops.DB {
|
||||||
path := findOpsDB()
|
path, err := findOpsDB()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
db, err := ops.Open(path)
|
db, err := ops.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "error opening operations.db: %v\n", err)
|
fmt.Fprintf(os.Stderr, "error opening operations.db: %v\n", err)
|
||||||
fmt.Fprintln(os.Stderr, "Run 'fn ops init' first to create one.")
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return db
|
return db
|
||||||
@@ -1398,6 +1412,188 @@ func formatResultValue(v map[string]any) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Log subcommands ---
|
||||||
|
|
||||||
|
func cmdOpsLog(args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: fn ops log <add|list|show>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
switch args[0] {
|
||||||
|
case "add":
|
||||||
|
cmdOpsLogAdd(args[1:])
|
||||||
|
case "list":
|
||||||
|
cmdOpsLogList(args[1:])
|
||||||
|
case "show":
|
||||||
|
cmdOpsLogShow(args[1:])
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "unknown log command: %s\n", args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdOpsLogAdd(args []string) {
|
||||||
|
var id, level, source, entityID, executionID, message, metadataStr string
|
||||||
|
i := 0
|
||||||
|
for i < len(args) {
|
||||||
|
switch args[i] {
|
||||||
|
case "--id":
|
||||||
|
i++
|
||||||
|
id = args[i]
|
||||||
|
case "--level":
|
||||||
|
i++
|
||||||
|
level = args[i]
|
||||||
|
case "--source":
|
||||||
|
i++
|
||||||
|
source = args[i]
|
||||||
|
case "--entity-id":
|
||||||
|
i++
|
||||||
|
entityID = args[i]
|
||||||
|
case "--execution-id":
|
||||||
|
i++
|
||||||
|
executionID = args[i]
|
||||||
|
case "--message", "-m":
|
||||||
|
i++
|
||||||
|
message = args[i]
|
||||||
|
case "--metadata":
|
||||||
|
i++
|
||||||
|
metadataStr = args[i]
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
// Read from stdin if no message flag
|
||||||
|
b, err := io.ReadAll(os.Stdin)
|
||||||
|
if err == nil && len(b) > 0 {
|
||||||
|
message = strings.TrimSpace(string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "error: --message is required (or pipe to stdin)")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if level == "" {
|
||||||
|
level = "info"
|
||||||
|
}
|
||||||
|
if id == "" {
|
||||||
|
id = fmt.Sprintf("log_%d", timeNow().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata map[string]any
|
||||||
|
if metadataStr != "" {
|
||||||
|
if err := json.Unmarshal([]byte(metadataStr), &metadata); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: invalid metadata JSON: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &ops.Log{
|
||||||
|
ID: id,
|
||||||
|
Level: ops.LogLevel(level),
|
||||||
|
Source: source,
|
||||||
|
EntityID: entityID,
|
||||||
|
ExecutionID: executionID,
|
||||||
|
Message: message,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
db := openOpsDB()
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if err := db.InsertLog(l); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Logged: [%s] %s\n", l.Level, truncate(l.Message, 60))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdOpsLogList(args []string) {
|
||||||
|
var level, source, entityID, executionID string
|
||||||
|
limit := 50
|
||||||
|
i := 0
|
||||||
|
for i < len(args) {
|
||||||
|
switch args[i] {
|
||||||
|
case "--level":
|
||||||
|
i++
|
||||||
|
level = args[i]
|
||||||
|
case "--source":
|
||||||
|
i++
|
||||||
|
source = args[i]
|
||||||
|
case "--entity-id":
|
||||||
|
i++
|
||||||
|
entityID = args[i]
|
||||||
|
case "--execution-id":
|
||||||
|
i++
|
||||||
|
executionID = args[i]
|
||||||
|
case "--limit", "-n":
|
||||||
|
i++
|
||||||
|
limit = int(parseInt64(args[i]))
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
db := openOpsDB()
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
logs, err := db.ListLogs(ops.LogLevel(level), source, entityID, executionID, limit)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(logs) == 0 {
|
||||||
|
fmt.Println("No logs.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||||
|
fmt.Fprintln(w, "CREATED_AT\tLEVEL\tSOURCE\tMESSAGE")
|
||||||
|
for _, l := range logs {
|
||||||
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", l.CreatedAt.Format(time.RFC3339), l.Level, l.Source, truncate(l.Message, 60))
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdOpsLogShow(args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: fn ops log show <id>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := openOpsDB()
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
l, err := db.GetLog(args[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if l == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "log %q not found\n", args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("ID: %s\n", l.ID)
|
||||||
|
fmt.Printf("Level: %s\n", l.Level)
|
||||||
|
fmt.Printf("Source: %s\n", l.Source)
|
||||||
|
if l.EntityID != "" {
|
||||||
|
fmt.Printf("Entity: %s\n", l.EntityID)
|
||||||
|
}
|
||||||
|
if l.ExecutionID != "" {
|
||||||
|
fmt.Printf("Execution: %s\n", l.ExecutionID)
|
||||||
|
}
|
||||||
|
fmt.Printf("Message: %s\n", l.Message)
|
||||||
|
if len(l.Metadata) > 0 {
|
||||||
|
m, _ := json.MarshalIndent(l.Metadata, " ", " ")
|
||||||
|
fmt.Printf("Metadata: %s\n", string(m))
|
||||||
|
}
|
||||||
|
fmt.Printf("Created: %s\n", l.CreatedAt.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
func tryOpenRegistryDB() *registry.DB {
|
func tryOpenRegistryDB() *registry.DB {
|
||||||
// Try FN_REGISTRY_ROOT env var first
|
// Try FN_REGISTRY_ROOT env var first
|
||||||
if envRoot := os.Getenv("FN_REGISTRY_ROOT"); envRoot != "" {
|
if envRoot := os.Getenv("FN_REGISTRY_ROOT"); envRoot != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user