Skip to content

Commit

Permalink
chore: debug logging improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
gmaclennan committed Oct 3, 2024
1 parent bad741b commit 8e59881
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 69 deletions.
29 changes: 22 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"random-access-file": "^4.0.7",
"random-access-memory": "^6.2.1",
"rimraf": "^5.0.5",
"supports-color": "^9.4.0",
"tempy": "^3.1.0",
"ts-proto": "^1.156.7",
"typedoc": "^0.26.7",
Expand Down
23 changes: 13 additions & 10 deletions src/core-manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const kCoreManagerReplicate = Symbol('replicate core manager')
export class CoreManager extends TypedEmitter {
#corestore
#coreIndex
/** @type {Core} */
#creatorCore
/** @type {CoreRecord} */
#creatorCoreRecord
#projectKey
#queries
#encryptionKeys
Expand Down Expand Up @@ -128,20 +128,20 @@ export class CoreManager extends TypedEmitter {
}
const writer = this.#addCore(keyPair, namespace)
if (namespace === 'auth' && projectSecretKey) {
this.#creatorCore = writer.core
this.#creatorCoreRecord = writer
}
}

// For anyone other than the project creator, creatorCore is readonly
this.#creatorCore ??= this.#addCore({ publicKey: projectKey }, 'auth').core
this.#creatorCoreRecord ??= this.#addCore({ publicKey: projectKey }, 'auth')

// Load persisted cores
const rows = db.select().from(coresTable).all()
for (const { publicKey, namespace } of rows) {
this.#addCore({ publicKey }, namespace)
}

this.#projectExtension = this.#creatorCore.registerExtension(
this.#projectExtension = this.creatorCore.registerExtension(
'mapeo/project',
{
encoding: ProjectExtensionCodec,
Expand All @@ -151,14 +151,14 @@ export class CoreManager extends TypedEmitter {
}
)

this.#haveExtension = this.#creatorCore.registerExtension('mapeo/have', {
this.#haveExtension = this.creatorCore.registerExtension('mapeo/have', {
encoding: HaveExtensionCodec,
onmessage: (msg, peer) => {
this.#handleHaveMessage(msg, peer)
},
})

this.#creatorCore.on('peer-add', (peer) => {
this.creatorCore.on('peer-add', (peer) => {
this.#sendHaves(peer, this.#coreIndex).catch(() => {
this.#l.log('Failed to send pre-haves to newly-connected peer')
})
Expand All @@ -179,7 +179,11 @@ export class CoreManager extends TypedEmitter {
}

get creatorCore() {
return this.#creatorCore
return this.#creatorCoreRecord.core
}

get creatorCoreRecord() {
return this.#creatorCoreRecord
}

/**
Expand Down Expand Up @@ -255,7 +259,6 @@ export class CoreManager extends TypedEmitter {
* @returns {CoreRecord}
*/
addCore(key, namespace) {
this.#l.log('Adding remote core %k to %s', key, namespace)
return this.#addCore({ publicKey: key }, namespace, true)
}

Expand Down Expand Up @@ -298,7 +301,7 @@ export class CoreManager extends TypedEmitter {

if (writer) {
const sendHaves = debounce(WRITER_CORE_PREHAVES_DEBOUNCE_DELAY, () => {
for (const peer of this.#creatorCore.peers) {
for (const peer of this.creatorCore.peers) {
this.#sendHaves(peer, [{ core, namespace }]).catch(() => {
this.#l.log('Failed to send new pre-haves to other peers')
})
Expand Down
2 changes: 1 addition & 1 deletion src/index-writer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class IndexWriter {
const indexer = this.#indexers.get(schemaName)
if (!indexer) continue // Won't happen, but TS doesn't know that
indexer.batch(docs)
if (this.#l.enabled) {
if (this.#l.log.enabled) {
for (const doc of docs) {
this.#l.log(
'Indexed %s %S @ %S',
Expand Down
37 changes: 28 additions & 9 deletions src/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ import util from 'util'

const TRIM = 7

const selectColorOriginal = createDebug.selectColor

/**
* Selects a color for a debug namespace (warning: overrides private api).
* Rather than the default behaviour of creating a unique color for each
* namespace, we only hash the last 7 characters of the namespace, which is the
* deviceId. This results in debug output where each deviceId has a different
* colour, which is more useful for debugging.
* @param {string} namespace The namespace string for the debug instance to be colored
* @return {number|string} An ANSI color code for the given namespace
*/
createDebug.selectColor = function (namespace) {
if (!namespace.startsWith('mapeo:')) {
return selectColorOriginal(namespace)
}
let hash = 0

for (let i = namespace.length - TRIM - 1; i < namespace.length; i++) {
hash = (hash << 5) - hash + namespace.charCodeAt(i)
hash |= 0 // Convert to 32bit integer
}

// @ts-expect-error - private debug api
return createDebug.colors[Math.abs(hash) % createDebug.colors.length]
}

createDebug.formatters.h = function (v) {
if (!Buffer.isBuffer(v)) return '[undefined]'
return v.toString('hex').slice(0, TRIM)
Expand Down Expand Up @@ -76,15 +102,8 @@ export class Logger {
this.#baseLogger = baseLogger || createDebug('mapeo' + (ns ? `:${ns}` : ''))
this.#log = this.#baseLogger.extend(this.deviceId.slice(0, TRIM))
}
get enabled() {
return this.#log.enabled
}

/**
* @param {Parameters<createDebug.Debugger>} args
*/
log = (...args) => {
this.#log.apply(this, args)
get log() {
return this.#log
}
/**
*
Expand Down
8 changes: 8 additions & 0 deletions src/mapeo-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,14 @@ export class MapeoManager extends TypedEmitter {
isConfigSynced
) {
return true
} else {
this.#l.log(
'Pending initial sync: role %s, projectSettings %o, auth %o, config %o',
isRoleSynced,
isProjectSettingsSynced,
isAuthSynced,
isConfigSynced
)
}
return new Promise((resolve, reject) => {
/** @param {import('./sync/sync-state.js').State} syncState */
Expand Down
1 change: 0 additions & 1 deletion src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,6 @@ export class MapeoProject extends TypedEmitter {
await this.#dataTypes.projectSettings.getByDocId(this.#projectId)
)
} catch (e) {
this.#l.log('No project settings')
return /** @type {EditableProjectSettings} */ (EMPTY_PROJECT_SETTINGS)
}
}
Expand Down
92 changes: 55 additions & 37 deletions src/sync/peer-sync-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class PeerSyncController {
#downloadingRanges = new Map()
/** @type {SyncStatus} */
#prevSyncStatus = createNamespaceMap('unknown')
/** @type {import('debug').Debugger} */
#log

/**
Expand All @@ -43,25 +44,27 @@ export class PeerSyncController {
* @param {Logger} [opts.logger]
*/
constructor({ protomux, coreManager, syncState, roles, logger }) {
/**
* @param {string} formatter
* @param {unknown[]} args
* @returns {void}
*/
this.#log = (formatter, ...args) => {
const log = Logger.create('peer', logger).log
return log.apply(null, [
`[%h] ${formatter}`,
protomux.stream.remotePublicKey,
...args,
])
}
const log = Logger.create('peer', logger).log
this.#log = Object.assign(
/**
* @param {any} formatter
* @param {...any} args
*/
(formatter, ...args) => {
return log.apply(null, [
`[%h] ${formatter}`,
protomux.stream.remotePublicKey,
...args,
])
},
log
)
this.#coreManager = coreManager
this.#protomux = protomux
this.#roles = roles

// Always need to replicate the project creator core
this.#replicateCore(coreManager.creatorCore)
this.#replicateCoreRecord(coreManager.creatorCoreRecord)

coreManager.on('add-core', this.#handleAddCore)
syncState.on('state', this.#handleStateChange)
Expand Down Expand Up @@ -98,14 +101,18 @@ export class PeerSyncController {
// If we already know about this core, then we will add it to the
// replication stream when we are ready
if (coreRecord) {
this.#log(
'Received discovery key %h, but already have core in namespace %s',
discoveryKey,
coreRecord.namespace
)
if (this.#enabledNamespaces.has(coreRecord.namespace)) {
this.#replicateCore(coreRecord.core)
this.#replicateCoreRecord(coreRecord)
} else {
this.#log(
'Received discovery key %h, for core %h, but namespace %s is disabled',
discoveryKey,
coreRecord.key,
coreRecord.namespace
)
}
} else {
this.#log('Received unknown discovery key %h', discoveryKey)
}
}

Expand All @@ -115,9 +122,9 @@ export class PeerSyncController {
*
* @param {CoreRecord} coreRecord
*/
#handleAddCore = ({ core, namespace }) => {
if (!this.#enabledNamespaces.has(namespace)) return
this.#replicateCore(core)
#handleAddCore = (coreRecord) => {
if (!this.#enabledNamespaces.has(coreRecord.namespace)) return
this.#replicateCoreRecord(coreRecord)
}

/**
Expand Down Expand Up @@ -235,25 +242,36 @@ export class PeerSyncController {
}

/**
* @param {import('hypercore')<'binary', any>} core
* @param {CoreRecord} coreRecord
*/
#replicateCore(core) {
#replicateCoreRecord({ core, namespace }) {
if (core.closed) return
if (this.#replicatingCores.has(core)) return
this.#log('replicating core %k', core.key)
this.#log('replicating %s core %k', namespace, core.key)
core.replicate(this.#protomux)
core.on('peer-remove', (peer) => {
if (!peer.remotePublicKey.equals(this.peerKey)) return
this.#log('peer-remove %h from core %k', peer.remotePublicKey, core.key)
})
this.#replicatingCores.add(core)

if (!this.#log.enabled) return

/** @type {(peer: any) => void} */
const handlePeerRemove = (peer) => {
if (!peer.remotePublicKey.equals(this.peerKey)) return
core.off('peer-remove', handlePeerRemove)
this.#log(
'peer-remove %h from %s core %k',
peer.remotePublicKey,
namespace,
core.key
)
}
core.on('peer-remove', handlePeerRemove)
}

/**
* @param {import('hypercore')<'binary', any>} core
* @param {CoreRecord} coreRecord
* @returns {Promise<void>}
*/
async #unreplicateCore(core) {
async #unreplicateCoreRecord({ core, namespace }) {
if (core === this.#coreManager.creatorCore) return

this.#replicatingCores.delete(core)
Expand All @@ -266,16 +284,16 @@ export class PeerSyncController {
}

unreplicate(core, this.#protomux)
this.#log('unreplicated core %k', core.key)
this.#log('unreplicated %s core %k', namespace, core.key)
}

/**
* @param {Namespace} namespace
*/
#enableNamespace(namespace) {
if (this.#enabledNamespaces.has(namespace)) return
for (const { core } of this.#coreManager.getCores(namespace)) {
this.#replicateCore(core)
for (const coreRecord of this.#coreManager.getCores(namespace)) {
this.#replicateCoreRecord(coreRecord)
}
this.#enabledNamespaces.add(namespace)
this.#log('enabled namespace %s', namespace)
Expand All @@ -286,8 +304,8 @@ export class PeerSyncController {
*/
#disableNamespace(namespace) {
if (!this.#enabledNamespaces.has(namespace)) return
for (const { core } of this.#coreManager.getCores(namespace)) {
this.#unreplicateCore(core)
for (const coreRecord of this.#coreManager.getCores(namespace)) {
this.#unreplicateCoreRecord(coreRecord)
}
this.#enabledNamespaces.delete(namespace)
this.#log('disabled namespace %s', namespace)
Expand Down
Loading

0 comments on commit 8e59881

Please sign in to comment.