From 98644be7ea161f882de74874a87145f79e06cafd Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Wed, 20 May 2026 14:33:08 -0400 Subject: [PATCH] feat: add structured logging to import pipeline Logs each stage of the import with [roux] prefix: request URL, script path, exit codes, stderr output, JSON parse status, conversion status, and file write path. Error messages now include the attempted URL for easier debugging. --- src/Roux/Server.hs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Roux/Server.hs b/src/Roux/Server.hs index 259bddd..52db74b 100644 --- a/src/Roux/Server.hs +++ b/src/Roux/Server.hs @@ -196,6 +196,7 @@ importHandler recipeDir _state request respond = if T.null url then respond (htmlResponse (Html.importPage (Just ("URL is required" :: Text)))) else do + putStrLn $ "[roux] import request for " <> T.unpack url result <- try $ runImportPipeline recipeDir url case result of Left (e :: SomeException) -> @@ -240,50 +241,56 @@ findScrapeScript = do -- | Run the full import pipeline: scrape -> parse -> convert -> render -> save. runImportPipeline :: FilePath -> Text -> IO (Either Html.ImportError FilePath) runImportPipeline recipeDir url = do - -- Step 1: scrape the URL + -- Step 1: locate and run the scraper script <- findScrapeScript + putStrLn $ "[roux] importing from " <> T.unpack url <> " using script " <> script (exitCode, stdout, stderr) <- readProcessWithExitCode script ["--pretty", T.unpack url] "" case exitCode of - ExitFailure _ -> + ExitFailure _ -> do + putStrLn $ "[roux] scrape-recipe exited with code " <> show exitCode <> " for " <> T.unpack url + putStrLn $ "[roux] scrape-recipe stderr: " <> stderr return $ Left $ Html.ImportError $ - "Could not scrape URL. The scraper exited with: " <> T.pack stderr + "Could not scrape URL (" <> url <> "). The scraper exited with: " <> T.pack stderr ExitSuccess -> do -- Step 2: decode stdout (String) as JSON Value - -- stdout from readProcessWithExitCode is a locale-decoded String; - -- re-encode to UTF-8 for JSON decoding. let jsonBytes = LB.fromStrict (TE.encodeUtf8 (T.pack stdout)) jsonVal = A.decode jsonBytes :: Maybe A.Value case jsonVal of - Nothing -> + Nothing -> do + putStrLn $ "[roux] failed to decode JSON from scrape-recipe stdout for " <> T.unpack url return $ Left $ Html.ImportError $ - "Could not parse JSON from scraper output." + "Could not parse JSON from scraper output (" <> url <> ")." Just val -> case parseSchemaOrgRecipe val of - Nothing -> + Nothing -> do + putStrLn $ "[roux] no schema.org Recipe found in JSON for " <> T.unpack url return $ Left $ Html.ImportError $ - "Could not parse recipe data. The page may not have schema.org recipe data." + "Could not parse recipe data from " <> url <> ". The page may not have schema.org recipe data." Just schema -> case schemaOrgToCooklang schema of - Left err -> + Left err -> do + putStrLn $ "[roux] failed to convert schema.org recipe for " <> T.unpack url <> ": " <> T.unpack (T.pack err) return $ Left $ Html.ImportError $ - "Could not convert recipe: " <> T.pack err + "Could not convert recipe from " <> url <> ": " <> T.pack err Right recipe -> do -- Step 3: render to Cooklang text let cookText = CooklangPrint.renderRecipe recipe filename = CooklangPrint.filenameFromTitle (soName schema) filepath = recipeDir filename -- Step 4: write the .cook file + putStrLn $ "[roux] writing imported recipe to " <> filepath BS.writeFile filepath (TE.encodeUtf8 cookText) + putStrLn $ "[roux] successfully imported " <> T.unpack url <> " as " <> filename return $ Right filename {- | Watch a directory for .cook file changes and update the shared state.