Skip to content

Commit ab09346

Browse files
authored
fix: improve error propagation with better stack traces (#789)
* fix: improve error propagation and stack trace visibility * fix: use toError helper for type safety * fix: optimize error logging for Chrome DevTools * feat: add ErrorUtils and improve error handling * fix: update error handling in engine files * docs: update todo list progress * fix: update error handling in formatters directory * docs: update todo list progress * fix: resolve build issues in error handling * refactor: rename handleError to reportError for better clarity * fix: improve error handling in main.ts and quickAddApi.ts * docs: update todo list progress * remove todos
1 parent cf372ce commit ab09346

14 files changed

+330
-76
lines changed

src/engine/CaptureChoiceEngine.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type ICaptureChoice from "../types/choices/ICaptureChoice";
22
import type { TFile } from "obsidian";
33
import type { App } from "obsidian";
44
import { log } from "../logger/logManager";
5+
import { reportError } from "../utils/errorUtils";
56
import { CaptureChoiceFormatter } from "../formatters/captureChoiceFormatter";
67
import {
78
appendToCurrentLine,
@@ -101,8 +102,8 @@ export class CaptureChoiceEngine extends QuickAddChoiceEngine {
101102
});
102103
}
103104
}
104-
} catch (e) {
105-
log.logError(e as string);
105+
} catch (err) {
106+
reportError(err, `Error running capture choice "${this.choice.name}"`);
106107
}
107108
}
108109

src/engine/MacroChoiceEngine.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as obsidian from "obsidian";
44
import type { IUserScript } from "../types/macros/IUserScript";
55
import type { IObsidianCommand } from "../types/macros/IObsidianCommand";
66
import { log } from "../logger/logManager";
7+
import { reportError } from "../utils/errorUtils";
8+
import { ErrorLevel } from "../logger/errorLevel";
79
import { CommandType } from "../types/macros/CommandType";
810
import { QuickAddApi } from "../quickAddApi";
911
import type { ICommand } from "../types/macros/ICommand";
@@ -136,12 +138,8 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine {
136138

137139
try {
138140
await this.userScriptDelegator(userScript);
139-
} catch (error) {
140-
log.logError(
141-
`failed to run user script ${command.name}. Error:\n\n${
142-
(error as { message: string }).message
143-
}`
144-
);
141+
} catch (err) {
142+
reportError(err, `Failed to run user script ${command.name}`);
145143
}
146144

147145
if (this.userScriptCommand) this.userScriptCommand = null;
@@ -248,8 +246,8 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine {
248246
);
249247

250248
await this.userScriptDelegator(obj[selected]);
251-
} catch (e) {
252-
log.logMessage(e as string);
249+
} catch (err) {
250+
reportError(err, "Error in script object handling", ErrorLevel.Log);
253251
}
254252
}
255253

@@ -356,4 +354,4 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine {
356354
this.choiceExecutor.variables.set(key, aiOutputVariables[key]);
357355
}
358356
}
359-
}
357+
}

src/engine/TemplateChoiceEngine.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
fileExistsIncrement,
1818
} from "../constants";
1919
import { log } from "../logger/logManager";
20+
import { reportError } from "../utils/errorUtils";
2021
import type QuickAdd from "../main";
2122
import { TemplateEngine } from "./TemplateEngine";
2223
import type { IChoiceExecutor } from "../IChoiceExecutor";
@@ -158,8 +159,8 @@ export class TemplateChoiceEngine extends TemplateEngine {
158159
});
159160
}
160161
}
161-
} catch (error) {
162-
log.logError(error as string);
162+
} catch (err) {
163+
reportError(err, `Error running template choice "${this.choice.name}"`);
163164
}
164165
}
165166

src/engine/TemplateEngine.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "../utilityObsidian";
1010
import GenericSuggester from "../gui/GenericSuggester/genericSuggester";
1111
import { FILE_NUMBER_REGEX, MARKDOWN_FILE_EXTENSION_REGEX } from "../constants";
12-
import { log } from "../logger/logManager";
12+
import { reportError } from "../utils/errorUtils";
1313
import type { IChoiceExecutor } from "../IChoiceExecutor";
1414

1515
export abstract class TemplateEngine extends QuickAddEngine {
@@ -110,12 +110,8 @@ export abstract class TemplateEngine extends QuickAddEngine {
110110
await replaceTemplaterTemplatesInCreatedFile(this.app, createdFile, true);
111111

112112
return createdFile;
113-
} catch (e) {
114-
log.logError(
115-
`Could not create file with template: \n\n${
116-
(e as { message: string }).message
117-
}`
118-
);
113+
} catch (err) {
114+
reportError(err, "Could not create file with template");
119115
return null;
120116
}
121117
}
@@ -137,8 +133,8 @@ export abstract class TemplateEngine extends QuickAddEngine {
137133
await replaceTemplaterTemplatesInCreatedFile(this.app, file, true);
138134

139135
return file;
140-
} catch (e) {
141-
log.logError(e as string);
136+
} catch (err) {
137+
reportError(err, "Could not overwrite file with template");
142138
return null;
143139
}
144140
}
@@ -166,8 +162,8 @@ export abstract class TemplateEngine extends QuickAddEngine {
166162
await replaceTemplaterTemplatesInCreatedFile(this.app, file, true);
167163

168164
return file;
169-
} catch (e) {
170-
log.logError(e as string);
165+
} catch (err) {
166+
reportError(err, "Could not append to file with template");
171167
return null;
172168
}
173169
}
@@ -187,4 +183,4 @@ export abstract class TemplateEngine extends QuickAddEngine {
187183

188184
return await this.app.vault.cachedRead(templateFile);
189185
}
190-
}
186+
}

src/formatters/captureChoiceFormatter.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CompleteFormatter } from "./completeFormatter";
22
import type ICaptureChoice from "../types/choices/ICaptureChoice";
33
import { MarkdownView, type TFile } from "obsidian";
44
import { log } from "../logger/logManager";
5+
import { reportError } from "../utils/errorUtils";
56
import { templaterParseTemplate } from "../utilityObsidian";
67
import {
78
CREATE_IF_NOT_FOUND_BOTTOM,
@@ -131,7 +132,7 @@ export class CaptureChoiceFormatter extends CompleteFormatter {
131132
return await this.createInsertAfterIfNotFound(formatted);
132133
}
133134

134-
log.logError("unable to find insert after line in file.");
135+
reportError(new Error("Unable to find insert after line in file"), "Insert After Error");
135136
}
136137

137138
if (this.choice.insertAfter?.insertAtEnd) {
@@ -218,11 +219,10 @@ export class CaptureChoiceFormatter extends CompleteFormatter {
218219
);
219220

220221
return newFileContent;
221-
} catch (e) {
222-
log.logError(
223-
`unable to insert line '${
224-
this.choice.insertAfter.after
225-
}' on your cursor.\n${e as string}`,
222+
} catch (err) {
223+
reportError(
224+
err,
225+
`Unable to insert line '${this.choice.insertAfter.after}' at cursor position`
226226
);
227227
}
228228
}
@@ -262,4 +262,4 @@ export class CaptureChoiceFormatter extends CompleteFormatter {
262262

263263
return `${pre}\n${text}${post}`;
264264
}
265-
}
265+
}

src/logger/consoleErrorLogger.ts

+71-9
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,94 @@
11
import { ErrorLevel } from "./errorLevel";
22
import { QuickAddLogger } from "./quickAddLogger";
33
import type { QuickAddError } from "./quickAddError";
4+
import { MAX_ERROR_LOG_SIZE } from "../utils/errorUtils";
45

6+
/**
7+
* Logger implementation that outputs to the browser console and maintains an error log
8+
* with a maximum size to prevent memory leaks. Uses native Error objects to leverage
9+
* browser DevTools stack trace display.
10+
*/
511
export class ConsoleErrorLogger extends QuickAddLogger {
12+
/**
13+
* In-memory log of errors for debugging
14+
* Limited to MAX_ERROR_LOG_SIZE entries to prevent memory leaks
15+
*/
616
public ErrorLog: QuickAddError[] = [];
717

8-
public logError(errorMsg: string) {
9-
const error = this.getQuickAddError(errorMsg, ErrorLevel.Error);
18+
/**
19+
* Logs an error to the console with proper stack trace handling
20+
*
21+
* @param errorMsg - Error message or Error object
22+
* @param stack - Optional stack trace string
23+
* @param originalError - Optional original Error object
24+
*/
25+
public logError(errorMsg: string, stack?: string, originalError?: Error) {
26+
const error = this.getQuickAddError(errorMsg, ErrorLevel.Error, stack, originalError);
1027
this.addMessageToErrorLog(error);
1128

12-
console.error(this.formatOutputString(error));
29+
// Always pass the original error or create a new one to leverage Dev Tools' stack trace UI
30+
const errorToLog = originalError || new Error(errorMsg);
31+
32+
// Just log the message as the first argument and the error object as the second
33+
console.error(this.formatOutputString(error), errorToLog);
1334
}
1435

15-
public logWarning(warningMsg: string) {
16-
const warning = this.getQuickAddError(warningMsg, ErrorLevel.Warning);
36+
/**
37+
* Logs a warning to the console with proper stack trace handling
38+
*
39+
* @param warningMsg - Warning message or Error object
40+
* @param stack - Optional stack trace string
41+
* @param originalError - Optional original Error object
42+
*/
43+
public logWarning(warningMsg: string, stack?: string, originalError?: Error) {
44+
const warning = this.getQuickAddError(warningMsg, ErrorLevel.Warning, stack, originalError);
1745
this.addMessageToErrorLog(warning);
1846

19-
console.warn(this.formatOutputString(warning));
47+
// Always pass the original error or create a new one to leverage Dev Tools' stack trace UI
48+
const errorToLog = originalError || new Error(warningMsg);
49+
50+
console.warn(this.formatOutputString(warning), errorToLog);
2051
}
2152

22-
public logMessage(logMsg: string) {
23-
const log = this.getQuickAddError(logMsg, ErrorLevel.Log);
53+
/**
54+
* Logs a message to the console
55+
*
56+
* @param logMsg - Log message
57+
* @param stack - Optional stack trace string
58+
* @param originalError - Optional original Error object
59+
*/
60+
public logMessage(logMsg: string, stack?: string, originalError?: Error) {
61+
const log = this.getQuickAddError(logMsg, ErrorLevel.Log, stack, originalError);
2462
this.addMessageToErrorLog(log);
2563

26-
console.log(this.formatOutputString(log));
64+
// For regular logs, we'll still show the error if available
65+
if (originalError) {
66+
console.log(this.formatOutputString(log), originalError);
67+
} else {
68+
console.log(this.formatOutputString(log));
69+
}
2770
}
2871

72+
/**
73+
* Adds an error to the error log, maintaining the maximum size limit
74+
* by removing the oldest entries when needed
75+
*
76+
* @param error - Error to add to the log
77+
*/
2978
private addMessageToErrorLog(error: QuickAddError): void {
79+
// Add the new error
3080
this.ErrorLog.push(error);
81+
82+
// If we've exceeded the maximum size, remove the oldest entries
83+
if (this.ErrorLog.length > MAX_ERROR_LOG_SIZE) {
84+
this.ErrorLog = this.ErrorLog.slice(-MAX_ERROR_LOG_SIZE);
85+
}
86+
}
87+
88+
/**
89+
* Clears the error log
90+
*/
91+
public clearErrorLog(): void {
92+
this.ErrorLog = [];
3193
}
3294
}

src/logger/guiLogger.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ export class GuiLogger extends QuickAddLogger {
88
super();
99
}
1010

11-
logError(msg: string): void {
12-
const error = this.getQuickAddError(msg, ErrorLevel.Error);
11+
logError(msg: string, stack?: string, originalError?: Error): void {
12+
const error = this.getQuickAddError(msg, ErrorLevel.Error, stack, originalError);
1313
new Notice(this.formatOutputString(error), 15000);
1414
}
1515

16-
logWarning(msg: string): void {
17-
const warning = this.getQuickAddError(msg, ErrorLevel.Warning);
16+
logWarning(msg: string, stack?: string, originalError?: Error): void {
17+
const warning = this.getQuickAddError(msg, ErrorLevel.Warning, stack, originalError);
1818
new Notice(this.formatOutputString(warning));
1919
}
2020

21-
logMessage(msg: string): void {}
21+
logMessage(msg: string, stack?: string, originalError?: Error): void {}
2222
}

src/logger/ilogger.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export interface ILogger {
2-
logError(msg: string): void;
2+
logError(msg: string, stack?: string, originalError?: Error): void;
33

4-
logWarning(msg: string): void;
4+
logWarning(msg: string, stack?: string, originalError?: Error): void;
55

6-
logMessage(msg: string): void;
6+
logMessage(msg: string, stack?: string, originalError?: Error): void;
77
}

src/logger/logManager.ts

+41-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
import type { ILogger } from "./ilogger";
22

3-
class LogManager {
3+
/**
4+
* Helper function to convert any value to an Error object
5+
* This function ensures that an Error object is always returned, preserving
6+
* the original Error if provided or creating a new one otherwise.
7+
*
8+
* @param err - The error value to convert (can be any type)
9+
* @returns A proper Error object with stack trace
10+
*
11+
* @example
12+
* ```ts
13+
* try {
14+
* // Some operation
15+
* } catch (err) {
16+
* log.logError(toError(err));
17+
* }
18+
* ```
19+
*/
20+
export function toError(err: unknown): Error {
21+
if (err instanceof Error) return err;
22+
return new Error(typeof err === 'string' ? err : String(err));
23+
}
24+
25+
export class LogManager {
426
public static loggers: ILogger[] = [];
527

628
public register(logger: ILogger): LogManager {
@@ -9,16 +31,28 @@ class LogManager {
931
return this;
1032
}
1133

12-
logError(message: string) {
13-
LogManager.loggers.forEach((logger) => logger.logError(message));
34+
logError(message: string | Error) {
35+
const messageStr = message instanceof Error ? message.message : message;
36+
const stack = message instanceof Error ? message.stack : undefined;
37+
const originalError = message instanceof Error ? message : undefined;
38+
39+
LogManager.loggers.forEach((logger) => logger.logError(messageStr, stack, originalError));
1440
}
1541

16-
logWarning(message: string) {
17-
LogManager.loggers.forEach((logger) => logger.logWarning(message));
42+
logWarning(message: string | Error) {
43+
const messageStr = message instanceof Error ? message.message : message;
44+
const stack = message instanceof Error ? message.stack : undefined;
45+
const originalError = message instanceof Error ? message : undefined;
46+
47+
LogManager.loggers.forEach((logger) => logger.logWarning(messageStr, stack, originalError));
1848
}
1949

20-
logMessage(message: string) {
21-
LogManager.loggers.forEach((logger) => logger.logMessage(message));
50+
logMessage(message: string | Error) {
51+
const messageStr = message instanceof Error ? message.message : message;
52+
const stack = message instanceof Error ? message.stack : undefined;
53+
const originalError = message instanceof Error ? message : undefined;
54+
55+
LogManager.loggers.forEach((logger) => logger.logMessage(messageStr, stack, originalError));
2256
}
2357
}
2458

src/logger/quickAddError.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ export interface QuickAddError {
44
message: string;
55
level: ErrorLevel;
66
time: number;
7+
stack?: string;
8+
originalError?: Error;
79
}

0 commit comments

Comments
 (0)