diff --git a/.changeset/calm-roses-poke.md b/.changeset/calm-roses-poke.md new file mode 100644 index 000000000..e87a34ff1 --- /dev/null +++ b/.changeset/calm-roses-poke.md @@ -0,0 +1,5 @@ +--- +'@powersync/node': patch +--- + +Using logger types from @powersync/common. diff --git a/.changeset/dirty-buttons-hunt.md b/.changeset/dirty-buttons-hunt.md new file mode 100644 index 000000000..cabfca0e0 --- /dev/null +++ b/.changeset/dirty-buttons-hunt.md @@ -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. diff --git a/.changeset/fresh-jobs-explain.md b/.changeset/fresh-jobs-explain.md new file mode 100644 index 000000000..6e9721717 --- /dev/null +++ b/.changeset/fresh-jobs-explain.md @@ -0,0 +1,5 @@ +--- +'@powersync/common': minor +--- + +Exposing logger on AbstractPowerSyncDatabase. diff --git a/.changeset/green-rings-judge.md b/.changeset/green-rings-judge.md new file mode 100644 index 000000000..7f24b955b --- /dev/null +++ b/.changeset/green-rings-judge.md @@ -0,0 +1,5 @@ +--- +'@powersync/web': patch +--- + +Updated db and sync workers to respect log levels. diff --git a/packages/attachments/src/AbstractAttachmentQueue.ts b/packages/attachments/src/AbstractAttachmentQueue.ts index 888d38a53..b056007f1 100644 --- a/packages/attachments/src/AbstractAttachmentQueue.ts +++ b/packages/attachments/src/AbstractAttachmentQueue.ts @@ -91,6 +91,10 @@ export abstract class AbstractAttachmentQueue { 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 0) { const id = this.downloadQueue.values().next().value; this.downloadQueue.delete(id); @@ -474,9 +478,9 @@ export abstract class AbstractAttachmentQueue { for (const record of res) { await this.delete(record, tx); @@ -527,7 +531,7 @@ export abstract class AbstractAttachmentQueue { - console.debug(`Clearing attachment queue...`); + this.logger.debug(`Clearing attachment queue...`); await this.powersync.writeTransaction(async (tx) => { await tx.execute(`DELETE FROM ${this.table}`); }); diff --git a/packages/common/src/client/AbstractPowerSyncDatabase.ts b/packages/common/src/client/AbstractPowerSyncDatabase.ts index 2e71083e7..c56507359 100644 --- a/packages/common/src/client/AbstractPowerSyncDatabase.ts +++ b/packages/common/src/client/AbstractPowerSyncDatabase.ts @@ -406,6 +406,10 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver 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 { @@ -633,7 +637,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver { @@ -661,7 +665,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver 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 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 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); } } diff --git a/packages/react/src/hooks/useQuery.ts b/packages/react/src/hooks/useQuery.ts index b5701027d..b7400eaea 100644 --- a/packages/react/src/hooks/useQuery.ts +++ b/packages/react/src/hooks/useQuery.ts @@ -42,6 +42,7 @@ export const useQuery = ( options: AdditionalOptions = { runQueryOnce: false } ): QueryResult => { const powerSync = usePowerSync(); + const logger = powerSync?.logger ?? console; if (!powerSync) { return { isLoading: false, isFetching: false, data: [], error: new Error('PowerSync not configured.') }; } @@ -50,7 +51,7 @@ export const useQuery = ( try { parsedQuery = parseQuery(query, parameters); } catch (error) { - console.error('Failed to parse query:', error); + logger.error('Failed to parse query:', error); return { isLoading: false, isFetching: false, data: [], error }; } @@ -107,7 +108,7 @@ export const useQuery = ( handleResult(result); } catch (e) { - console.error('Failed to fetch data:', e); + logger.error('Failed to fetch data:', e); handleError(e); } }; @@ -122,7 +123,7 @@ export const useQuery = ( setTables(tables); } catch (e) { - console.error('Failed to fetch tables:', e); + logger.error('Failed to fetch tables:', e); handleError(e); } }; diff --git a/packages/vue/src/composables/useQuery.ts b/packages/vue/src/composables/useQuery.ts index 57295e65a..2a5d32f25 100644 --- a/packages/vue/src/composables/useQuery.ts +++ b/packages/vue/src/composables/useQuery.ts @@ -65,6 +65,7 @@ export const useQuery = ( let fetchData: () => Promise | undefined; const powerSync = usePowerSync(); + const logger = powerSync?.value?.logger ?? console; const finishLoading = () => { isLoading.value = false; @@ -99,7 +100,7 @@ export const useQuery = ( const result = await executor(); handleResult(result); } catch (e) { - console.error('Failed to fetch data:', e); + logger.error('Failed to fetch data:', e); handleError(e); } }; @@ -114,7 +115,7 @@ export const useQuery = ( try { parsedQuery = parseQuery(queryValue, toValue(sqlParameters).map(toValue)); } catch (e) { - console.error('Failed to parse query:', e); + logger.error('Failed to parse query:', e); handleError(e); return; } @@ -125,7 +126,7 @@ export const useQuery = ( try { resolvedTables = await powerSync.value.resolveTables(sql, parameters, options); } catch (e) { - console.error('Failed to fetch tables:', e); + logger.error('Failed to fetch tables:', e); handleError(e); return; } diff --git a/packages/web/src/db/adapters/AbstractWebSQLOpenFactory.ts b/packages/web/src/db/adapters/AbstractWebSQLOpenFactory.ts index 0f4e6647f..faa252d85 100644 --- a/packages/web/src/db/adapters/AbstractWebSQLOpenFactory.ts +++ b/packages/web/src/db/adapters/AbstractWebSQLOpenFactory.ts @@ -25,7 +25,7 @@ export abstract class AbstractWebSQLOpenFactory implements SQLOpenFactory { resolvedFlags: { disableSSRWarning, enableMultiTabs, ssrMode = isServerSide() } } = this; if (ssrMode && !disableSSRWarning) { - console.warn( + this.logger.warn( ` Running PowerSync in SSR mode. Only empty query results will be returned. @@ -34,7 +34,7 @@ export abstract class AbstractWebSQLOpenFactory implements SQLOpenFactory { } if (!enableMultiTabs) { - console.warn( + this.logger.warn( 'Multiple tab support is not enabled. Using this site across multiple tabs may not function correctly.' ); } diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts index 6217b35dc..129a3b130 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts @@ -1,4 +1,4 @@ -import { DBAdapter } from '@powersync/common'; +import { type ILogLevel, DBAdapter } from '@powersync/common'; import * as Comlink from 'comlink'; import { openWorkerDatabasePort, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database'; import { AbstractWebSQLOpenFactory } from '../AbstractWebSQLOpenFactory'; @@ -20,6 +20,11 @@ export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions { export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions { vfs: WASQLiteVFS; } + +export interface WorkerDBOpenerOptions extends ResolvedWASQLiteOpenFactoryOptions { + logLevel: ILogLevel; +} + /** * Opens a SQLite connection using WA-SQLite. */ @@ -73,7 +78,7 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory { ) : openWorkerDatabasePort(this.options.dbFilename, enableMultiTabs, optionsDbWorker, this.waOptions.vfs); - const workerDBOpener = Comlink.wrap>(workerPort); + const workerDBOpener = Comlink.wrap>(workerPort); return new WorkerWrappedAsyncDatabaseConnection({ remote: workerDBOpener, @@ -83,7 +88,8 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory { temporaryStorage, cacheSizeKb, flags: this.resolvedFlags, - encryptionKey: encryptionKey + encryptionKey: encryptionKey, + logLevel: this.logger.getLevel() }), identifier: this.options.dbFilename, onClose: () => { diff --git a/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts b/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts index 916541d9f..dadb1276c 100644 --- a/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts +++ b/packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts @@ -135,6 +135,8 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem } this.syncManager = Comlink.wrap(this.messagePort); + this.syncManager.setLogLevel(this.logger.getLevel()); + this.triggerCrudUpload = this.syncManager.triggerCrudUpload; /** diff --git a/packages/web/src/db/sync/WebStreamingSyncImplementation.ts b/packages/web/src/db/sync/WebStreamingSyncImplementation.ts index 05d60f0a2..10f8905d4 100644 --- a/packages/web/src/db/sync/WebStreamingSyncImplementation.ts +++ b/packages/web/src/db/sync/WebStreamingSyncImplementation.ts @@ -1,6 +1,7 @@ import { AbstractStreamingSyncImplementation, AbstractStreamingSyncImplementationOptions, + createLogger, LockOptions, LockType } from '@powersync/common'; @@ -26,7 +27,9 @@ export class WebStreamingSyncImplementation extends AbstractStreamingSyncImpleme obtainLock(lockOptions: LockOptions): Promise { const identifier = `streaming-sync-${lockOptions.type}-${this.webOptions.identifier}`; - lockOptions.type == LockType.SYNC && console.debug('requesting lock for ', identifier); + if (lockOptions.type == LockType.SYNC) { + this.logger.debug('requesting lock for ', identifier); + } return getNavigatorLocks().request(identifier, { signal: lockOptions.signal }, lockOptions.callback); } } diff --git a/packages/web/src/worker/db/WASQLiteDB.worker.ts b/packages/web/src/worker/db/WASQLiteDB.worker.ts index 7e28b024d..2ffd8be89 100644 --- a/packages/web/src/worker/db/WASQLiteDB.worker.ts +++ b/packages/web/src/worker/db/WASQLiteDB.worker.ts @@ -6,8 +6,16 @@ import '@journeyapps/wa-sqlite'; import * as Comlink from 'comlink'; import { AsyncDatabaseConnection } from '../../db/adapters/AsyncDatabaseConnection'; import { WASqliteConnection } from '../../db/adapters/wa-sqlite/WASQLiteConnection'; -import { ResolvedWASQLiteOpenFactoryOptions } from '../../db/adapters/wa-sqlite/WASQLiteOpenFactory'; +import { + ResolvedWASQLiteOpenFactoryOptions, + WorkerDBOpenerOptions +} from '../../db/adapters/wa-sqlite/WASQLiteOpenFactory'; import { getNavigatorLocks } from '../../shared/navigator'; +import { createBaseLogger, createLogger } from '@powersync/common'; + +const baseLogger = createBaseLogger(); +baseLogger.useDefaults(); +const logger = createLogger('db-worker'); /** * Keeps track of open DB connections and the clients which @@ -39,11 +47,14 @@ const openWorkerConnection = async (options: ResolvedWASQLiteOpenFactoryOptions) }; }; -const openDBShared = async (options: ResolvedWASQLiteOpenFactoryOptions): Promise => { +const openDBShared = async (options: WorkerDBOpenerOptions): Promise => { // Prevent multiple simultaneous opens from causing race conditions return getNavigatorLocks().request(OPEN_DB_LOCK, async () => { const clientId = nextClientId++; - const { dbFilename } = options; + const { dbFilename, logLevel } = options; + + logger.setLevel(logLevel); + if (!DBMap.has(dbFilename)) { const clientIds = new Set(); const connection = await openWorkerConnection(options); @@ -65,14 +76,14 @@ const openDBShared = async (options: ResolvedWASQLiteOpenFactoryOptions): Promis }), close: Comlink.proxy(() => { const { clientIds } = dbEntry; - console.debug(`Close requested from client ${clientId} of ${[...clientIds]}`); + logger.debug(`Close requested from client ${clientId} of ${[...clientIds]}`); clientIds.delete(clientId); if (clientIds.size == 0) { - console.debug(`Closing connection to ${dbFilename}.`); + logger.debug(`Closing connection to ${dbFilename}.`); DBMap.delete(dbFilename); return db.close?.(); } - console.debug(`Connection to ${dbFilename} not closed yet due to active clients.`); + logger.debug(`Connection to ${dbFilename} not closed yet due to active clients.`); return; }) }; @@ -86,7 +97,6 @@ if (typeof SharedWorkerGlobalScope !== 'undefined') { const _self: SharedWorkerGlobalScope = self as any; _self.onconnect = function (event: MessageEvent) { const port = event.ports[0]; - console.debug('Exposing shared db on port', port); Comlink.expose(openDBShared, port); }; } else { diff --git a/packages/web/src/worker/sync/BroadcastLogger.ts b/packages/web/src/worker/sync/BroadcastLogger.ts index 2cb6b1d1e..01b015489 100644 --- a/packages/web/src/worker/sync/BroadcastLogger.ts +++ b/packages/web/src/worker/sync/BroadcastLogger.ts @@ -13,6 +13,8 @@ export class BroadcastLogger implements ILogger { ERROR: ILogLevel; OFF: ILogLevel; + private currentLevel: ILogLevel = LogLevel.INFO; + constructor(protected clients: WrappedSyncPort[]) { this.TRACE = LogLevel.TRACE; this.DEBUG = LogLevel.DEBUG; @@ -24,63 +26,86 @@ export class BroadcastLogger implements ILogger { } trace(...x: any[]): void { + if (!this.enabledFor(this.TRACE)) return; + console.trace(...x); const sanitized = this.sanitizeArgs(x); this.iterateClients((client) => client.clientProvider.trace(...sanitized)); } debug(...x: any[]): void { + if (!this.enabledFor(this.DEBUG)) return; + console.debug(...x); const sanitized = this.sanitizeArgs(x); this.iterateClients((client) => client.clientProvider.debug(...sanitized)); } info(...x: any[]): void { + if (!this.enabledFor(this.INFO)) return; + console.info(...x); const sanitized = this.sanitizeArgs(x); this.iterateClients((client) => client.clientProvider.info(...sanitized)); } log(...x: any[]): void { + if (!this.enabledFor(this.INFO)) return; + console.log(...x); const sanitized = this.sanitizeArgs(x); this.iterateClients((client) => client.clientProvider.log(...sanitized)); } warn(...x: any[]): void { + if (!this.enabledFor(this.WARN)) return; + console.warn(...x); const sanitized = this.sanitizeArgs(x); this.iterateClients((client) => client.clientProvider.warn(...sanitized)); } error(...x: any[]): void { + if (!this.enabledFor(this.ERROR)) return; + console.error(...x); const sanitized = this.sanitizeArgs(x); this.iterateClients((client) => client.clientProvider.error(...sanitized)); } time(label: string): void { + if (!this.enabledFor(this.TIME)) return; + console.time(label); this.iterateClients((client) => client.clientProvider.time(label)); } timeEnd(label: string): void { + if (!this.enabledFor(this.TIME)) return; + console.timeEnd(label); this.iterateClients((client) => client.clientProvider.timeEnd(label)); } + /** + * Set the global log level. + */ setLevel(level: ILogLevel): void { - // Levels are not adjustable on this level. + this.currentLevel = level; } + /** + * Get the current log level. + */ getLevel(): ILogLevel { - // Levels are not adjustable on this level. - return LogLevel.INFO; + return this.currentLevel; } + /** + * Returns true if the given level is enabled. + */ enabledFor(level: ILogLevel): boolean { - // Levels are not adjustable on this level. - return true; + return level.value >= this.currentLevel.value; } /** diff --git a/packages/web/src/worker/sync/SharedSyncImplementation.ts b/packages/web/src/worker/sync/SharedSyncImplementation.ts index 13ec31709..65c0504e4 100644 --- a/packages/web/src/worker/sync/SharedSyncImplementation.ts +++ b/packages/web/src/worker/sync/SharedSyncImplementation.ts @@ -1,6 +1,7 @@ import { type AbstractStreamingSyncImplementation, type ILogger, + type ILogLevel, type LockOptions, type PowerSyncConnectionOptions, type StreamingSyncImplementation, @@ -146,6 +147,11 @@ export class SharedSyncImplementation return this.isInitialized; } + setLogLevel(level: ILogLevel) { + this.logger.setLevel(level); + this.broadCastLogger.setLevel(level); + } + /** * Configures the DBAdapter connection and a streaming sync client. */ @@ -235,7 +241,7 @@ export class SharedSyncImplementation async removePort(port: MessagePort) { const index = this.ports.findIndex((p) => p.port == port); if (index < 0) { - console.warn(`Could not remove port ${port} since it is not present in active ports.`); + this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`); return; } @@ -312,7 +318,7 @@ export class SharedSyncImplementation abortController.signal.onabort = reject; try { - console.log('calling the last port client provider for credentials'); + this.logger.log('calling the last port client provider for credentials'); resolve(await lastPort.clientProvider.fetchCredentials()); } catch (ex) { reject(ex); diff --git a/packages/web/src/worker/sync/SharedSyncImplementation.worker.ts b/packages/web/src/worker/sync/SharedSyncImplementation.worker.ts index 9716aa4d9..ad7644456 100644 --- a/packages/web/src/worker/sync/SharedSyncImplementation.worker.ts +++ b/packages/web/src/worker/sync/SharedSyncImplementation.worker.ts @@ -22,7 +22,6 @@ _self.onconnect = function (event: MessageEvent) { port.addEventListener('message', (event) => { const payload = event.data as ManualSharedSyncPayload; if (payload?.event == SharedSyncClientEvent.CLOSE_CLIENT) { - console.log('closing shared for port', port); sharedSyncImplementation.removePort(port); } });