Skip to content

Commit 0fb597f

Browse files
authored
Merge pull request #771 from callstack-internal/feat/87780-improve-logging
feat: Improve retryOperation and reportStorageQuota logging to include original error
2 parents 8ae75ea + e08c160 commit 0fb597f

2 files changed

Lines changed: 56 additions & 8 deletions

File tree

lib/OnyxUtils.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -763,13 +763,13 @@ function remove<TKey extends OnyxKey>(key: TKey, isProcessingCollectionUpdate?:
763763
return Storage.removeItem(key).then(() => undefined);
764764
}
765765

766-
function reportStorageQuota(): Promise<void> {
766+
function reportStorageQuota(error?: Error): Promise<void> {
767767
return Storage.getDatabaseSize()
768768
.then(({bytesUsed, bytesRemaining}) => {
769-
Logger.logInfo(`Storage Quota Check -- bytesUsed: ${bytesUsed} bytesRemaining: ${bytesRemaining}`);
769+
Logger.logInfo(`Storage Quota Check -- bytesUsed: ${bytesUsed} bytesRemaining: ${bytesRemaining}. Original error: ${error}`);
770770
})
771771
.catch((dbSizeError) => {
772-
Logger.logAlert(`Unable to get database size. Error: ${dbSizeError}`);
772+
Logger.logAlert(`Unable to get database size. getDatabaseSize error: ${dbSizeError}. Original error: ${error}`);
773773
});
774774
}
775775

@@ -786,7 +786,7 @@ function retryOperation<TMethod extends RetriableOnyxOperation>(error: Error, on
786786
Logger.logInfo(`Failed to save to storage. Error: ${error}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${currentRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`);
787787

788788
if (error && Str.startsWith(error.message, "Failed to execute 'put' on 'IDBObjectStore'")) {
789-
Logger.logAlert('Attempted to set invalid data set in Onyx. Please ensure all data is serializable.');
789+
Logger.logAlert(`Attempted to set invalid data set in Onyx. Please ensure all data is serializable. Error: ${error}`);
790790
throw error;
791791
}
792792

@@ -810,13 +810,13 @@ function retryOperation<TMethod extends RetriableOnyxOperation>(error: Error, on
810810
// If we have no acceptable keys to remove then we are possibly trying to save mission critical data. If this is the case,
811811
// then we should stop retrying as there is not much the user can do to fix this. Instead of getting them stuck in an infinite loop we
812812
// will allow this write to be skipped.
813-
Logger.logAlert('Out of storage. But found no acceptable keys to remove.');
814-
return reportStorageQuota();
813+
Logger.logAlert(`Out of storage. But found no acceptable keys to remove. Error: ${error}`);
814+
return reportStorageQuota(error);
815815
}
816816

817817
// Remove the least recently accessed key and retry.
818-
Logger.logInfo(`Out of storage. Evicting least recently accessed key (${keyForRemoval}) and retrying.`);
819-
reportStorageQuota();
818+
Logger.logInfo(`Out of storage. Evicting least recently accessed key (${keyForRemoval}) and retrying. Error: ${error}`);
819+
reportStorageQuota(error);
820820

821821
// @ts-expect-error No overload matches this call.
822822
return remove(keyForRemoval).then(() => onyxMethod(defaultParams, nextRetryAttempt));

tests/unit/onyxUtilsTest.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import utils from '../../lib/utils';
66
import type {Collection, OnyxCollection} from '../../lib/types';
77
import type GenericCollection from '../utils/GenericCollection';
88
import OnyxCache from '../../lib/OnyxCache';
9+
import * as Logger from '../../lib/Logger';
910
import StorageMock from '../../lib/storage';
1011
import createDeferredTask from '../../lib/createDeferredTask';
1112
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
@@ -463,6 +464,37 @@ describe('OnyxUtils', () => {
463464
expect(retryOperationSpy).toHaveBeenCalledTimes(1);
464465
});
465466

467+
it('should include the error in logAlert for IDBObjectStore invalid data errors', async () => {
468+
const logAlertSpy = jest.spyOn(Logger, 'logAlert');
469+
StorageMock.setItem = jest.fn().mockRejectedValueOnce(invalidDataError);
470+
471+
await expect(Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'})).rejects.toThrow(invalidDataError);
472+
473+
expect(logAlertSpy).toHaveBeenCalledWith(`Attempted to set invalid data set in Onyx. Please ensure all data is serializable. Error: ${invalidDataError}`);
474+
});
475+
476+
it('should include the error in logs when out of storage with no evictable keys', async () => {
477+
const logAlertSpy = jest.spyOn(Logger, 'logAlert');
478+
const logInfoSpy = jest.spyOn(Logger, 'logInfo');
479+
StorageMock.setItem = jest.fn().mockRejectedValue(diskFullError);
480+
481+
await Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'});
482+
483+
expect(logAlertSpy).toHaveBeenCalledWith(`Out of storage. But found no acceptable keys to remove. Error: ${diskFullError}`);
484+
expect(logInfoSpy).toHaveBeenCalledWith(`Storage Quota Check -- bytesUsed: 0 bytesRemaining: Infinity. Original error: ${diskFullError}`);
485+
});
486+
487+
it('should include the error in logAlert when out of storage and getDatabaseSize fails', async () => {
488+
const dbSizeError = new Error('Failed to estimate storage');
489+
const logAlertSpy = jest.spyOn(Logger, 'logAlert');
490+
StorageMock.setItem = jest.fn().mockRejectedValue(diskFullError);
491+
StorageMock.getDatabaseSize = jest.fn().mockRejectedValue(dbSizeError);
492+
493+
await Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'});
494+
495+
expect(logAlertSpy).toHaveBeenCalledWith(`Unable to get database size. getDatabaseSize error: ${dbSizeError}. Original error: ${diskFullError}`);
496+
});
497+
466498
it('should not re-add an evicted key to recentlyAccessedKeys after removal', async () => {
467499
// Re-init with evictable keys so getKeyForEviction() has something to return
468500
Object.assign(OnyxUtils.getDeferredInitTask(), createDeferredTask());
@@ -490,6 +522,7 @@ describe('OnyxUtils', () => {
490522
let LocalOnyxUtils: typeof OnyxUtils;
491523
let LocalOnyxCache: typeof OnyxCache;
492524
let LocalStorageMock: typeof StorageMock;
525+
let LocalLogger: typeof Logger;
493526

494527
// Reset all modules to get fresh singletons (OnyxCache, OnyxUtils, etc.)
495528
// then re-init Onyx with evictableKeys configured
@@ -500,6 +533,7 @@ describe('OnyxUtils', () => {
500533
LocalOnyxUtils = require('../../lib/OnyxUtils').default;
501534
LocalOnyxCache = require('../../lib/OnyxCache').default;
502535
LocalStorageMock = require('../../lib/storage').default;
536+
LocalLogger = require('../../lib/Logger');
503537

504538
LocalOnyx.init({
505539
keys: ONYXKEYS,
@@ -605,6 +639,20 @@ describe('OnyxUtils', () => {
605639
expect(keyForEviction).toBeDefined();
606640
expect(keyForEviction?.startsWith(ONYXKEYS.COLLECTION.TEST_KEY)).toBe(true);
607641
});
642+
643+
it('should include the error in logs when evicting a key', async () => {
644+
const logInfoSpy = jest.spyOn(LocalLogger, 'logInfo');
645+
const key1 = `${ONYXKEYS.COLLECTION.TEST_KEY}1`;
646+
647+
await LocalOnyx.set(key1, {id: 1});
648+
649+
LocalStorageMock.setItem = jest.fn(LocalStorageMock.setItem).mockRejectedValueOnce(diskFullError).mockImplementation(LocalStorageMock.setItem);
650+
651+
await LocalOnyx.set(ONYXKEYS.TEST_KEY, {test: 'data'});
652+
653+
expect(logInfoSpy).toHaveBeenCalledWith(`Out of storage. Evicting least recently accessed key (${key1}) and retrying. Error: ${diskFullError}`);
654+
expect(logInfoSpy).toHaveBeenCalledWith(`Storage Quota Check -- bytesUsed: 0 bytesRemaining: Infinity. Original error: ${diskFullError}`);
655+
});
608656
});
609657

610658
describe('afterInit', () => {

0 commit comments

Comments
 (0)