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 if T.null url
then respond (htmlResponse (Html.importPage (Just ("URL is required" :: Text)))) then respond (htmlResponse (Html.importPage (Just ("URL is required" :: Text))))
else do else do
putStrLn $ "[roux] import request for " <> T.unpack url
result <- try $ runImportPipeline recipeDir url result <- try $ runImportPipeline recipeDir url
case result of case result of
Left (e :: SomeException) -> Left (e :: SomeException) ->
@@ -240,50 +241,56 @@ findScrapeScript = do
-- | Run the full import pipeline: scrape -> parse -> convert -> render -> save. -- | Run the full import pipeline: scrape -> parse -> convert -> render -> save.
runImportPipeline :: FilePath -> Text -> IO (Either Html.ImportError FilePath) runImportPipeline :: FilePath -> Text -> IO (Either Html.ImportError FilePath)
runImportPipeline recipeDir url = do runImportPipeline recipeDir url = do
-- Step 1: scrape the URL -- Step 1: locate and run the scraper
script <- findScrapeScript script <- findScrapeScript
putStrLn $ "[roux] importing from " <> T.unpack url <> " using script " <> script
(exitCode, stdout, stderr) <- (exitCode, stdout, stderr) <-
readProcessWithExitCode readProcessWithExitCode
script script
["--pretty", T.unpack url] ["--pretty", T.unpack url]
"" ""
case exitCode of 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 $ return $
Left $ Left $
Html.ImportError $ 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 ExitSuccess -> do
-- Step 2: decode stdout (String) as JSON Value -- 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)) let jsonBytes = LB.fromStrict (TE.encodeUtf8 (T.pack stdout))
jsonVal = A.decode jsonBytes :: Maybe A.Value jsonVal = A.decode jsonBytes :: Maybe A.Value
case jsonVal of case jsonVal of
Nothing -> Nothing -> do
putStrLn $ "[roux] failed to decode JSON from scrape-recipe stdout for " <> T.unpack url
return $ return $
Left $ Left $
Html.ImportError $ Html.ImportError $
"Could not parse JSON from scraper output." "Could not parse JSON from scraper output (" <> url <> ")."
Just val -> case parseSchemaOrgRecipe val of Just val -> case parseSchemaOrgRecipe val of
Nothing -> Nothing -> do
putStrLn $ "[roux] no schema.org Recipe found in JSON for " <> T.unpack url
return $ return $
Left $ Left $
Html.ImportError $ 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 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 $ return $
Left $ Left $
Html.ImportError $ Html.ImportError $
"Could not convert recipe: " <> T.pack err "Could not convert recipe from " <> url <> ": " <> T.pack err
Right recipe -> do Right recipe -> do
-- Step 3: render to Cooklang text -- Step 3: render to Cooklang text
let cookText = CooklangPrint.renderRecipe recipe let cookText = CooklangPrint.renderRecipe recipe
filename = CooklangPrint.filenameFromTitle (soName schema) filename = CooklangPrint.filenameFromTitle (soName schema)
filepath = recipeDir </> filename filepath = recipeDir </> filename
-- Step 4: write the .cook file -- Step 4: write the .cook file
putStrLn $ "[roux] writing imported recipe to " <> filepath
BS.writeFile filepath (TE.encodeUtf8 cookText) BS.writeFile filepath (TE.encodeUtf8 cookText)
putStrLn $ "[roux] successfully imported " <> T.unpack url <> " as " <> filename
return $ Right filename return $ Right filename
{- | Watch a directory for .cook file changes and update the shared state. {- | Watch a directory for .cook file changes and update the shared state.