feat: add structured logging to import pipeline
Build and Deploy / build-and-deploy (push) Successful in 1m17s

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.
This commit is contained in:
2026-05-20 14:33:08 -04:00
parent 29952d456c
commit 98644be7ea
+18 -11
View File
@@ -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.