Skip to content

feat: Logging cleanup in packages (web, node, attachments, react, vue) #565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 24, 2025
5 changes: 5 additions & 0 deletions .changeset/calm-roses-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/node': patch
---

Using logger types from @powersync/common.
7 changes: 7 additions & 0 deletions .changeset/dirty-buttons-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@powersync/attachments': patch
'@powersync/react': patch
'@powersync/vue': patch
---

Using newly exposed logger from AbstractPowerSyncDatabase to have controlled logging instead of using console based logging.
5 changes: 5 additions & 0 deletions .changeset/fresh-jobs-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/common': minor
---

Exposing logger on AbstractPowerSyncDatabase.
5 changes: 5 additions & 0 deletions .changeset/green-rings-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/web': patch
---

Updated db and sync workers to respect log levels.
42 changes: 23 additions & 19 deletions packages/attachments/src/AbstractAttachmentQueue.ts
Original file line number Diff line number Diff line change
@@ -91,6 +91,10 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return this.options.powersync;
}

get logger() {
return this.powersync.logger ?? console;
}

protected get storage() {
return this.options.storage;
}
@@ -123,7 +127,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
async watchAttachmentIds() {
this.onAttachmentIdsChange(async (ids) => {
const _ids = `${ids.map((id) => `'${id}'`).join(',')}`;
console.debug(`Queuing for sync, attachment IDs: [${_ids}]`);
this.logger.debug(`Queuing for sync, attachment IDs: [${_ids}]`);

if (this.initialSync) {
this.initialSync = false;
@@ -151,11 +155,11 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
id: id,
state: AttachmentState.QUEUED_SYNC
});
console.debug(`Attachment (${id}) not found in database, creating new record`);
this.logger.debug(`Attachment (${id}) not found in database, creating new record`);
await this.saveToQueue(newRecord);
} else if (record.local_uri == null || !(await this.storage.fileExists(this.getLocalUri(record.local_uri)))) {
// 2. Attachment in database but no local file, mark as queued download
console.debug(`Attachment (${id}) found in database but no local file, marking as queued download`);
this.logger.debug(`Attachment (${id}) found in database but no local file, marking as queued download`);
await this.update({
...record,
state: AttachmentState.QUEUED_DOWNLOAD
@@ -241,7 +245,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
filename: record.filename
});
} catch (e) {
console.error(e);
this.logger.error(e);
}
}

@@ -267,7 +271,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
const localFilePathUri = this.getLocalUri(record.local_uri);
try {
if (!(await this.storage.fileExists(localFilePathUri))) {
console.warn(`File for ${record.id} does not exist, skipping upload`);
this.logger.warn(`File for ${record.id} does not exist, skipping upload`);
await this.update({
...record,
state: AttachmentState.QUEUED_DOWNLOAD
@@ -285,11 +289,11 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
});
// Mark as uploaded
await this.update({ ...record, state: AttachmentState.SYNCED });
console.debug(`Uploaded attachment "${record.id}" to Cloud Storage`);
this.logger.debug(`Uploaded attachment "${record.id}" to Cloud Storage`);
return true;
} catch (e: any) {
if (e.error == 'Duplicate') {
console.debug(`File already uploaded, marking ${record.id} as synced`);
this.logger.debug(`File already uploaded, marking ${record.id} as synced`);
await this.update({ ...record, state: AttachmentState.SYNCED });
return false;
}
@@ -300,7 +304,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return true;
}
}
console.error(`UploadAttachment error for record ${JSON.stringify(record, null, 2)}`);
this.logger.error(`UploadAttachment error for record ${JSON.stringify(record, null, 2)}`);
return false;
}
}
@@ -314,7 +318,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}
const localFilePathUri = this.getLocalUri(record.local_uri);
if (await this.storage.fileExists(localFilePathUri)) {
console.debug(`Local file already downloaded, marking "${record.id}" as synced`);
this.logger.debug(`Local file already downloaded, marking "${record.id}" as synced`);
await this.update({ ...record, state: AttachmentState.SYNCED });
return true;
}
@@ -345,7 +349,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
media_type: fileBlob.type,
state: AttachmentState.SYNCED
});
console.debug(`Downloaded attachment "${record.id}"`);
this.logger.debug(`Downloaded attachment "${record.id}"`);
return true;
} catch (e) {
if (this.options.onDownloadError) {
@@ -355,7 +359,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return true;
}
}
console.error(`Download attachment error for record ${JSON.stringify(record, null, 2)}`, e);
this.logger.error(`Download attachment error for record ${JSON.stringify(record, null, 2)}`, e);
}
return false;
}
@@ -396,7 +400,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
if (!record) {
return;
}
console.debug(`Uploading attachments...`);
this.logger.debug(`Uploading attachments...`);
while (record) {
const uploaded = await this.uploadAttachment(record);
if (!uploaded) {
@@ -405,9 +409,9 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}
record = await this.getNextUploadRecord();
}
console.debug('Finished uploading attachments');
this.logger.debug('Finished uploading attachments');
} catch (error) {
console.error('Upload failed:', error);
this.logger.error('Upload failed:', error);
} finally {
this.uploading = false;
}
@@ -464,7 +468,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =

this.downloading = true;
try {
console.debug(`Downloading ${this.downloadQueue.size} attachments...`);
this.logger.debug(`Downloading ${this.downloadQueue.size} attachments...`);
while (this.downloadQueue.size > 0) {
const id = this.downloadQueue.values().next().value;
this.downloadQueue.delete(id);
@@ -474,9 +478,9 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}
await this.downloadRecord(record);
}
console.debug('Finished downloading attachments');
this.logger.debug('Finished downloading attachments');
} catch (e) {
console.error('Downloads failed:', e);
this.logger.error('Downloads failed:', e);
} finally {
this.downloading = false;
}
@@ -518,7 +522,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return;
}

console.debug(`Deleting ${res.length} attachments from cache...`);
this.logger.debug(`Deleting ${res.length} attachments from cache...`);
await this.powersync.writeTransaction(async (tx) => {
for (const record of res) {
await this.delete(record, tx);
@@ -527,7 +531,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}

async clearQueue(): Promise<void> {
console.debug(`Clearing attachment queue...`);
this.logger.debug(`Clearing attachment queue...`);
await this.powersync.writeTransaction(async (tx) => {
await tx.execute(`DELETE FROM ${this.table}`);
});
34 changes: 19 additions & 15 deletions packages/common/src/client/AbstractPowerSyncDatabase.ts
Original file line number Diff line number Diff line change
@@ -406,6 +406,10 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
}

get logger() {
return this.options.logger!;
}

/**
* Wait for initialization to complete.
* While initializing is automatic, this helps to catch and report initialization errors.
@@ -555,7 +559,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* This method does include transaction ids in the result, but does not group
* data by transaction. One batch may contain data from multiple transactions,
* and a single transaction may be split over multiple batches.
*
*
* @param limit Maximum number of CRUD entries to include in the batch
* @returns A batch of CRUD operations to upload, or null if there are none
*/
@@ -594,7 +598,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
*
* Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
* All data for the transaction is loaded into memory.
*
*
* @returns A transaction of CRUD operations to upload, or null if there are none
*/
async getNextCrudTransaction(): Promise<CrudTransaction | null> {
@@ -633,7 +637,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Get an unique client id for this database.
*
* The id is not reset when the database is cleared, only when the database is deleted.
*
*
* @returns A unique identifier for the database instance
*/
async getClientId(): Promise<string> {
@@ -661,7 +665,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
/**
* Execute a SQL write (INSERT/UPDATE/DELETE) query
* and optionally return results.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The query result as an object with structured key-value pairs
@@ -674,7 +678,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
/**
* Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
* This bypasses certain PowerSync abstractions and is useful for accessing the raw database results.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The raw query result from the underlying database as a nested array of raw values, where each row is
@@ -689,7 +693,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
* and optionally return results.
* This is faster than executing separately with each parameter set.
*
*
* @param sql The SQL query to execute
* @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
* @returns The query result
@@ -701,7 +705,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB

/**
* Execute a read-only query and return results.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns An array of results
@@ -713,7 +717,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB

/**
* Execute a read-only query and return the first result, or null if the ResultSet is empty.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The first result if found, or null if no results are returned
@@ -725,7 +729,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB

/**
* Execute a read-only query and return the first result, error if the ResultSet is empty.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The first result matching the query
@@ -761,7 +765,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Open a read-only transaction.
* Read transactions can run concurrently to a write transaction.
* Changes from any write transaction are not visible to read transactions started before it.
*
*
* @param callback Function to execute within the transaction
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
* @returns The result of the callback
@@ -786,7 +790,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Open a read-write transaction.
* This takes a global lock - only one write transaction can execute against the database at a time.
* Statements within the transaction must be done on the provided {@link Transaction} interface.
*
*
* @param callback Function to execute within the transaction
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
* @returns The result of the callback
@@ -865,7 +869,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
*
* Note that the `onChange` callback member of the handler is required.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @param handler Callbacks for handling results and errors
@@ -915,7 +919,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Execute a read query every time the source tables are modified.
* Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @param options Options for configuring watch behavior
@@ -944,7 +948,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Resolves the list of tables that are used in a SQL query.
* If tables are specified in the options, those are used directly.
* Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed.
*
*
* @param sql The SQL query to analyze
* @param parameters Optional parameters for the SQL query
* @param options Optional watch options that may contain explicit table list
@@ -1077,7 +1081,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
*
* This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed
* together when data is changed.
*
*
* Note: do not declare this as `async *onChange` as it will not work in React Native.
*
* @param options Options for configuring watch behavior
3 changes: 1 addition & 2 deletions packages/node/src/sync/stream/NodeRemote.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as os from 'node:os';

import { ILogger } from 'js-logger';

import {
type ILogger,
AbstractRemote,
AbstractRemoteOptions,
BSONImplementation,
8 changes: 6 additions & 2 deletions packages/react/src/WatchedQuery.ts
Original file line number Diff line number Diff line change
@@ -46,6 +46,10 @@ export class WatchedQuery extends BaseObserver<WatchedQueryListener> implements
});
}

get logger() {
return this.db.logger ?? console;
}

addTemporaryHold() {
const ref = new Object();
this.temporaryHolds.add(ref);
@@ -88,7 +92,7 @@ export class WatchedQuery extends BaseObserver<WatchedQueryListener> implements
try {
this.tables = await this.db.resolveTables(this.query.sqlStatement, this.query.queryParameters, this.options);
} catch (e) {
console.error('Failed to fetch tables:', e);
this.logger.error('Failed to fetch tables:', e);
this.setError(e);
}
}
@@ -103,7 +107,7 @@ export class WatchedQuery extends BaseObserver<WatchedQueryListener> implements
const data = result ?? [];
this.setData(data);
} catch (e) {
console.error('Failed to fetch data:', e);
this.logger.error('Failed to fetch data:', e);
this.setError(e);
}
}
Loading