Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions documentMD/design/error_handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ logError(projectRootDir, componentDir, "エラー:", error);
logStdout(projectRootDir, componentDir, "タスク出力:", line);
```

### notifyUser() — ユーザーが対応すべきエラーの通知

エラーの扱いは「ユーザーが何か対応しないといけないか」で分ける。

| 関数 | 用途 | クライアント表示 |
|------|------|-----------------|
| `notifyUser(projectRootDir, ...)` | ユーザーが要求した操作が失敗した/設定を直さないと先に進めない | ログ **+ トースト**(`logERR` イベント) |
| `logError()` / `logger.error()` | 記録はしたいがユーザーの対応は不要(自動リトライ中の転送、内部アサーション、廃止APIの呼び出し、コンポーネント自体に赤表示される単発のタスク失敗 等) | ログのみ |

```javascript
import { notifyUser } from "../logSettings.js";

//run/save が失敗した、リモートホストが未設定、プロジェクト名が不正 …
notifyUser(projectRootDir, "fatal error occurred while preparing phase:", err);

//プロジェクトに紐づかないもの(プロジェクト一覧/リモートホスト画面/インポートダイアログ)は "default"
notifyUser("default", "export project failed:", err);
```

- `notifyUser()` は内部で `getLogger(projectRootDir).error(...)` を呼ぶため、ログ画面(`WHEEL_LOG`)とログファイルには従来どおり出力される。
- 追加で `logERR` イベントを対象ルーム(`projectRootDir` または `default`)へ emit する。クライアントの `onLogErr`(Home / Viewer / Workflow / Remotehost 各画面)がこれを snackbar として表示する。
- トーストにはスタックトレースは載せない(`Error` は `message` のみ)。全文はログ画面/ログファイルで確認する。
- 引数は `logger.error()` と同じ(第1引数は `projectRootDir` または `"default"`)。`componentDir` は取らない。

### ログファイルの保存先

```
Expand Down
4 changes: 2 additions & 2 deletions server/app/core/dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
logInfo,
logWarn,
logError,
logFatal,
notifyUser,
_internal
} from "../logSettings.js";
import { cancelDispatchedTasks } from "./taskUtil.js";
Expand Down Expand Up @@ -383,7 +383,7 @@ class Dispatcher extends EventEmitter {
const stuckNames = Array.from(new Set(stuckPairs.map(({ stuck })=>{
return stuck.name;
}))).join(", ");
logFatal(this.projectRootDir, this.cwfDir,
notifyUser(this.projectRootDir,
`project failed: ${blockedNames} can never start because required input file(s) from ${stuckNames} were never delivered (stage-out stuck). Resolve the transfer and re-run to continue.`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion server/app/core/executerManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ function createExecuter(task, hostinfo) {
err.task = task.name;
err.useJobScheduler = task.useJobScheduler;
err.hostinfo = hostinfo;
loggerWrapper.logError(task.projectRootDir, task.workingDir, err);
loggerWrapper.notifyUser(task.projectRootDir, `[${task.name}] job scheduler "${hostinfo.jobScheduler}" of host "${task.host}" is not defined in jobScheduler.json`);
throw err;
}
if (onRemote) {
Expand Down
10 changes: 5 additions & 5 deletions server/app/core/projectOperations.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import fs from "fs-extra";
import path from "path";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
import { projectList, defaultCleanupRemoteRoot, projectJsonFilename, componentJsonFilename, suffix } from "../db/db.js";
import { getDateString, writeJsonWrapper, isValidName, removeTrailingPathSep } from "../lib/utility.js";
import { convertPathSep } from "./pathUtils.js";
Expand Down Expand Up @@ -110,7 +110,7 @@ export async function addProject(projectDir, description) {

const projectName = path.basename(projectRootDir.slice(0, -suffix.length));
if (!_internal.isValidName(projectName)) {
_internal.getLogger().error(projectName, "is not allowed for project name");
notifyUser("default", projectName, "is not allowed for project name");
throw (new Error("illegal project name"));
}
projectRootDir = await _internal.createNewProject(projectRootDir, projectName, description, "wheel", "wheel@example.com");
Expand All @@ -126,12 +126,12 @@ export async function addProject(projectDir, description) {
export async function renameProject(id, argNewName, oldDir) {
const newName = argNewName.endsWith(suffix) ? argNewName.slice(0, -suffix.length) : argNewName;
if (!_internal.isValidName(newName)) {
_internal.getLogger().error(newName, "is not allowed for project name");
notifyUser("default", newName, "is not allowed for project name");
throw (new Error("illegal project name"));
}
const newDir = path.resolve(path.dirname(oldDir), `${newName}${suffix}`);
if (await _internal.fs.pathExists(newDir)) {
_internal.getLogger().error(newName, "directory is already exists");
notifyUser("default", newName, "directory is already exists");
throw (new Error("already exists"));
}

Expand Down Expand Up @@ -190,7 +190,7 @@ export async function readProject(projectRootDir) {
await _internal.gitAdd(projectRootDir, "./");
await _internal.gitCommit(projectRootDir, "import project");
} catch (e) {
_internal.getLogger().error("can not access to git repository", e);
notifyUser("default", "can not access to git repository", e);
return null;
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion server/app/db/version.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version": "2026-0903-113554-beta" }
{"version": "2026-0910-132440-beta" }
6 changes: 3 additions & 3 deletions server/app/handlers/componentArchive.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License in the project root for the license information.
*/
"use strict";
import { getLogger } from "../logSettings.js";
import { notifyUser } from "../logSettings.js";
import { exportComponent } from "../core/exportComponent.js";
import { importComponent } from "../core/importComponent.js";

Expand All @@ -19,7 +19,7 @@ async function onExportComponent(projectRootDir, componentID, cb) {
const url = await exportComponent(projectRootDir, componentID);
cb(url);
} catch (e) {
getLogger(projectRootDir).error("export component failed", e);
notifyUser(projectRootDir, "export component failed", e);
cb(e);
}
}
Expand All @@ -41,7 +41,7 @@ async function onImportComponent(archiveFile, projectRootDir, targetParentID, po
}
return newComponentID;
} catch (e) {
getLogger(projectRootDir).error("import component failed", e);
notifyUser(projectRootDir, "import component failed", e);
if (typeof cb === "function") {
cb(e);
}
Expand Down
16 changes: 8 additions & 8 deletions server/app/handlers/fileManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { getUnusedPath } from "../core/fileUtils.js";
import { deliverFile } from "../core/deliverFile.js";
import { escapeRegExp } from "../lib/utility.js";
import fileBrowser from "../core/fileBrowser.js";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
import { gitLFSSize, projectJsonFilename, componentJsonFilename, rootDir, remoteHost, logFilename } from "../db/db.js";
import { baseURL } from "../core/global.js";
import { emitAll } from "./commUtils.js";
Expand Down Expand Up @@ -120,7 +120,7 @@ export async function onCreateNewFile(projectRootDir, argFilename, cb) {
await gitAdd(projectRootDir, filename);
}
} catch (e) {
getLogger(projectRootDir).error(projectRootDir, "create new file failed", e);
notifyUser(projectRootDir, "create new file failed", e);
cb(null);
return;
}
Expand All @@ -142,7 +142,7 @@ export async function onCreateNewDir(projectRootDir, argDirname, cb) {
await gitAdd(projectRootDir, path.resolve(dirname, ".gitkeep"));
}
} catch (e) {
getLogger(projectRootDir).error(projectRootDir, "create new directory failed", e);
notifyUser(projectRootDir, "create new directory failed", e);
cb(null);
return;
}
Expand Down Expand Up @@ -186,7 +186,7 @@ export async function onRenameFile(projectRootDir, parentDir, argOldName, argNew
return;
}
if (await fs.pathExists(newName)) {
getLogger(projectRootDir).error(newName, "is already exists");
notifyUser(projectRootDir, newName, "is already exists");
cb(false);
return;
}
Expand Down Expand Up @@ -216,7 +216,7 @@ export async function onRenameFile(projectRootDir, parentDir, argOldName, argNew
err.path = parentDir;
err.oldName = oldName;
err.newName = newName;
getLogger(projectRootDir).error("rename failed", err);
notifyUser(projectRootDir, "rename failed", err);
cb(false);
return;
}
Expand All @@ -243,7 +243,7 @@ export async function onCommitFiles(projectRootDir, files, cb) {
});
await gitCommit(projectRootDir, undefined, filenames);
} catch (err) {
getLogger(projectRootDir).error("commit files failed", err);
notifyUser(projectRootDir, "commit files failed", err);
cb(false);
return;
}
Expand All @@ -258,7 +258,7 @@ export async function onCommitFiles(projectRootDir, files, cb) {
export async function onUploadFileSaved(event, socket) {
const projectRootDir = event.file.meta.projectRootDir;
if (!event.file.success) {
getLogger(projectRootDir).error("file upload failed", event.file.name);
notifyUser(projectRootDir, "file upload failed", event.file.name);
return;
}
const uploadDir = path.resolve(projectRootDir, event.file.meta.currentDir);
Expand Down Expand Up @@ -457,7 +457,7 @@ export async function onDownloadFullLog(projectRootDir, cb) {
getLogger(projectRootDir).info("Debug log archive is ready for download", url);
cb(url);
} catch (e) {
getLogger(projectRootDir).error("Failed to create debug log archive", e);
notifyUser(projectRootDir, "Failed to create debug log archive", e);
cb(null);
}
};
4 changes: 2 additions & 2 deletions server/app/handlers/fileManager2.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"use strict";
import path from "node:path";
import fs from "fs-extra";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
import { emitAll } from "./commUtils.js";

/**
Expand All @@ -20,7 +20,7 @@ import { emitAll } from "./commUtils.js";
*/
async function onUploadFileSaved2(event) {
if (!event.file.success) {
getLogger().error("file upload failed", event.file.name);
notifyUser("default", "file upload failed", event.file.name);
return;
}
const fileSizeMB = parseInt(event.file.size / 1024 / 1024, 10);
Expand Down
6 changes: 3 additions & 3 deletions server/app/handlers/projectArchive.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License in the project root for the license information.
*/
"use strict";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
import { exportProject } from "../core/exportProject.js";
import { importProject, importProjectFromGitRepository } from "../core/importProject.js";

Expand All @@ -15,7 +15,7 @@ async function onImportProject(clientID, target, parentDir, isURL, cb) {
cb(projectRootDir);
} catch (e) {
if (e.reason !== "CANCELED") {
getLogger("default").error(`${e.message}`);
notifyUser("default", `${e.message}`);
} else {
getLogger("default").debug("user canceled importing project:", target);
}
Expand All @@ -28,7 +28,7 @@ async function onExportProject(projectRootDir, name, mail, memo, cb) {
const url = await exportProject(projectRootDir, name, mail, memo);
cb(url);
} catch (e) {
getLogger("default").error("export project failed:", e);
notifyUser("default", "export project failed:", e);
cb(false);
}
}
Expand Down
16 changes: 8 additions & 8 deletions server/app/handlers/projectController.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import axios from "axios";
import { glob } from "glob";
import fs from "fs-extra";
import SBS from "simple-batch-system";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
import { filesJsonFilename, remoteHost, componentJsonFilename, projectJsonFilename } from "../db/db.js";
import { deliverFile } from "../core/deliverFile.js";
import { gitAdd, gitCommit, gitResetHEAD, gitClean, gitPromise, getUnsavedFiles } from "../core/gitOperator2.js";
Expand Down Expand Up @@ -221,7 +221,7 @@ async function runValidationPhase(projectRootDir, ack, commitMessage) {
await gitCommit(projectRootDir, commitMessage);
return true;
} catch (err) {
getLogger(projectRootDir).error("fatal error occurred while validation phase:", err);
notifyUser(projectRootDir, "fatal error occurred while validation phase:", err);
ack(err);
return false;
} finally {
Expand Down Expand Up @@ -322,9 +322,9 @@ async function runDispatcher(clientID, projectRootDir, ack) {
if (err.reason === "CANCELED") {
getLogger(projectRootDir).debug(err.message);
} else if (err.reason === "invalidRemoteStorage") {
getLogger(projectRootDir).error(`you do not have write permission to ${err.storagePath} on ${err.host}`);
notifyUser(projectRootDir, `you do not have write permission to ${err.storagePath} on ${err.host}`);
} else {
getLogger(projectRootDir).error("fatal error occurred while preparing phase:", err);
notifyUser(projectRootDir, "fatal error occurred while preparing phase:", err);
}
removeSsh(projectRootDir);
removeAllJWTServerPassphrase(projectRootDir);
Expand Down Expand Up @@ -391,7 +391,7 @@ async function runDispatcher(clientID, projectRootDir, ack) {
await runProject(projectRootDir);
await _internal.unlockIfFinished(projectRootDir);
} catch (err) {
getLogger(projectRootDir).error("fatal error occurred while parsing workflow:", err);
notifyUser(projectRootDir, "fatal error occurred while parsing workflow:", err);
await updateProjectState(projectRootDir, "failed");
ack(err);
} finally {
Expand Down Expand Up @@ -563,11 +563,11 @@ async function onSaveProject(projectRootDir, ack) {
const projectJson = await getProjectJson(projectRootDir);
const { readOnly, state: projectState } = projectJson;
if (readOnly) {
getLogger(projectRootDir).error("readOnly project can not be saved", projectRootDir);
notifyUser(projectRootDir, "readOnly project can not be saved");
return ack(new Error("project is read-only"));
}
if (!allowedOperations[projectState].includes("saveProject")) {
getLogger(projectRootDir).error(projectState, "project can not be saved", projectRootDir);
notifyUser(projectRootDir, projectState, "project can not be saved");
return ack(new Error(`${projectState} project is not allowed to save`));
}
if (projectJson.exportInfo && projectJson.exportInfo.notChanged) {
Expand Down Expand Up @@ -619,7 +619,7 @@ async function projectOperator({ clientID, projectRootDir, ack, operation }) {
break;
}
} catch (e) {
getLogger(projectRootDir).error(`${operation} failed`, e);
notifyUser(projectRootDir, `${operation} failed`, e);
ack(e);
} finally {
if (operation !== "runProject") {
Expand Down
8 changes: 4 additions & 4 deletions server/app/handlers/registerHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
import { onAddJobScriptTemplate, onUpdateJobScriptTemplate, onRemoveJobScriptTemplate, onGetJobScriptTemplates } from "./jobScript.js";
import { onGetResultFiles } from "./resultFiles.js";
import { sendTaskStateList, sendComponentTree, sendWorkflow, sendProjectJson } from "./senders.js";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
import {
onCreateNewRemoteFile,
onCreateNewRemoteDir,
Expand Down Expand Up @@ -120,7 +120,7 @@ const registerHandlers = (socket, Siofu)=>{
//Handle component import uploads specially
if (event.file.meta.isComponentImport) {
if (!event.file.success) {
getLogger(event.file.meta.projectRootDir).error("component import upload failed", event.file.name);
notifyUser(event.file.meta.projectRootDir, "component import upload failed", event.file.name);
return;
}
//Call importComponent with the uploaded file path
Expand All @@ -138,7 +138,7 @@ const registerHandlers = (socket, Siofu)=>{
const parentDir = await getComponentDir(projectRootDir, targetParentID, true);
await sendWorkflow(null, projectRootDir, parentDir);
} catch (e) {
getLogger(projectRootDir).error("component import failed", e);
notifyUser(projectRootDir, "component import failed", e);
}
return;
}
Expand All @@ -149,7 +149,7 @@ const registerHandlers = (socket, Siofu)=>{
});
uploader.on("error", (event)=>{
const projectRootDir = event.file.meta.projectRootDir;
getLogger(projectRootDir).error("file upload failed", event.file, event.error);
notifyUser(projectRootDir, "file upload failed", event.file && event.file.name, event.error);
});
//create
socket.on("createNewFile", onCreateNewFile);
Expand Down
6 changes: 3 additions & 3 deletions server/app/handlers/remoteFileBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import path from "path";
import fs from "fs-extra";
import { readComponentJsonByID } from "../core/componentJsonIO.js";
import { remoteHost } from "../db/db.js";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
import { createSsh, getSsh, askPassword } from "../core/sshManager.js";
import { createTempd } from "../core/tempd.js";
import { hasRemoteFileBrowser, hasGfarmTarBrowser } from "../../../common/checkComponent.js";
Expand Down Expand Up @@ -159,7 +159,7 @@ async function onRemoteDownload(projectRootDir, target, host, cb) {
getLogger(projectRootDir).debug("Download url is ready", url);
return cb(url);
} catch (e) {
getLogger(projectRootDir).error("fetch download file failed", e);
notifyUser(projectRootDir, "fetch download file failed", e);
return cb(null);
}
}
Expand Down Expand Up @@ -190,7 +190,7 @@ async function gfarmFileUtilWrapper(func, projectRootDir, ...args) {
const host = args.pop();
const hostID = remoteHost.getID("name", host);
if (!hostID) {
getLogger(projectRootDir).error(`${host} not found in remotehost settings`);
notifyUser(projectRootDir, `${host} not found in remotehost settings`);
cb(false);
}
try {
Expand Down
4 changes: 2 additions & 2 deletions server/app/handlers/tryToConnect.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
"use strict";
import SshClientWrapper from "ssh-client-wrapper";
import { getLogger } from "../logSettings.js";
import { getLogger, notifyUser } from "../logSettings.js";
const logger = getLogger();
import { remoteHost, verboseSsh } from "../db/db.js";
import { askPassword } from "../core/sshManager.js";
Expand All @@ -32,7 +32,7 @@ async function onTryToConnect(clientID, hostInfo, cb) {
logger.info("tryToConnect canceled by user");
return cb("canceled");
}
logger.error("tryToConnect failed with", err);
notifyUser("default", "connection test failed:", err);
return cb(err);
}
ssh.disconnect();
Expand Down
Loading
Loading