diff --git a/.gitignore b/.gitignore
index c124775b567cb73187c712ce6716ef5ba27e2a13..637f049bf560872d04b147cc4d77c75ed445e4f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,7 @@ src/002-config/fc-version.js.commitHash.js
 devTools/scripts/.package.json.diff
 /settings.json
 .eslintcache
+/sanityCheck.log
 
 # macOS devices
 *.icloud
diff --git a/cspell.json b/cspell.json
index d3e07f562172ba17a9f88936d9b0a484165139d5..0e68a3a648e4e8ceaa4586e766e0d89c34ad7e7f 100644
--- a/cspell.json
+++ b/cspell.json
@@ -38,6 +38,7 @@
         "*.svg",
         "*.sh",
         "*.bat",
+        ".gitignore",
         "# Below ignored until properly cleaned up #",
         "src/npc/children",
         "src/npc/infants"
diff --git a/devTools/scripts/sanityCheck.js b/devTools/scripts/sanityCheck.js
index 60a35b859fe45e7ff91f614e5a9b8fce138457b5..21a24b60982098a594e380f51dc53c152f1a1800 100644
--- a/devTools/scripts/sanityCheck.js
+++ b/devTools/scripts/sanityCheck.js
@@ -4,6 +4,7 @@ import {ESLint} from "eslint";
 import yargs from "yargs";
 import {hideBin} from "yargs/helpers";
 import {execSync} from 'child_process';
+import stripAnsi from "strip-ansi";
 
 import customChecks from "./extraChecks.js";
 import spellingChecks from "./spellingChecks.js";
@@ -29,6 +30,31 @@ execSync("node devTools/scripts/setup.js --settings");
 /** @type {import("./setup.js").Settings} */
 const settings = jetpack.read("settings.json", "json");
 
+// move old log to sanityCheck.log.bak
+if (jetpack.exists("sanityCheck.log") === "file") {
+	jetpack.move("sanityCheck.log", "sanityCheck.log.bak", {overwrite: true});
+}
+
+// write new log starting with the current date and time
+jetpack.write("sanityCheck.log", new Date().toLocaleString() + "\n\n", {atomic: true});
+
+/**
+ * Adds a message to the log file, also prints the message to the console by default
+ * @param {string} message message to log
+ * @param {boolean} [print] if true (default) then print message to console
+ */
+function log(message, print = true) {
+	// if message doesn't end with `\n` then add it
+	if (!message.endsWith("\n")) {
+		message += "\n";
+	}
+	if (print === true) {
+		console.log(message);
+	}
+	// strip color codes and save to log file
+	jetpack.append("sanityCheck.log", stripAnsi(message));
+}
+
 const eslint = new ESLint({
 	cache: true,
 	cacheStrategy: "content"
@@ -45,10 +71,13 @@ let typescriptProblems = [];
 
 let stagedFiles = undefined;
 
+// add state of staged flag to log
+log(`args.staged: ${args.staged}`, false);
+
 if (args.staged === true) {
 	// get staged files
 	stagedFiles = parser.stagedFiles();
-	if (stagedFiles === null || stagedFiles.length === 0) {
+	if (stagedFiles === null) {
 		stagedFiles = undefined;
 	}
 } else {
@@ -56,6 +85,16 @@ if (args.staged === true) {
 	await parser.init();
 }
 
+if (stagedFiles !== undefined) {
+	// if no files in stagedFiles
+	if (stagedFiles.length === 0) {
+		log("No staged files found for processing!");
+		process.exit(2);
+	}
+	// add list of staged files to log
+	log(`staged files: ${JSON.stringify(stagedFiles, undefined, "\t")}`, false);
+}
+
 if (settings.checksEnableExtras === true) {
 	customProblems = customChecks(settings.checksOnlyChangedExtras, stagedFiles, parser);
 }
@@ -72,78 +111,83 @@ if (settings.checksEnableTypescript === true) {
 }
 
 if (customProblems.length > 0) {
-	console.log("");
-	console.log(customProblems.join("\n"));
+	log("=".repeat(20) + "Custom problems" + "=".repeat(20));
+	log("");
+	log(customProblems.join("\n"));
 }
 if (spellingProblems.length > 0) {
-	console.log("");
-	console.log(spellingProblems.join("\n"));
+	log("=".repeat(20) + "Spelling problems" + "=".repeat(20));
+	log("");
+	log(spellingProblems.join("\n"));
 }
 if (eslintProblems.length > 0) {
-	console.log("");
+	log("=".repeat(20) + "Javascript linting problems" + "=".repeat(20));
+	log("");
 	// @ts-ignore
 	const formatter = await eslint.loadFormatter("stylish");
-	const reportText = formatter.format(eslintProblems);
-	console.log(reportText);
+	// @ts-ignore
+	const reportText = await formatter.format(eslintProblems);
+	log(reportText);
 }
 if (typescriptProblems.length > 0) {
-	console.log("");
-	console.log(typescriptProblems.join("\n"));
+	log("=".repeat(20) + "Javascript type problems" + "=".repeat(20));
+	log("");
+	log(typescriptProblems.join("\n"));
 }
 
-console.log("=".repeat(60));
+log("=".repeat(60));
 
 let skippedChecks = 0;
 
 // TODO:@franklygeorge color the messages below
 if (customProblems.length > 0) {
-	console.log(`Custom sanity checks found ${customProblems.length} issues.`);
+	log(`Custom sanity checks found ${customProblems.length} issues.`);
 } else if (settings.checksEnableExtras === true) {
-	console.log("Custom sanity checks found no issues.");
+	log("Custom sanity checks found no issues.");
 } else {
-	console.log("Custom sanity checks are currently disabled.");
+	log("Custom sanity checks are currently disabled.");
 	skippedChecks += 1;
 }
 
 if (spellingProblems.length > 0) {
-	console.log(`cSpell found ${spellingProblems.length} spelling issues.`);
+	log(`cSpell found ${spellingProblems.length} spelling issues.`);
 } else if (settings.checksEnableSpelling === true) {
-	console.log("cSpell found no spelling issues.");
+	log("cSpell found no spelling issues.");
 } else {
-	console.log("Spelling checks using cSpell are currently disabled.");
+	log("Spelling checks using cSpell are currently disabled.");
 	skippedChecks += 1;
 }
 
 if (eslintProblems.length > 0) {
-	console.log(`ESLint found ${eslintProblems.length} linting issues.`);
+	log(`ESLint found ${eslintProblems.length} linting issues.`);
 } else if (settings.checksEnableESLint === true) {
-	console.log(`ESLint found no linting issues.`);
+	log(`ESLint found no linting issues.`);
 } else {
-	console.log("Linting using ESLint is currently disabled.");
+	log("Linting using ESLint is currently disabled.");
 	skippedChecks += 1;
 }
 
 if (typescriptProblems.length > 0) {
-	console.log(`The TypeScript compiler found ${typescriptProblems.length} type issues.`);
+	log(`The TypeScript compiler found ${typescriptProblems.length} type issues.`);
 } else if (settings.checksEnableTypescript === true) {
-	console.log(`The Typescript compiler found no type issues.`);
+	log(`The Typescript compiler found no type issues.`);
 } else {
-	console.log("Type checking using the Typescript compiler is currently disabled.");
+	log("Type checking using the Typescript compiler is currently disabled.");
 	skippedChecks += 1;
 }
 
 const issueCount = customProblems.length + spellingProblems.length + eslintProblems.length + typescriptProblems.length;
 
-console.log("");
-console.log(`${issueCount} issues found.${(skippedChecks > 0) ? ` ${skippedChecks} sanity checker(s) skipped.`: ""}`);
+log("");
+log(`${issueCount} issues found.${(skippedChecks > 0) ? ` ${skippedChecks} sanity checker(s) skipped.`: ""}`);
 
-console.log("=".repeat(60));
+log("=".repeat(60));
 
 // exclude eslint and typescript problems from making git pre-commit hook fail
 // we may change this in the future
 if ((issueCount - (eslintProblems.length + typescriptProblems.length)) > 0) {
 	if (args.staged === true) {
-		console.log(`You can temporarily disable the pre-commit hook by changing 'Edit Sanity Check Settings' -> 'Running sanity checks before commiting' in "setup.${process.platform === "win32" ? "bat": "sh"}" to 'Sanity checks are temporarily disabled...'`);
+		log(`You can temporarily disable the pre-commit hook by changing 'Edit Sanity Check Settings' -> 'Running sanity checks before commiting' in "setup.${process.platform === "win32" ? "bat": "sh"}" to 'Sanity checks are temporarily disabled...'`);
 	}
 	process.exit(1);
 }