feat: logging to include categories for better log management
This commit is contained in:
@@ -393,13 +393,13 @@ export const useAuthStore = create<AuthState>()(
|
||||
oauthAccessToken = access_token;
|
||||
oauthExpiresIn = expires_in;
|
||||
upgradedToOAuth = true;
|
||||
debug.log('TOTP login upgraded to token-based auth (has_refresh_token=' + has_refresh_token + ')');
|
||||
debug.log('auth', 'TOTP login upgraded to token-based auth (has_refresh_token=' + has_refresh_token + ')');
|
||||
} else {
|
||||
const errorBody = await tokenRes.json().catch(() => ({ error: 'unknown' }));
|
||||
debug.warn('TOTP token exchange failed:', tokenRes.status, errorBody);
|
||||
debug.warn('auth', 'TOTP token exchange failed:', tokenRes.status, errorBody);
|
||||
}
|
||||
} catch (err) {
|
||||
debug.warn('TOTP token exchange error:', err);
|
||||
debug.warn('auth', 'TOTP token exchange error:', err);
|
||||
}
|
||||
|
||||
// If token exchange failed, enable TOTP re-auth prompt so the
|
||||
@@ -407,7 +407,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
if (!upgradedToOAuth) {
|
||||
const { useTotpReauthStore } = await import('@/stores/totp-reauth-store');
|
||||
client.enableTotpReauth(password, () => useTotpReauthStore.getState().requestTotp());
|
||||
debug.log('TOTP re-auth enabled — user will be prompted for fresh codes on session expiry');
|
||||
debug.log('auth', 'TOTP re-auth enabled — user will be prompted for fresh codes on session expiry');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -197,7 +197,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
// Expand recurring events client-side (Stalwart doesn't support
|
||||
// mutations on synthetic IDs from server-side expandRecurrences)
|
||||
const events = expandRecurringEvents(validEvents, start, end);
|
||||
debug.log('Calendar fetchEvents completed', {
|
||||
debug.log('calendar', 'Calendar fetchEvents completed', {
|
||||
start,
|
||||
end,
|
||||
rawCount: rawEvents.length,
|
||||
@@ -206,7 +206,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
droppedEvents,
|
||||
});
|
||||
if (droppedEvents > 0) {
|
||||
debug.warn('Calendar fetchEvents dropped malformed events without a start field', { droppedEvents });
|
||||
debug.warn('calendar', 'Calendar fetchEvents dropped malformed events without a start field', { droppedEvents });
|
||||
}
|
||||
set({ events, isLoadingEvents: false, dateRange: { start, end } });
|
||||
} catch (error) {
|
||||
@@ -237,7 +237,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
if (event.originalCalendarIds) {
|
||||
cleanEvent.calendarIds = event.originalCalendarIds;
|
||||
}
|
||||
debug.log('Calendar createEvent request', {
|
||||
debug.log('calendar', 'Calendar createEvent request', {
|
||||
event: getStoreEventDebugSnapshot(cleanEvent),
|
||||
sendSchedulingMessages,
|
||||
targetAccountId,
|
||||
@@ -256,7 +256,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
? mappedCreated.start >= currentDateRange.start && mappedCreated.start <= currentDateRange.end
|
||||
: null;
|
||||
|
||||
debug.log('Calendar createEvent response', {
|
||||
debug.log('calendar', 'Calendar createEvent response', {
|
||||
created: getStoreEventDebugSnapshot(created),
|
||||
mappedCreated: getStoreEventDebugSnapshot(mappedCreated),
|
||||
isVisible,
|
||||
@@ -265,21 +265,21 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
});
|
||||
|
||||
if (!isVisible) {
|
||||
debug.warn('Created event is hidden by current calendar filters', {
|
||||
debug.warn('calendar', 'Created event is hidden by current calendar filters', {
|
||||
selectedCalendarIds,
|
||||
createdCalendarIds,
|
||||
});
|
||||
}
|
||||
|
||||
if (inCurrentDateRange === false) {
|
||||
debug.warn('Created event is outside the currently loaded date range', {
|
||||
debug.warn('calendar', 'Created event is outside the currently loaded date range', {
|
||||
currentDateRange,
|
||||
createdStart: mappedCreated.start,
|
||||
});
|
||||
}
|
||||
|
||||
if (mappedCreated.showWithoutTime && mappedCreated.timeZone !== null) {
|
||||
debug.warn('Created all-day event came back with a non-null timeZone', {
|
||||
debug.warn('calendar', 'Created all-day event came back with a non-null timeZone', {
|
||||
timeZone: mappedCreated.timeZone,
|
||||
event: getStoreEventDebugSnapshot(mappedCreated),
|
||||
});
|
||||
@@ -301,7 +301,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
const storeEvent = get().events.find(e => e.id === id);
|
||||
const realId = storeEvent?.originalId || id;
|
||||
const targetAccountId = storeEvent?.accountId;
|
||||
debug.log('Calendar updateEvent', {
|
||||
debug.log('calendar', 'Calendar updateEvent', {
|
||||
storeId: id,
|
||||
realId,
|
||||
uid: storeEvent?.uid,
|
||||
@@ -445,20 +445,20 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
await client.updateCalendarEvent(eventId, { calendarIds } as Partial<CalendarEvent>, undefined, targetAccountId);
|
||||
linked++;
|
||||
} catch (err) {
|
||||
debug.warn(`Import: failed to link event ${eventId} to target calendar:`, err);
|
||||
debug.warn('calendar', `Import: failed to link event ${eventId} to target calendar:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
if (linked > 0) {
|
||||
debug.log(`Import: linked ${linked} existing events to target calendar`);
|
||||
debug.log('calendar', `Import: linked ${linked} existing events to target calendar`);
|
||||
}
|
||||
const skipped = eventsToProcess.length - newEvents.length - eventsToLink.length;
|
||||
if (skipped > 0) {
|
||||
debug.log(`Import: skipped ${skipped} events already in target calendar`);
|
||||
debug.log('calendar', `Import: skipped ${skipped} events already in target calendar`);
|
||||
}
|
||||
eventsToProcess = newEvents;
|
||||
} catch (error) {
|
||||
debug.warn('Could not fetch existing events for deduplication, proceeding without:', error);
|
||||
debug.warn('calendar', 'Could not fetch existing events for deduplication, proceeding without:', error);
|
||||
}
|
||||
|
||||
// Prepare all events for batch creation
|
||||
@@ -543,7 +543,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
const { created, failed } = await client.batchCreateCalendarEvents(batch, targetAccountId);
|
||||
imported += created.length;
|
||||
if (failed.length > 0) {
|
||||
debug.warn(`Import batch ${i / BATCH_SIZE + 1}: ${failed.length} events failed`);
|
||||
debug.warn('calendar', `Import batch ${i / BATCH_SIZE + 1}: ${failed.length} events failed`);
|
||||
}
|
||||
} catch (error) {
|
||||
debug.error(`Import batch ${i / BATCH_SIZE + 1} failed:`, error);
|
||||
@@ -577,7 +577,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
debug.error('Failed to send cancellation emails:', e);
|
||||
}
|
||||
}
|
||||
debug.log('Calendar deleteEvent', {
|
||||
debug.log('calendar', 'Calendar deleteEvent', {
|
||||
storeId: id,
|
||||
realId,
|
||||
uid: storeEvent?.uid,
|
||||
@@ -675,7 +675,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
|
||||
// If we couldn't destroy any events, stop to avoid infinite loop
|
||||
if (destroyed.length === 0) {
|
||||
debug.warn('Could not delete any events, stopping clear loop. Not destroyed:', ids.length);
|
||||
debug.warn('calendar', 'Could not delete any events, stopping clear loop. Not destroyed:', ids.length);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -744,7 +744,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
await get().refreshICalSubscription(client, subscription.id);
|
||||
} catch {
|
||||
// Subscription created, initial fetch failed - user can retry
|
||||
debug.warn('Initial subscription fetch failed for:', name);
|
||||
debug.warn('calendar', 'Initial subscription fetch failed for:', name);
|
||||
}
|
||||
|
||||
return subscription;
|
||||
@@ -892,7 +892,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
try {
|
||||
await get().refreshICalSubscription(client, sub.id);
|
||||
} catch {
|
||||
debug.warn('Failed to refresh subscription:', sub.name);
|
||||
debug.warn('calendar', 'Failed to refresh subscription:', sub.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export const useFilterStore = create<FilterStore>()((set, get) => ({
|
||||
set({ sieveCapabilities: capabilities });
|
||||
|
||||
const allScripts = await client.getSieveScripts();
|
||||
debug.log('Sieve scripts fetched:', allScripts.length);
|
||||
debug.log('filters', 'Sieve scripts fetched:', allScripts.length);
|
||||
|
||||
// Skip the server-managed 'vacation' script (RFC 9661 §4) — it can only
|
||||
// be modified via VacationResponse/set, not SieveScript/set.
|
||||
@@ -73,10 +73,10 @@ export const useFilterStore = create<FilterStore>()((set, get) => ({
|
||||
const result = parseScript(content);
|
||||
|
||||
if (result.isOpaque) {
|
||||
debug.log('Sieve script is opaque (hand-edited)');
|
||||
debug.log('filters', 'Sieve script is opaque (hand-edited)');
|
||||
set({ isLoading: false, isOpaque: true, rules: [], vacationSettings: result.vacation || null });
|
||||
} else {
|
||||
debug.log('Parsed', result.rules.length, 'filter rules');
|
||||
debug.log('filters', 'Parsed', result.rules.length, 'filter rules');
|
||||
set({ isLoading: false, isOpaque: false, rules: result.rules, vacationSettings: result.vacation || null });
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -108,7 +108,7 @@ export const useFilterStore = create<FilterStore>()((set, get) => ({
|
||||
}
|
||||
|
||||
set({ isSaving: false, rawScript: content });
|
||||
debug.log('Filters saved successfully');
|
||||
debug.log('filters', 'Filters saved successfully');
|
||||
} catch (error) {
|
||||
debug.error('Failed to save filters:', error);
|
||||
set({
|
||||
@@ -212,7 +212,7 @@ export const useFilterStore = create<FilterStore>()((set, get) => ({
|
||||
});
|
||||
}
|
||||
|
||||
debug.log('Vacation synced to sieve script');
|
||||
debug.log('filters', 'Vacation synced to sieve script');
|
||||
} catch (error) {
|
||||
debug.error('Failed to sync vacation to sieve script:', error);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,18 @@ export const ALL_HOVER_ACTIONS: { id: HoverAction; labelKey: string }[] = [
|
||||
{ id: 'spam', labelKey: 'spam' },
|
||||
];
|
||||
|
||||
export type DebugCategory = 'jmap' | 'calendar' | 'tasks' | 'auth' | 'filters' | 'email' | 'push';
|
||||
|
||||
export const ALL_DEBUG_CATEGORIES: { id: DebugCategory; labelKey: string }[] = [
|
||||
{ id: 'jmap', labelKey: 'jmap' },
|
||||
{ id: 'calendar', labelKey: 'calendar' },
|
||||
{ id: 'tasks', labelKey: 'tasks' },
|
||||
{ id: 'auth', labelKey: 'auth' },
|
||||
{ id: 'filters', labelKey: 'filters' },
|
||||
{ id: 'email', labelKey: 'email' },
|
||||
{ id: 'push', labelKey: 'push' },
|
||||
];
|
||||
|
||||
export interface KeywordDefinition {
|
||||
id: string; // Used as JMAP keyword suffix: $label:<id>
|
||||
label: string; // Display name
|
||||
@@ -175,6 +187,7 @@ interface SettingsState {
|
||||
|
||||
// Advanced
|
||||
debugMode: boolean;
|
||||
debugCategories: Record<DebugCategory, boolean>;
|
||||
settingsSyncDisabled: boolean;
|
||||
|
||||
// Actions
|
||||
@@ -299,6 +312,15 @@ const DEFAULT_SETTINGS = {
|
||||
|
||||
// Advanced
|
||||
debugMode: false,
|
||||
debugCategories: {
|
||||
jmap: true,
|
||||
calendar: true,
|
||||
tasks: true,
|
||||
auth: true,
|
||||
filters: true,
|
||||
email: true,
|
||||
push: true,
|
||||
} as Record<DebugCategory, boolean>,
|
||||
settingsSyncDisabled: false,
|
||||
};
|
||||
|
||||
@@ -383,6 +405,7 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
sidebarApps: state.sidebarApps,
|
||||
keepAppsLoaded: state.keepAppsLoaded,
|
||||
debugMode: state.debugMode,
|
||||
debugCategories: state.debugCategories,
|
||||
settingsSyncDisabled: state.settingsSyncDisabled,
|
||||
// Cross-store settings
|
||||
theme: useThemeStore.getState().theme,
|
||||
|
||||
@@ -37,13 +37,13 @@ export const useTaskStore = create<TaskStore>((set, get) => ({
|
||||
setShowCompleted: (show) => set({ showCompleted: show }),
|
||||
|
||||
fetchTasks: async (client, calendarIds) => {
|
||||
debug.log('TaskStore/fetchTasks start', { calendarIds: calendarIds || 'all' });
|
||||
debug.log('tasks', 'TaskStore/fetchTasks start', { calendarIds: calendarIds || 'all' });
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const tasks = await client.getCalendarTasks(calendarIds);
|
||||
debug.log('TaskStore/fetchTasks received', tasks.length, 'tasks');
|
||||
debug.log('tasks', 'TaskStore/fetchTasks received', tasks.length, 'tasks');
|
||||
tasks.forEach((t, i) => {
|
||||
debug.log(`TaskStore/fetchTasks [${i}]`, {
|
||||
debug.log('tasks', `TaskStore/fetchTasks [${i}]`, {
|
||||
id: t.id, uid: t.uid, '@type': t['@type'],
|
||||
title: t.title, due: t.due, progress: t.progress,
|
||||
showWithoutTime: t.showWithoutTime, calendarIds: t.calendarIds,
|
||||
@@ -57,9 +57,9 @@ export const useTaskStore = create<TaskStore>((set, get) => ({
|
||||
},
|
||||
|
||||
createTask: async (client, task) => {
|
||||
debug.log('TaskStore/createTask', task);
|
||||
debug.log('tasks', 'TaskStore/createTask', task);
|
||||
const created = await client.createCalendarTask(task);
|
||||
debug.log('TaskStore/createTask result', { id: created.id, uid: created.uid, title: created.title });
|
||||
debug.log('tasks', 'TaskStore/createTask result', { id: created.id, uid: created.uid, title: created.title });
|
||||
set({ tasks: [...get().tasks, created] });
|
||||
return created;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user