From 07643d045088d8d279e6d6f5e25de73f7a05bd4b Mon Sep 17 00:00:00 2001 From: HeroBlackInk Date: Fri, 20 Mar 2026 01:46:39 +0800 Subject: [PATCH] fix: add logging to 22 silent early returns across sync pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, many guard conditions in the sync pipeline returned silently with zero feedback, making it impossible to diagnose why operations were dropped (root cause of issues #109, #111). Changes: - syncLock: log timeout failures in waitForRelease/waitForSaveRelease and acquire/acquireExclusive (console.warn + debugLog + logOperation) - toTodoist: log isTaskSyncEnabled skips on user actions (edit/close/reopen), isPrimaryDevice guard in lastLineNewTaskCheck, and parser failures - toObsidian: log missing mapping/file/taskLine in syncSingleTaskToObsidian - eventHandlers: log apiInitialized guards in all 5 event handlers - scheduler: log lock acquisition failures during file sync New logOperation event types: SYNC_LOCK_TIMEOUT, SYNC_DISABLED_SKIP, SYNC_TARGET_MISSING, TASK_PARSE_FAILED No logic changes, no Notice additions — logging only. --- src/plugin/eventHandlers.ts | 25 ++++++++++++++++++++----- src/sync/scheduler.ts | 5 ++++- src/sync/syncLock.ts | 28 ++++++++++++++++++++++++---- src/sync/toObsidian.ts | 21 ++++++++++++++++++--- src/sync/toTodoist.ts | 29 +++++++++++++++++++++++++---- 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/src/plugin/eventHandlers.ts b/src/plugin/eventHandlers.ts index 50e4514..f891186 100644 --- a/src/plugin/eventHandlers.ts +++ b/src/plugin/eventHandlers.ts @@ -18,7 +18,10 @@ export class EventHandlers { } private async onKeyUp(evt: KeyboardEvent): Promise { - if (!this.plugin.settings.apiInitialized) return; + if (!this.plugin.settings.apiInitialized) { + this.plugin.debugLog('[onKeyUp] API not initialized, skipping'); + return; + } if (!(this.plugin.app.workspace.activeEditor?.editor?.hasFocus())) { this.plugin.debugLog(`editor is not focused`); return; @@ -46,7 +49,10 @@ export class EventHandlers { } private async onClick(evt: MouseEvent): Promise { - if (!this.plugin.settings.apiInitialized) return; + if (!this.plugin.settings.apiInitialized) { + this.plugin.debugLog('[onClick] API not initialized, skipping'); + return; + } if (this.plugin.app.workspace.activeEditor?.editor?.hasFocus()) { this.lineNumberCheck(); @@ -60,7 +66,10 @@ export class EventHandlers { } private async onEditorChange(editor: Editor, view: MarkdownView): Promise { - if (!this.plugin.settings.apiInitialized) return; + if (!this.plugin.settings.apiInitialized) { + this.plugin.debugLog('[onEditorChange] API not initialized, skipping'); + return; + } if (this.plugin.isSyncingFromTodoist) return; this.lineNumberCheck(); if (!await this.plugin.checkModuleClass()) return; @@ -75,7 +84,10 @@ export class EventHandlers { } private async onFileRename(file: TFile, oldpath: string): Promise { - if (!this.plugin.settings.apiInitialized) return; + if (!this.plugin.settings.apiInitialized) { + this.plugin.debugLog('[onFileRename] API not initialized, skipping'); + return; + } this.plugin.debugLog(`${oldpath} is renamed`); const taskCount = this.plugin.cacheOperation!.getTaskCountInFile(oldpath); @@ -99,7 +111,10 @@ export class EventHandlers { } private async onFileModify(file: TFile): Promise { - if (!this.plugin.settings.apiInitialized) return; + if (!this.plugin.settings.apiInitialized) { + this.plugin.debugLog('[onFileModify] API not initialized, skipping'); + return; + } if (this.plugin.isSyncingFromTodoist) return; if (this.plugin.isProcessingModify) return; if (!file.path.endsWith('.md')) return; diff --git a/src/sync/scheduler.ts b/src/sync/scheduler.ts index f068b14..6901330 100644 --- a/src/sync/scheduler.ts +++ b/src/sync/scheduler.ts @@ -53,7 +53,10 @@ export class SyncScheduler { const lockOk = await this.plugin.syncLockManager.run('obsidianToTodoist', async () => { await this.plugin.obsidianToTodoist!.fullTextNewTaskCheck(fileKey); }); - if (!lockOk) continue; + if (!lockOk) { + this.plugin.debugLog(`[Scheduler] Skipping file sync for ${fileKey}: lock not acquired`); + continue; + } await this.plugin.syncLockManager.run('obsidianToTodoist', async () => { await this.plugin.obsidianToTodoist!.deletedTaskCheck(fileKey); diff --git a/src/sync/syncLock.ts b/src/sync/syncLock.ts index 60c1fc8..4213b76 100644 --- a/src/sync/syncLock.ts +++ b/src/sync/syncLock.ts @@ -25,7 +25,13 @@ export class SyncLockManager { await new Promise(resolve => setTimeout(resolve, 1000)); attempts++; } - return !this.locked; + const released = !this.locked; + if (!released) { + console.warn('[SyncLock] Sync lock acquire FAILED after 10s timeout'); + this.plugin.debugLog('[SyncLock] Sync lock acquire FAILED after 10s timeout'); + this.plugin.logOperation?.log('SYNC_LOCK_TIMEOUT', 'Sync lock acquire failed after 10s timeout'); + } + return released; } async waitForSaveRelease(): Promise { @@ -34,7 +40,13 @@ export class SyncLockManager { await new Promise(resolve => setTimeout(resolve, 500)); attempts++; } - return !this.plugin.saveLock; + const released = !this.plugin.saveLock; + if (!released) { + console.warn('[SyncLock] Save lock acquire FAILED after 5s timeout'); + this.plugin.debugLog('[SyncLock] Save lock acquire FAILED after 5s timeout'); + this.plugin.logOperation?.log('SYNC_LOCK_TIMEOUT', 'Save lock acquire failed after 5s timeout'); + } + return released; } async acquireExclusive(): Promise { @@ -50,7 +62,11 @@ export class SyncLockManager { if (this.locked) { this.plugin.debugLog('sync locked. waiting for exclusive acquire.'); const released = await this.waitForRelease(); - if (!released) return false; + if (!released) { + console.warn('[SyncLock] Exclusive sync lock acquire FAILED after timeout'); + this.plugin.debugLog('[SyncLock] Exclusive sync lock acquire FAILED after timeout'); + return false; + } this.plugin.debugLog('sync unlocked.'); } @@ -87,7 +103,11 @@ export class SyncLockManager { if (this.locked) { this.plugin.debugLog('sync locked.'); const released = await this.waitForRelease(); - if (!released) return false; + if (!released) { + console.warn(`[SyncLock] Sync lock acquire FAILED for direction=${direction || 'none'}`); + this.plugin.debugLog(`[SyncLock] Sync lock acquire FAILED for direction=${direction || 'none'}`); + return false; + } this.plugin.debugLog('sync unlocked.'); } diff --git a/src/sync/toObsidian.ts b/src/sync/toObsidian.ts index 1342efd..aa6dff2 100644 --- a/src/sync/toObsidian.ts +++ b/src/sync/toObsidian.ts @@ -93,10 +93,20 @@ export class TodoistToObsidianSync { private async syncSingleTaskToObsidian(taskId: string, task: any): Promise { const mapping = this.plugin.cacheOperation.getTaskFileMapping(taskId); - if (!mapping) return; + if (!mapping) { + console.warn(`[syncSingleTaskToObsidian] No mapping found for task ${taskId}`); + this.plugin.debugLog(`[syncSingleTaskToObsidian] No mapping found for task ${taskId}`); + this.plugin.logOperation?.log('SYNC_TARGET_MISSING', `Todoist→Obsidian sync skipped: no mapping for task ${taskId}`, undefined, taskId); + return; + } const file = this.app.vault.getAbstractFileByPath(mapping.filePath); - if (!file) return; + if (!file) { + console.warn(`[syncSingleTaskToObsidian] File not found: ${mapping.filePath} (task ${taskId})`); + this.plugin.debugLog(`[syncSingleTaskToObsidian] File not found: ${mapping.filePath} (task ${taskId})`); + this.plugin.logOperation?.log('SYNC_TARGET_MISSING', `Todoist→Obsidian sync skipped: file not found ${mapping.filePath}`, mapping.filePath, taskId); + return; + } const fileContent = await this.app.vault.read(file); const lines = fileContent.split('\n'); @@ -108,7 +118,12 @@ export class TodoistToObsidianSync { break; } } - if (!taskLine) return; + if (!taskLine) { + console.warn(`[syncSingleTaskToObsidian] Task line not found in file for task ${taskId}`); + this.plugin.debugLog(`[syncSingleTaskToObsidian] Task line not found in file for task ${taskId}`); + this.plugin.logOperation?.log('SYNC_TARGET_MISSING', `Todoist→Obsidian sync skipped: task line not found in file`, mapping.filePath, taskId); + return; + } const obsidianIsChecked = /\[(x|X)\]/.test(taskLine); const todoistIsChecked = task.checked || false; diff --git a/src/sync/toTodoist.ts b/src/sync/toTodoist.ts index 77a7c8e..95d322e 100644 --- a/src/sync/toTodoist.ts +++ b/src/sync/toTodoist.ts @@ -171,7 +171,10 @@ export class ObsidianToTodoistSync { * this fires once when the cursor moves away, so the user can finish typing. */ async lastLineNewTaskCheck(filepath: string, lineText: string, lineNumber: number, fileContent: string): Promise { - if (!this.plugin.isPrimaryDevice()) return; + if (!this.plugin.isPrimaryDevice()) { + this.plugin.debugLog('[lastLineNewTaskCheck] Push blocked: not primary device'); + return; + } if (!this.plugin.settings.enableFullVaultSync) return; const isTask = this.plugin.taskParser.isMarkdownTask(lineText); @@ -191,7 +194,12 @@ export class ObsidianToTodoistSync { this.plugin.debugLog('[lastLineNewTaskCheck] New task detected on line leave:', processedLine); const currentTask = await this.plugin.taskParser.convertTextToTodoistTaskObject(processedLine, filepath, lineNumber, fileContent); - if (typeof currentTask === 'undefined') return; + if (typeof currentTask === 'undefined') { + console.warn(`[lastLineNewTaskCheck] Task parser returned undefined for line: ${processedLine}`); + this.plugin.debugLog(`[lastLineNewTaskCheck] Task parser returned undefined for line ${lineNumber} in ${filepath}`); + this.plugin.logOperation?.log('TASK_PARSE_FAILED', `Task parser failed for line ${lineNumber}`, filepath); + return; + } try { const newTask = await this.plugin.todoistSyncAPI.AddTask(currentTask); @@ -284,6 +292,9 @@ export class ObsidianToTodoistSync { this.plugin.debugLog(filepath); const currentTask = await this.plugin.taskParser.convertTextToTodoistTaskObject(line, filepath, i, lines.join('\n')); if (typeof currentTask === 'undefined') { + console.warn(`[fullTextNewTaskCheck] Task parser returned undefined for line ${i} in ${filepath}`); + this.plugin.debugLog(`[fullTextNewTaskCheck] Task parser returned undefined for line ${i} in ${filepath}`); + this.plugin.logOperation?.log('TASK_PARSE_FAILED', `Task parser failed for line ${i}`, filepath); continue; } this.plugin.debugLog(currentTask); @@ -367,6 +378,8 @@ export class ObsidianToTodoistSync { } if (!this.plugin.cacheOperation.isTaskSyncEnabled(lineTask_todoist_id)) { + this.plugin.debugLog(`[lineModifiedTaskCheck] Sync disabled for task ${lineTask_todoist_id}, skipping modification`); + this.plugin.logOperation?.log('SYNC_DISABLED_SKIP', `User edit ignored: sync disabled for task ${lineTask_todoist_id}`, filepath, lineTask_todoist_id); return; } @@ -577,7 +590,11 @@ export class ObsidianToTodoistSync { this.plugin.debugLog('[toTodoist] Push blocked: not primary device'); return; } - if (!this.plugin.cacheOperation?.isTaskSyncEnabled(taskId)) return; + if (!this.plugin.cacheOperation?.isTaskSyncEnabled(taskId)) { + this.plugin.debugLog(`[closeTask] Sync disabled for task ${taskId}, skipping close`); + this.plugin.logOperation?.log('SYNC_DISABLED_SKIP', `Checkbox close ignored: sync disabled for task ${taskId}`, undefined, taskId); + return; + } try { const taskMapping = this.plugin.cacheOperation.getTaskFileMapping(taskId); const savedTask = await this.plugin.todoistSyncAPI.GetTaskById(taskId); @@ -638,7 +655,11 @@ export class ObsidianToTodoistSync { this.plugin.debugLog('[toTodoist] Push blocked: not primary device'); return; } - if (!this.plugin.cacheOperation?.isTaskSyncEnabled(taskId)) return; + if (!this.plugin.cacheOperation?.isTaskSyncEnabled(taskId)) { + this.plugin.debugLog(`[repoenTask] Sync disabled for task ${taskId}, skipping reopen`); + this.plugin.logOperation?.log('SYNC_DISABLED_SKIP', `Checkbox reopen ignored: sync disabled for task ${taskId}`, undefined, taskId); + return; + } try { const taskMapping = this.plugin.cacheOperation.getTaskFileMapping(taskId); const savedTask = await this.plugin.todoistSyncAPI.GetTaskById(taskId);