fix(fn-run): propagar stdout/stderr de bash functions library-style #1
+202
-6
@@ -37,6 +37,8 @@ func cmdOps(args []string) {
|
||||
cmdOpsExecution(args[1:])
|
||||
case "assertion":
|
||||
cmdOpsAssertion(args[1:])
|
||||
case "log":
|
||||
cmdOpsLog(args[1:])
|
||||
case "help", "-h", "--help":
|
||||
printOpsUsage()
|
||||
default:
|
||||
@@ -87,7 +89,16 @@ Assertion commands:
|
||||
fn ops assertion delete <id> Elimina assertion
|
||||
fn ops assertion eval --entity-id <id> Evalua assertions activas
|
||||
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 ---
|
||||
@@ -714,12 +725,12 @@ func requireRegistryDB() *registry.DB {
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
func findOpsDB() string {
|
||||
func findOpsDB() (string, error) {
|
||||
dir, _ := os.Getwd()
|
||||
for {
|
||||
path := filepath.Join(dir, opsDBName)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
return path, nil
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
@@ -727,15 +738,18 @@ func findOpsDB() string {
|
||||
}
|
||||
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 {
|
||||
path := findOpsDB()
|
||||
path, err := findOpsDB()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
db, err := ops.Open(path)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
return db
|
||||
@@ -1398,6 +1412,188 @@ func formatResultValue(v map[string]any) string {
|
||||
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 {
|
||||
// Try FN_REGISTRY_ROOT env var first
|
||||
if envRoot := os.Getenv("FN_REGISTRY_ROOT"); envRoot != "" {
|
||||
|
||||
Reference in New Issue
Block a user