save_logs: Handle malformed files better

Bug: b/307813618
Change-Id: I485436c15cb1141a2ea40f20ecf785ef60bb4c3e
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/177891
Pigweed-Auto-Submit: Rob Mohr <mohrr@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/recipe_modules/cq_deps/__init__.py b/recipe_modules/cq_deps/__init__.py
index 499f22c..9323046 100644
--- a/recipe_modules/cq_deps/__init__.py
+++ b/recipe_modules/cq_deps/__init__.py
@@ -21,7 +21,6 @@
     'fuchsia/gitiles',
     'recipe_engine/context',
     'recipe_engine/json',
-    'recipe_engine/service_account',
     'recipe_engine/step',
     'recipe_engine/url',
 ]
diff --git a/recipe_modules/environment/tests/full.expected/doctor-fail.json b/recipe_modules/environment/tests/full.expected/doctor-fail.json
index b90f934..b831cea 100644
--- a/recipe_modules/environment/tests/full.expected/doctor-fail.json
+++ b/recipe_modules/environment/tests/full.expected/doctor-fail.json
@@ -353,6 +353,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -726,6 +727,24 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/environment/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "environment.run pw_env_setup.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/environment/links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipe_modules/environment/tests/full.expected/normal.json b/recipe_modules/environment/tests/full.expected/normal.json
index 99cf8b7..d1f735e 100644
--- a/recipe_modules/environment/tests/full.expected/normal.json
+++ b/recipe_modules/environment/tests/full.expected/normal.json
@@ -584,6 +584,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -957,6 +958,24 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/environment/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "environment.run pw_env_setup.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/environment/links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipe_modules/environment/tests/full.expected/override-cas.json b/recipe_modules/environment/tests/full.expected/override-cas.json
index f0288ec..155395f 100644
--- a/recipe_modules/environment/tests/full.expected/override-cas.json
+++ b/recipe_modules/environment/tests/full.expected/override-cas.json
@@ -351,6 +351,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -724,6 +725,24 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/environment/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "environment.run pw_env_setup.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/environment/links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipe_modules/environment/tests/full.expected/override-cipd.json b/recipe_modules/environment/tests/full.expected/override-cipd.json
index dc5438c..0cad010 100644
--- a/recipe_modules/environment/tests/full.expected/override-cipd.json
+++ b/recipe_modules/environment/tests/full.expected/override-cipd.json
@@ -351,6 +351,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -724,6 +725,24 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/environment/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "environment.run pw_env_setup.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/environment/links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipe_modules/environment/tests/full.expected/windows.json b/recipe_modules/environment/tests/full.expected/windows.json
index 5f36888..5d2f6f7 100644
--- a/recipe_modules/environment/tests/full.expected/windows.json
+++ b/recipe_modules/environment/tests/full.expected/windows.json
@@ -351,6 +351,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -707,6 +708,24 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]\\environment\\foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "environment.run pw_env_setup.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]\\environment\\links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipe_modules/gerrit_comment/__init__.py b/recipe_modules/gerrit_comment/__init__.py
index 0ec9b0f..a405409 100644
--- a/recipe_modules/gerrit_comment/__init__.py
+++ b/recipe_modules/gerrit_comment/__init__.py
@@ -17,6 +17,5 @@
 DEPS = [
     'fuchsia/gerrit',
     'recipe_engine/buildbucket',
-    'recipe_engine/defer',
     'recipe_engine/step',
 ]
diff --git a/recipe_modules/pw_presubmit/api.py b/recipe_modules/pw_presubmit/api.py
index 67a8943..47eae7f 100644
--- a/recipe_modules/pw_presubmit/api.py
+++ b/recipe_modules/pw_presubmit/api.py
@@ -298,13 +298,9 @@
                 )
             if log_dir and log_dir != step.export_dir:
                 self.m.defer(
-                    self.m.file.ensure_directory,
-                    'create log dir',
-                    log_dir,
+                    self.m.file.ensure_directory, 'create log dir', log_dir,
                 )
-            self.m.defer(
-                self.m.save_logs, (step.dir,), log_dir, pres=pres
-            )
+            self.m.defer(self.m.save_logs, (step.dir,), log_dir, pres=pres)
 
             self.m.defer(
                 self.m.file.listdir, 'ls out', step.dir, recursive=True
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json b/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json
index 8c30eeb..3acb6d2 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json
@@ -476,6 +476,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_0/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_0/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_0/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_0/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_0/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_0/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -1005,6 +1006,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/program_0/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "program_0.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/program_0/links.json",
       "/path/to/tmp/"
     ],
@@ -1208,6 +1239,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/program_0/foo.log",
+      "[START_DIR]/checkout/p/program_0/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "program_0.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/program_0/links.json",
       "[START_DIR]/checkout/p/program_0/export/build_logs/links.json"
     ],
@@ -1765,6 +1825,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/program_1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2294,6 +2355,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/program_1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "program_1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/program_1/links.json",
       "/path/to/tmp/"
     ],
@@ -2497,6 +2588,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/program_1/foo.log",
+      "[START_DIR]/logs/program_1/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "program_1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/program_1/links.json",
       "[START_DIR]/logs/program_1/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json
index 8b776be..11c50a9 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json
@@ -476,6 +476,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -1005,6 +1006,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -1208,6 +1239,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "[START_DIR]/checkout/p/step1/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "[START_DIR]/checkout/p/step1/export/build_logs/links.json"
     ],
@@ -1775,6 +1835,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2304,6 +2365,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -2507,6 +2598,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "[START_DIR]/logs/step2/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "[START_DIR]/logs/step2/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json
index 436effb..a824bbd 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json
@@ -476,6 +476,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -1005,6 +1006,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -1208,6 +1239,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "[START_DIR]/checkout/p/step1/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "[START_DIR]/checkout/p/step1/export/build_logs/links.json"
     ],
@@ -1775,6 +1835,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2304,6 +2365,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -2507,6 +2598,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "[START_DIR]/logs/step2/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "[START_DIR]/logs/step2/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json
index 2994ae9..cf51d72 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json
@@ -643,6 +643,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -1172,6 +1173,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -1375,6 +1406,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "[START_DIR]/checkout/p/step1/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "[START_DIR]/checkout/p/step1/export/build_logs/links.json"
     ],
@@ -1985,6 +2045,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2514,6 +2575,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -2717,6 +2808,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "[START_DIR]/logs/step2/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "[START_DIR]/logs/step2/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json
index ddd245d..5f8c8a2 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json
@@ -476,6 +476,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -1005,6 +1006,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -1208,6 +1239,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "[START_DIR]/checkout/p/step1/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "[START_DIR]/checkout/p/step1/export/build_logs/links.json"
     ],
@@ -1775,6 +1835,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2304,6 +2365,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -2507,6 +2598,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "[START_DIR]/logs/step2/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "[START_DIR]/logs/step2/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/long.json b/recipe_modules/pw_presubmit/tests/full.expected/long.json
index 9dd10d2..9744249 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/long.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/long.json
@@ -468,6 +468,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -997,6 +998,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -1200,6 +1231,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "[START_DIR]/checkout/p/step1/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "[START_DIR]/checkout/p/step1/export/build_logs/links.json"
     ],
@@ -1759,6 +1819,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2288,6 +2349,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -2491,6 +2582,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "[START_DIR]/logs/step2/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "[START_DIR]/logs/step2/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/medium.json b/recipe_modules/pw_presubmit/tests/full.expected/medium.json
index 213e575..6a8f46a 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/medium.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/medium.json
@@ -468,6 +468,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -997,6 +998,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -1200,6 +1231,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "[START_DIR]/checkout/p/step1/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "[START_DIR]/checkout/p/step1/export/build_logs/links.json"
     ],
@@ -1759,6 +1819,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2288,6 +2349,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -2491,6 +2582,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "[START_DIR]/logs/step2/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "[START_DIR]/logs/step2/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json b/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
index c5a7003..82e2ad9 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
@@ -466,6 +466,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_0/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_0/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_0/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_0/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_0/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_0/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -995,6 +996,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/full_0/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_0.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/full_0/links.json",
       "/path/to/tmp/"
     ],
@@ -1198,6 +1229,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/full_0/foo.log",
+      "[START_DIR]/checkout/p/full_0/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_0.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/full_0/links.json",
       "[START_DIR]/checkout/p/full_0/export/build_logs/links.json"
     ],
@@ -1757,6 +1817,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/full_1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2286,6 +2347,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/full_1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/full_1/links.json",
       "/path/to/tmp/"
     ],
@@ -2489,6 +2580,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/full_1/foo.log",
+      "[START_DIR]/logs/full_1/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/full_1/links.json",
       "[START_DIR]/logs/full_1/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/step.json b/recipe_modules/pw_presubmit/tests/full.expected/step.json
index 960505d..31853cc 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/step.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/step.json
@@ -476,6 +476,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -1005,6 +1006,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -1208,6 +1239,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step1/foo.log",
+      "[START_DIR]/checkout/p/step1/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step1/links.json",
       "[START_DIR]/checkout/p/step1/export/build_logs/links.json"
     ],
@@ -1773,6 +1833,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2302,6 +2363,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -2505,6 +2596,35 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/step2/foo.log",
+      "[START_DIR]/logs/step2/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/step2/links.json",
       "[START_DIR]/logs/step2/build_logs/links.json"
     ],
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/substep.json b/recipe_modules/pw_presubmit/tests/full.expected/substep.json
index 6af6506..5a17f49 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/substep.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/substep.json
@@ -370,6 +370,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/composite/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/composite/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/composite/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/composite/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/composite/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/p/composite/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -743,6 +744,24 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/composite/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "composite.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/composite/links.json",
       "/path/to/tmp/"
     ],
@@ -886,6 +905,23 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/p/composite/foo.log",
+      "[START_DIR]/checkout/p/composite/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "name": "composite.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/p/composite/links.json",
       "[START_DIR]/checkout/p/composite/export/build_logs/links.json"
     ],
diff --git a/recipe_modules/save_logs/__init__.py b/recipe_modules/save_logs/__init__.py
index f3daa33..29f0f96 100644
--- a/recipe_modules/save_logs/__init__.py
+++ b/recipe_modules/save_logs/__init__.py
@@ -16,7 +16,6 @@
 
 DEPS = [
     'fuchsia/buildbucket_util',
-    'recipe_engine/defer',
     'recipe_engine/file',
     'recipe_engine/path',
     'recipe_engine/step',
diff --git a/recipe_modules/save_logs/api.py b/recipe_modules/save_logs/api.py
index b4d7d1e..10f0362 100644
--- a/recipe_modules/save_logs/api.py
+++ b/recipe_modules/save_logs/api.py
@@ -58,6 +58,7 @@
         self.m.path.mock_add_file(dirs[0] / '.ninja_log')
         self.m.path.mock_add_file(dirs[0] / 'coverage_reports' / 'foo.tar.gz')
         self.m.path.mock_add_file(dirs[0] / 'failure-summary.log')
+        self.m.path.mock_add_file(dirs[0] / 'foo.log')
         self.m.path.mock_add_file(dirs[0] / 'links.json')
 
         found_files: Set[config_types.Path] = set()
@@ -71,6 +72,7 @@
                             '.ninja_log',
                             'coverage_reports/foo.tar.gz',
                             'failure-summary.log',
+                            'foo.log',
                             'links.json',
                             'links.json',
                             'CMakeCache.txt',
@@ -130,14 +132,27 @@
                         {'description': 'description', 'url': 'https://url',},
                     ]
 
+                # JSON and text could have parse errors. Fall back to raw if
+                # they fail.
                 if name.endswith('.json'):
-                    read_func = self.m.file.read_json
+                    read_funcs = (self.m.file.read_json, self.m.file.read_raw)
                 elif name.endswith(('.gz', '.bz2')):
-                    read_func = self.m.file.read_raw
+                    read_funcs = (self.m.file.read_raw,)
                 else:
-                    read_func = self.m.file.read_text
+                    read_funcs = (self.m.file.read_text, self.m.file.read_raw)
 
-                contents = read_func(name, path, test_data=test_data)
+                for read_func in read_funcs:
+                    try:
+                        contents = read_func(name, path, test_data=test_data)
+                        break
+                    except Exception:
+                        contents = None
+                        # If we're changing the function to be used, it's likely
+                        # the original test_data will no longer be useful.
+                        test_data = None
+
+                if not contents:
+                    continue
 
                 if name == '.ninja_log':
                     ninja_log = contents
diff --git a/recipe_modules/save_logs/tests/full.expected/full.json b/recipe_modules/save_logs/tests/full.expected/full.json
index 217e659..db91419 100644
--- a/recipe_modules/save_logs/tests/full.expected/full.json
+++ b/recipe_modules/save_logs/tests/full.expected/full.json
@@ -4,14 +4,15 @@
     "name": "save logs",
     "~followup_annotations": [
       "@@@STEP_LINK@description@https://url@@@",
-      "@@@STEP_FAILURE@@@"
+      "@@@STEP_EXCEPTION@@@"
     ]
   },
   {
     "cmd": [],
     "name": "save logs.logs",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_EXCEPTION@@@"
     ]
   },
   {
@@ -197,6 +198,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/checkout/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -570,6 +572,41 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "save logs.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_EXCEPTION@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "save logs.logs.foo.log (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/links.json",
       "/path/to/tmp/"
     ],
@@ -713,6 +750,23 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/checkout/foo.log",
+      "[START_DIR]/export/build_logs/foo.log"
+    ],
+    "infra_step": true,
+    "name": "save logs.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/checkout/links.json",
       "[START_DIR]/export/build_logs/links.json"
     ],
diff --git a/recipe_modules/save_logs/tests/full.py b/recipe_modules/save_logs/tests/full.py
index 31aa12c..3e7747e 100644
--- a/recipe_modules/save_logs/tests/full.py
+++ b/recipe_modules/save_logs/tests/full.py
@@ -30,4 +30,4 @@
 
 
 def GenTests(api):  # pylint: disable=invalid-name
-    yield api.test('full')
+    yield api.test('full', api.step_data('save logs.logs.foo.log', retcode=1))
diff --git a/recipes/docs_builder.expected/docs-postsubmit.json b/recipes/docs_builder.expected/docs-postsubmit.json
index 8eac15f..7c638be 100644
--- a/recipes/docs_builder.expected/docs-postsubmit.json
+++ b/recipes/docs_builder.expected/docs-postsubmit.json
@@ -1440,6 +1440,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2083,6 +2084,42 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "0",
+      "BUILDBUCKET_NAME": "project:bucket:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "name": "step.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step/links.json",
       "/path/to/tmp/"
     ],
@@ -2316,6 +2353,41 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step/foo.log",
+      "[START_DIR]/co/p/step/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "0",
+      "BUILDBUCKET_NAME": "project:bucket:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "name": "step.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step/links.json",
       "[START_DIR]/co/p/step/export/build_logs/links.json"
     ],
diff --git a/recipes/docs_builder.expected/docs-presubmit.json b/recipes/docs_builder.expected/docs-presubmit.json
index fa3d329..f682b0f 100644
--- a/recipes/docs_builder.expected/docs-presubmit.json
+++ b/recipes/docs_builder.expected/docs-presubmit.json
@@ -2248,6 +2248,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -3047,6 +3048,54 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step/links.json",
       "/path/to/tmp/"
     ],
@@ -3340,6 +3389,53 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step/foo.log",
+      "[START_DIR]/co/p/step/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step/links.json",
       "[START_DIR]/co/p/step/export/build_logs/links.json"
     ],
diff --git a/recipes/envtest.expected/environment_variables.json b/recipes/envtest.expected/environment_variables.json
index 46d8937..3acb57c 100644
--- a/recipes/envtest.expected/environment_variables.json
+++ b/recipes/envtest.expected/environment_variables.json
@@ -1153,6 +1153,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -1526,6 +1527,24 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/environment/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/environment/links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipes/envtest.expected/fail.json b/recipes/envtest.expected/fail.json
index 88b5d31..bfb0a8a 100644
--- a/recipes/envtest.expected/fail.json
+++ b/recipes/envtest.expected/fail.json
@@ -1778,6 +1778,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2307,6 +2308,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/environment/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/environment/links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipes/envtest.expected/pigweed.json b/recipes/envtest.expected/pigweed.json
index 3dac3a2..ccd70b2 100644
--- a/recipes/envtest.expected/pigweed.json
+++ b/recipes/envtest.expected/pigweed.json
@@ -1775,6 +1775,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/environment/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2304,6 +2305,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/environment/foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/environment/links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipes/envtest.expected/windows.json b/recipes/envtest.expected/windows.json
index 88df07f..89ca69b 100644
--- a/recipes/envtest.expected/windows.json
+++ b/recipes/envtest.expected/windows.json
@@ -1773,6 +1773,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]\\environment\\links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2273,6 +2274,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]\\environment\\foo.log",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]\\environment\\links.json",
       "/path/to/tmp/"
     ],
diff --git a/recipes/pipeline.py b/recipes/pipeline.py
index ca27966..ec5fe9d 100644
--- a/recipes/pipeline.py
+++ b/recipes/pipeline.py
@@ -22,7 +22,6 @@
 
 DEPS = [
     'fuchsia/subbuild',
-    'recipe_engine/defer',
     'recipe_engine/properties',
     'recipe_engine/step',
 ]
diff --git a/recipes/pw_presubmit.expected/one_step_no_exit_not_in_cv.json b/recipes/pw_presubmit.expected/one_step_no_exit_not_in_cv.json
index f5e35fe..aeb8627 100644
--- a/recipes/pw_presubmit.expected/one_step_no_exit_not_in_cv.json
+++ b/recipes/pw_presubmit.expected/one_step_no_exit_not_in_cv.json
@@ -2248,6 +2248,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -3047,6 +3048,54 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -3340,6 +3389,53 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "[START_DIR]/co/p/step1/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "[START_DIR]/co/p/step1/export/build_logs/links.json"
     ],
diff --git a/recipes/pw_presubmit.expected/one_step_no_exit_not_tryjob.json b/recipes/pw_presubmit.expected/one_step_no_exit_not_tryjob.json
index 8944514..9b0f15a 100644
--- a/recipes/pw_presubmit.expected/one_step_no_exit_not_tryjob.json
+++ b/recipes/pw_presubmit.expected/one_step_no_exit_not_tryjob.json
@@ -2093,6 +2093,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2892,6 +2893,54 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -3185,6 +3234,53 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "[START_DIR]/co/p/step1/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "[START_DIR]/co/p/step1/export/build_logs/links.json"
     ],
diff --git a/recipes/pw_presubmit.expected/one_step_no_exit_passing_in_ci.json b/recipes/pw_presubmit.expected/one_step_no_exit_passing_in_ci.json
index f5e35fe..aeb8627 100644
--- a/recipes/pw_presubmit.expected/one_step_no_exit_passing_in_ci.json
+++ b/recipes/pw_presubmit.expected/one_step_no_exit_passing_in_ci.json
@@ -2248,6 +2248,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -3047,6 +3048,54 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -3340,6 +3389,53 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "[START_DIR]/co/p/step1/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "[START_DIR]/co/p/step1/export/build_logs/links.json"
     ],
diff --git a/recipes/pw_presubmit.expected/sign.json b/recipes/pw_presubmit.expected/sign.json
index 5524eb7..26b7b23 100644
--- a/recipes/pw_presubmit.expected/sign.json
+++ b/recipes/pw_presubmit.expected/sign.json
@@ -2093,6 +2093,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/release/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/release/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/release/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/release/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/release/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/release/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2892,6 +2893,54 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/release/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "release.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/release/links.json",
       "/path/to/tmp/"
     ],
@@ -3185,6 +3234,53 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/release/foo.log",
+      "[START_DIR]/co/p/release/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "release.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/release/links.json",
       "[START_DIR]/co/p/release/export/build_logs/links.json"
     ],
diff --git a/recipes/pw_presubmit.expected/two_steps.json b/recipes/pw_presubmit.expected/two_steps.json
index 02efd08..67dcaa2 100644
--- a/recipes/pw_presubmit.expected/two_steps.json
+++ b/recipes/pw_presubmit.expected/two_steps.json
@@ -2254,6 +2254,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step1/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -3053,6 +3054,54 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "/path/to/tmp/"
     ],
@@ -3346,6 +3395,53 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step1/foo.log",
+      "[START_DIR]/co/p/step1/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step1/links.json",
       "[START_DIR]/co/p/step1/export/build_logs/links.json"
     ],
@@ -4126,6 +4222,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step2/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step2/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step2/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step2/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step2/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step2/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -4925,6 +5022,54 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step2/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step2/links.json",
       "/path/to/tmp/"
     ],
@@ -5218,6 +5363,53 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step2/foo.log",
+      "[START_DIR]/co/p/step2/export/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:try:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step2/links.json",
       "[START_DIR]/co/p/step2/export/build_logs/links.json"
     ],
diff --git a/recipes/recipes.py b/recipes/recipes.py
index 745d336..7b90524 100644
--- a/recipes/recipes.py
+++ b/recipes/recipes.py
@@ -23,7 +23,6 @@
     'recipe_engine/cv',
     'recipe_engine/defer',
     'recipe_engine/properties',
-    'recipe_engine/step',
 ]
 
 PROPERTIES = InputProperties
diff --git a/recipes/target_to_cipd.expected/pw-presubmit.json b/recipes/target_to_cipd.expected/pw-presubmit.json
index c6f73f2..b416527 100644
--- a/recipes/target_to_cipd.expected/pw-presubmit.json
+++ b/recipes/target_to_cipd.expected/pw-presubmit.json
@@ -1476,6 +1476,7 @@
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/CMakeCache.txt@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/coverage_reports/foo.tar.gz@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/failure-summary.log@@@",
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/foo.log@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/links.json@@@",
       "@@@STEP_LOG_LINE@glob@[START_DIR]/co/p/step/links.json@@@",
       "@@@STEP_LOG_END@glob@@@"
@@ -2119,6 +2120,42 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step/foo.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "0",
+      "BUILDBUCKET_NAME": "project:bucket:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "name": "step.logs.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@foo.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step/links.json",
       "/path/to/tmp/"
     ],
@@ -2352,6 +2389,41 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
+      "[START_DIR]/co/p/step/foo.log",
+      "[START_DIR]/logs/build_logs/foo.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "0",
+      "BUILDBUCKET_NAME": "project:bucket:builder",
+      "BUILD_NUMBER": "0",
+      "CLICOLOR": "0",
+      "CLICOLOR_FORCE": "0",
+      "CTCACHE_DIR": "[CACHE]/clang_tidy",
+      "GOCACHE": "[CACHE]/go",
+      "NO_COLOR": "1",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_TEST_VAR": "test_value",
+      "PW_USE_COLOR": "",
+      "TEST_TMPDIR": "[CACHE]/bazel",
+      "TRIGGERING_CHANGES_JSON": "[CLEANUP]/tmp_tmp_1"
+    },
+    "infra_step": true,
+    "name": "step.copy.foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
       "[START_DIR]/co/p/step/links.json",
       "[START_DIR]/logs/build_logs/links.json"
     ],