Toast on main process errors

This commit is contained in:
trevor-signal
2025-09-19 19:00:46 -04:00
committed by GitHub
parent 40eaf078cc
commit 2c1cb5ac4e
8 changed files with 130 additions and 9 deletions

View File

@@ -443,12 +443,15 @@ function deleteOrphanedAttachments({
await sql.sqlRead('finishGetKnownMessageAttachments', cursor); await sql.sqlRead('finishGetKnownMessageAttachments', cursor);
} }
} }
log.info( log.info(
`cleanupOrphanedAttachments: ${totalAttachmentsFound} message ` + `cleanupOrphanedAttachments: ${totalAttachmentsFound} attachments and \
`attachments; ${orphanedAttachments.size} remain` ${totalDownloadsFound} downloads found on disk`
); );
if (orphanedAttachments.size > 0) {
log.error(`${orphanedAttachments.size} orphaned attachment(s) found`);
}
if (totalMissing > 0) { if (totalMissing > 0) {
log.warn( log.warn(
`cleanupOrphanedAttachments: ${totalMissing} message attachments were not found on disk` `cleanupOrphanedAttachments: ${totalMissing} message attachments were not found on disk`
@@ -460,10 +463,10 @@ function deleteOrphanedAttachments({
attachments: Array.from(orphanedAttachments), attachments: Array.from(orphanedAttachments),
}); });
log.info( if (orphanedDownloads.size > 0) {
`cleanupOrphanedAttachments: found ${totalDownloadsFound} downloads ` + log.error(`${orphanedDownloads.size} orphaned download(s) found`);
`${orphanedDownloads.size} remain` }
);
await deleteAllDownloads({ await deleteAllDownloads({
userDataPath, userDataPath,
downloads: Array.from(orphanedDownloads), downloads: Array.from(orphanedDownloads),

View File

@@ -28,6 +28,7 @@ const KnownConfigKeys = [
'desktop.donations', 'desktop.donations',
'desktop.donations.prod', 'desktop.donations.prod',
'desktop.internalUser', 'desktop.internalUser',
'desktop.loggingErrorToasts',
'desktop.mediaQuality.levels', 'desktop.mediaQuality.levels',
'desktop.messageCleanup', 'desktop.messageCleanup',
'desktop.retryRespondMaxAge', 'desktop.retryRespondMaxAge',

View File

@@ -152,6 +152,11 @@ function getToast(toastType: ToastType): AnyToast {
return { toastType: ToastType.LinkCopied }; return { toastType: ToastType.LinkCopied };
case ToastType.LoadingFullLogs: case ToastType.LoadingFullLogs:
return { toastType: ToastType.LoadingFullLogs }; return { toastType: ToastType.LoadingFullLogs };
case ToastType._InternalMainProcessLoggingError:
return {
toastType: ToastType._InternalMainProcessLoggingError,
parameters: { logLines: ['error1', 'error2'], count: 2 },
};
case ToastType.MaxAttachments: case ToastType.MaxAttachments:
return { toastType: ToastType.MaxAttachments }; return { toastType: ToastType.MaxAttachments };
case ToastType.MediaNoLongerAvailable: case ToastType.MediaNoLongerAvailable:

View File

@@ -19,6 +19,7 @@ import type { LocalizerType } from '../types/Util.js';
import type { AnyToast } from '../types/Toast.js'; import type { AnyToast } from '../types/Toast.js';
import type { AnyActionableMegaphone } from '../types/Megaphone.js'; import type { AnyActionableMegaphone } from '../types/Megaphone.js';
import type { Location } from '../types/Nav.js'; import type { Location } from '../types/Nav.js';
import { tw } from '../axo/tw.js';
export type PropsType = { export type PropsType = {
changeLocation: (newLocation: Location) => unknown; changeLocation: (newLocation: Location) => unknown;
@@ -586,6 +587,40 @@ export function renderToast({
); );
} }
if (toastType === ToastType._InternalMainProcessLoggingError) {
return (
<Toast
autoDismissDisabled
onClose={hideToast}
toastAction={{
label: i18n('icu:Toast__ActionLabel--SubmitLog'),
onClick: onShowDebugLog,
}}
// eslint-disable-next-line better-tailwindcss/no-restricted-classes
className={tw('max-w-[640px]!')}
>
<h2>
[INTERNAL]: {toast.parameters.count} error(s) from main process,
please submit log.
</h2>
{toast.parameters.count > toast.parameters.logLines.length ? (
<h3
className={tw('my-2')}
>{`Showing only last ${toast.parameters.logLines.length} errors`}</h3>
) : null}
<pre
className={tw(
'my-2 max-h-48 min-h-24 max-w-[520px] overflow-auto border-1 border-solid p-2'
)}
>
{toast.parameters.logLines.join('\n')}
</pre>
</Toast>
);
}
if (toastType === ToastType.PinnedConversationsFull) { if (toastType === ToastType.PinnedConversationsFull) {
return ( return (
<Toast onClose={hideToast}>{i18n('icu:pinnedConversationsFull')}</Toast> <Toast onClose={hideToast}>{i18n('icu:pinnedConversationsFull')}</Toast>

View File

@@ -38,6 +38,13 @@ const SUBSYSTEM_COLORS = new LRUCache<string, string>({
max: 500, max: 500,
}); });
let onLogCallback: (level: number, logLine: string, msgPrefix?: string) => void;
export function setOnLogCallback(
cb: (level: number, logLine: string, msgPrefix?: string) => void
): void {
onLogCallback = cb;
}
// Only for unpackaged app // Only for unpackaged app
function getSubsystemColor(name: string): string { function getSubsystemColor(name: string): string {
const cached = SUBSYSTEM_COLORS.get(name); const cached = SUBSYSTEM_COLORS.get(name);
@@ -168,6 +175,8 @@ const pinoInstance = pino(
typeof item === 'string' ? item : reallyJsonStringify(item) typeof item === 'string' ? item : reallyJsonStringify(item)
) )
.join(' '); .join(' ');
onLogCallback?.(level, line, this.msgPrefix);
return method.call(this, line); return method.call(this, line);
}, },
}, },

View File

@@ -27,10 +27,11 @@ import * as Errors from '../types/errors.js';
import { createRotatingPinoDest } from '../util/rotatingPinoDest.js'; import { createRotatingPinoDest } from '../util/rotatingPinoDest.js';
import { redactAll } from '../util/privacy.js'; import { redactAll } from '../util/privacy.js';
import { setPinoDestination, log } from './log.js'; import { setPinoDestination, log, setOnLogCallback } from './log.js';
import type { FetchLogIpcData, LogEntryType } from './shared.js'; import type { FetchLogIpcData, LogEntryType } from './shared.js';
import { LogLevel, isLogEntry } from './shared.js'; import { LogLevel, isLogEntry } from './shared.js';
import { isProduction } from '../util/version.js';
const { filter, flatten, map, pick, sortBy } = lodash; const { filter, flatten, map, pick, sortBy } = lodash;
@@ -47,6 +48,17 @@ export async function initialize(
} }
isInitialized = true; isInitialized = true;
if (!isProduction(app.getVersion())) {
setOnLogCallback((level, logLine, msgPrefix) => {
if (level >= LogLevel.Error) {
getMainWindow()?.webContents.send(
'logging-error',
`${msgPrefix ? `${msgPrefix}` : ''}${logLine}`
);
}
});
}
const basePath = app.getPath('userData'); const basePath = app.getPath('userData');
const logPath = join(basePath, 'logs'); const logPath = join(basePath, 'logs');
mkdirSync(logPath, { recursive: true }); mkdirSync(logPath, { recursive: true });

View File

@@ -54,6 +54,7 @@ export enum ToastType {
LeftGroup = 'LeftGroup', LeftGroup = 'LeftGroup',
LinkCopied = 'LinkCopied', LinkCopied = 'LinkCopied',
LoadingFullLogs = 'LoadingFullLogs', LoadingFullLogs = 'LoadingFullLogs',
_InternalMainProcessLoggingError = '_InternalMainProcessLoggingError',
MaxAttachments = 'MaxAttachments', MaxAttachments = 'MaxAttachments',
MediaNoLongerAvailable = 'MediaNoLongerAvailable', MediaNoLongerAvailable = 'MediaNoLongerAvailable',
MessageBodyTooLong = 'MessageBodyTooLong', MessageBodyTooLong = 'MessageBodyTooLong',
@@ -165,6 +166,10 @@ export type AnyToast =
| { toastType: ToastType.LeftGroup } | { toastType: ToastType.LeftGroup }
| { toastType: ToastType.LinkCopied } | { toastType: ToastType.LinkCopied }
| { toastType: ToastType.LoadingFullLogs } | { toastType: ToastType.LoadingFullLogs }
| {
toastType: ToastType._InternalMainProcessLoggingError;
parameters: { count: number; logLines: Array<string> };
}
| { toastType: ToastType.MaxAttachments } | { toastType: ToastType.MaxAttachments }
| { toastType: ToastType.MediaNoLongerAvailable } | { toastType: ToastType.MediaNoLongerAvailable }
| { toastType: ToastType.MessageBodyTooLong } | { toastType: ToastType.MessageBodyTooLong }

View File

@@ -4,7 +4,7 @@
import EventEmitter from 'node:events'; import EventEmitter from 'node:events';
import { ipcRenderer as ipc } from 'electron'; import { ipcRenderer as ipc } from 'electron';
import * as semver from 'semver'; import * as semver from 'semver';
import lodash from 'lodash'; import lodash, { throttle } from 'lodash';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import type { IPCType } from '../../window.d.ts'; import type { IPCType } from '../../window.d.ts';
@@ -35,6 +35,7 @@ import {
conversationJobQueue, conversationJobQueue,
conversationQueueJobEnum, conversationQueueJobEnum,
} from '../../jobs/conversationJobQueue.js'; } from '../../jobs/conversationJobQueue.js';
import { isEnabled } from '../../RemoteConfig.js';
const { groupBy, mapValues } = lodash; const { groupBy, mapValues } = lodash;
@@ -499,6 +500,56 @@ ipc.on('sql-error', () => {
}); });
}); });
let untoastedMainProcessErrorLogCount = 0;
let untoastedMainProcessErrorLogs: Array<string> = [];
const MAX_MAIN_PROCESS_ERROR_LOGS_TO_CACHE = 5;
ipc.on('logging-error', (_event, logLine) => {
if (isProduction(window.getVersion())) {
return;
}
if (!isEnabled('desktop.loggingErrorToasts')) {
return;
}
untoastedMainProcessErrorLogCount += 1;
const numCached = untoastedMainProcessErrorLogs.unshift(logLine);
if (numCached > MAX_MAIN_PROCESS_ERROR_LOGS_TO_CACHE) {
untoastedMainProcessErrorLogs.pop();
}
throttledHandleMainProcessErrors();
});
const throttledHandleMainProcessErrors = throttle(
_handleMainProcessErrors,
5000
);
function _handleMainProcessErrors() {
if (!window.reduxActions) {
// Try again in a bit!
throttledHandleMainProcessErrors();
return;
}
if (untoastedMainProcessErrorLogs.length === 0) {
return;
}
window.reduxActions.toast.showToast({
toastType: ToastType._InternalMainProcessLoggingError,
parameters: {
count: untoastedMainProcessErrorLogCount,
logLines: untoastedMainProcessErrorLogs,
},
});
untoastedMainProcessErrorLogCount = 0;
untoastedMainProcessErrorLogs = [];
}
ipc.on( ipc.on(
'art-creator:uploadStickerPack', 'art-creator:uploadStickerPack',
async ( async (