From 2aee75ab22c032daf7d137045bc8bb0ca2dc3c96 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 7 Nov 2023 14:39:06 +0000 Subject: [PATCH] feat: add component logger (#2198) Adds a `ComponentLogger` to `@libp2p/logger` with implementations that prefixes all log messages with a truncated peer id or an arbitrary string. When running multiple libp2p instances simultaneously it's often hard to work out which log messages come from which instance. This will solve this problem. Refs #2105 --- packages/interface/src/index.ts | 26 +++++ packages/libp2p/src/components.ts | 9 +- .../src/connection-manager/auto-dial.ts | 44 ++++----- .../connection-manager/connection-pruner.ts | 20 ++-- .../src/connection-manager/dial-queue.ts | 53 +++++----- .../libp2p/src/connection-manager/index.ts | 43 ++++---- .../libp2p/src/connection-manager/utils.ts | 12 +-- packages/libp2p/src/connection/index.ts | 23 ++--- packages/libp2p/src/identify/identify.ts | 43 ++++---- packages/libp2p/src/identify/index.ts | 3 +- packages/libp2p/src/index.ts | 23 ++++- packages/libp2p/src/libp2p.ts | 39 ++++---- packages/libp2p/src/upgrader.ts | 63 ++++++------ .../test/connection-manager/auto-dial.spec.ts | 21 ++-- .../connection-manager/dial-queue.spec.ts | 10 +- .../test/connection-manager/index.spec.ts | 63 ++++-------- .../libp2p/test/connection/compliance.spec.ts | 3 + packages/libp2p/test/connection/index.spec.ts | 4 +- packages/logger/src/index.ts | 98 ++++++++++++++++++- packages/logger/src/utils.ts | 10 ++ packages/logger/test/index.spec.ts | 15 ++- packages/logger/test/utils.spec.ts | 11 +++ 22 files changed, 407 insertions(+), 229 deletions(-) create mode 100644 packages/logger/src/utils.ts create mode 100644 packages/logger/test/utils.spec.ts diff --git a/packages/interface/src/index.ts b/packages/interface/src/index.ts index 5b1463e4c2..975fa3d2c0 100644 --- a/packages/interface/src/index.ts +++ b/packages/interface/src/index.ts @@ -106,6 +106,23 @@ export interface IdentifyResult { connection: Connection } +/** + * Logger component for libp2p + */ +export interface Logger { + (formatter: any, ...args: any[]): void + error(formatter: any, ...args: any[]): void + trace(formatter: any, ...args: any[]): void + enabled: boolean +} + +/** + * Peer logger component for libp2p + */ +export interface ComponentLogger { + forComponent(name: string): Logger +} + /** * Once you have a libp2p instance, you can listen to several events it emits, * so that you can be notified of relevant network events. @@ -408,6 +425,8 @@ export interface Libp2p extends Startable, Ty */ metrics?: Metrics + logger: ComponentLogger + /** * Get a deduplicated list of peer advertising multiaddrs by concatenating * the listen addresses used by transports with any configured @@ -626,6 +645,13 @@ export interface AbortOptions { signal?: AbortSignal } +/** + * An object that contains a Logger as the `log` property. + */ +export interface LoggerOptions { + log: Logger +} + /** * Returns a new type with all fields marked optional. * diff --git a/packages/libp2p/src/components.ts b/packages/libp2p/src/components.ts index 2d65d956dc..6a254e12df 100644 --- a/packages/libp2p/src/components.ts +++ b/packages/libp2p/src/components.ts @@ -1,6 +1,7 @@ import { CodeError } from '@libp2p/interface/errors' import { isStartable, type Startable } from '@libp2p/interface/startable' -import type { Libp2pEvents } from '@libp2p/interface' +import { defaultLogger } from '@libp2p/logger' +import type { Libp2pEvents, ComponentLogger } from '@libp2p/interface' import type { ConnectionProtector } from '@libp2p/interface/connection' import type { ConnectionGater } from '@libp2p/interface/connection-gater' import type { ContentRouting } from '@libp2p/interface/content-routing' @@ -18,6 +19,7 @@ import type { Datastore } from 'interface-datastore' export interface Components extends Record, Startable { peerId: PeerId + logger: ComponentLogger events: TypedEventTarget addressManager: AddressManager peerStore: PeerStore @@ -35,6 +37,7 @@ export interface Components extends Record, Startable { export interface ComponentsInit { peerId?: PeerId + logger?: ComponentLogger events?: TypedEventTarget addressManager?: AddressManager peerStore?: PeerStore @@ -60,6 +63,10 @@ class DefaultComponents implements Startable { for (const [key, value] of Object.entries(init)) { this.components[key] = value } + + if (this.components.logger == null) { + this.components.logger = defaultLogger() + } } isStarted (): boolean { diff --git a/packages/libp2p/src/connection-manager/auto-dial.ts b/packages/libp2p/src/connection-manager/auto-dial.ts index a110adfd46..515c23d90f 100644 --- a/packages/libp2p/src/connection-manager/auto-dial.ts +++ b/packages/libp2p/src/connection-manager/auto-dial.ts @@ -1,16 +1,13 @@ -import { logger } from '@libp2p/logger' import { PeerMap, PeerSet } from '@libp2p/peer-collections' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { PeerJobQueue } from '../utils/peer-job-queue.js' import { AUTO_DIAL_CONCURRENCY, AUTO_DIAL_DISCOVERED_PEERS_DEBOUNCE, AUTO_DIAL_INTERVAL, AUTO_DIAL_MAX_QUEUE_LENGTH, AUTO_DIAL_PEER_RETRY_THRESHOLD, AUTO_DIAL_PRIORITY, LAST_DIAL_FAILURE_KEY, MIN_CONNECTIONS } from './constants.js' -import type { Libp2pEvents } from '@libp2p/interface' +import type { Libp2pEvents, Logger, ComponentLogger } from '@libp2p/interface' import type { TypedEventTarget } from '@libp2p/interface/events' import type { PeerStore } from '@libp2p/interface/peer-store' import type { Startable } from '@libp2p/interface/startable' import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' -const log = logger('libp2p:connection-manager:auto-dial') - interface AutoDialInit { minConnections?: number maxQueueLength?: number @@ -25,6 +22,7 @@ interface AutoDialComponents { connectionManager: ConnectionManager peerStore: PeerStore events: TypedEventTarget + logger: ComponentLogger } const defaultOptions = { @@ -50,6 +48,7 @@ export class AutoDial implements Startable { private autoDialInterval?: ReturnType private started: boolean private running: boolean + readonly #log: Logger /** * Proactively tries to connect to known peers stored in the PeerStore. @@ -65,20 +64,21 @@ export class AutoDial implements Startable { this.autoDialMaxQueueLength = init.maxQueueLength ?? defaultOptions.maxQueueLength this.autoDialPeerRetryThresholdMs = init.autoDialPeerRetryThreshold ?? defaultOptions.autoDialPeerRetryThreshold this.autoDialDiscoveredPeersDebounce = init.autoDialDiscoveredPeersDebounce ?? defaultOptions.autoDialDiscoveredPeersDebounce + this.#log = components.logger.forComponent('libp2p:connection-manager:auto-dial') this.started = false this.running = false this.queue = new PeerJobQueue({ concurrency: init.autoDialConcurrency ?? defaultOptions.autoDialConcurrency }) this.queue.addListener('error', (err) => { - log.error('error during auto-dial', err) + this.#log.error('error during auto-dial', err) }) // check the min connection limit whenever a peer disconnects components.events.addEventListener('connection:close', () => { this.autoDial() .catch(err => { - log.error(err) + this.#log.error(err) }) }) @@ -93,7 +93,7 @@ export class AutoDial implements Startable { debounce = setTimeout(() => { this.autoDial() .catch(err => { - log.error(err) + this.#log.error(err) }) }, this.autoDialDiscoveredPeersDebounce) }) @@ -107,7 +107,7 @@ export class AutoDial implements Startable { this.autoDialInterval = setTimeout(() => { this.autoDial() .catch(err => { - log.error('error while autodialing', err) + this.#log.error('error while autodialing', err) }) }, this.autoDialIntervalMs) this.started = true @@ -116,7 +116,7 @@ export class AutoDial implements Startable { afterStart (): void { this.autoDial() .catch(err => { - log.error('error while autodialing', err) + this.#log.error('error while autodialing', err) }) } @@ -139,24 +139,24 @@ export class AutoDial implements Startable { // Already has enough connections if (numConnections >= this.minConnections) { if (this.minConnections > 0) { - log.trace('have enough connections %d/%d', numConnections, this.minConnections) + this.#log.trace('have enough connections %d/%d', numConnections, this.minConnections) } return } if (this.queue.size > this.autoDialMaxQueueLength) { - log('not enough connections %d/%d but auto dial queue is full', numConnections, this.minConnections) + this.#log('not enough connections %d/%d but auto dial queue is full', numConnections, this.minConnections) return } if (this.running) { - log('not enough connections %d/%d - but skipping autodial as it is already running', numConnections, this.minConnections) + this.#log('not enough connections %d/%d - but skipping autodial as it is already running', numConnections, this.minConnections) return } this.running = true - log('not enough connections %d/%d - will dial peers to increase the number of connections', numConnections, this.minConnections) + this.#log('not enough connections %d/%d - will dial peers to increase the number of connections', numConnections, this.minConnections) const dialQueue = new PeerSet( // @ts-expect-error boolean filter removes falsy peer IDs @@ -172,25 +172,25 @@ export class AutoDial implements Startable { (peer) => { // Remove peers without addresses if (peer.addresses.length === 0) { - log.trace('not autodialing %p because they have no addresses', peer.id) + this.#log.trace('not autodialing %p because they have no addresses', peer.id) return false } // remove peers we are already connected to if (connections.has(peer.id)) { - log.trace('not autodialing %p because they are already connected', peer.id) + this.#log.trace('not autodialing %p because they are already connected', peer.id) return false } // remove peers we are already dialling if (dialQueue.has(peer.id)) { - log.trace('not autodialing %p because they are already being dialed', peer.id) + this.#log.trace('not autodialing %p because they are already being dialed', peer.id) return false } // remove peers already in the autodial queue if (this.queue.hasJob(peer.id)) { - log.trace('not autodialing %p because they are already being autodialed', peer.id) + this.#log.trace('not autodialing %p because they are already being autodialed', peer.id) return false } @@ -249,7 +249,7 @@ export class AutoDial implements Startable { return Date.now() - lastDialFailureTimestamp > this.autoDialPeerRetryThresholdMs }) - log('selected %d/%d peers to dial', peersThatHaveNotFailed.length, peers.length) + this.#log('selected %d/%d peers to dial', peersThatHaveNotFailed.length, peers.length) for (const peer of peersThatHaveNotFailed) { this.queue.add(async () => { @@ -257,19 +257,19 @@ export class AutoDial implements Startable { // Check to see if we still need to auto dial if (numConnections >= this.minConnections) { - log('got enough connections now %d/%d', numConnections, this.minConnections) + this.#log('got enough connections now %d/%d', numConnections, this.minConnections) this.queue.clear() return } - log('connecting to a peerStore stored peer %p', peer.id) + this.#log('connecting to a peerStore stored peer %p', peer.id) await this.connectionManager.openConnection(peer.id, { priority: this.autoDialPriority }) }, { peerId: peer.id }).catch(err => { - log.error('could not connect to peerStore stored peer', err) + this.#log.error('could not connect to peerStore stored peer', err) }) } @@ -279,7 +279,7 @@ export class AutoDial implements Startable { this.autoDialInterval = setTimeout(() => { this.autoDial() .catch(err => { - log.error('error while autodialing', err) + this.#log.error('error while autodialing', err) }) }, this.autoDialIntervalMs) } diff --git a/packages/libp2p/src/connection-manager/connection-pruner.ts b/packages/libp2p/src/connection-manager/connection-pruner.ts index adbed0a56c..8771a25b42 100644 --- a/packages/libp2p/src/connection-manager/connection-pruner.ts +++ b/packages/libp2p/src/connection-manager/connection-pruner.ts @@ -1,14 +1,11 @@ -import { logger } from '@libp2p/logger' import { PeerMap } from '@libp2p/peer-collections' import { MAX_CONNECTIONS } from './constants.js' -import type { Libp2pEvents } from '@libp2p/interface' +import type { Libp2pEvents, Logger, ComponentLogger } from '@libp2p/interface' import type { TypedEventTarget } from '@libp2p/interface/events' import type { PeerStore } from '@libp2p/interface/peer-store' import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' import type { Multiaddr } from '@multiformats/multiaddr' -const log = logger('libp2p:connection-manager:connection-pruner') - interface ConnectionPrunerInit { maxConnections?: number allow?: Multiaddr[] @@ -18,6 +15,7 @@ interface ConnectionPrunerComponents { connectionManager: ConnectionManager peerStore: PeerStore events: TypedEventTarget + logger: ComponentLogger } const defaultOptions = { @@ -34,6 +32,7 @@ export class ConnectionPruner { private readonly peerStore: PeerStore private readonly allow: Multiaddr[] private readonly events: TypedEventTarget + readonly #log: Logger constructor (components: ConnectionPrunerComponents, init: ConnectionPrunerInit = {}) { this.maxConnections = init.maxConnections ?? defaultOptions.maxConnections @@ -41,12 +40,13 @@ export class ConnectionPruner { this.connectionManager = components.connectionManager this.peerStore = components.peerStore this.events = components.events + this.#log = components.logger.forComponent('libp2p:connection-manager:connection-pruner') // check the max connection limit whenever a peer connects components.events.addEventListener('connection:open', () => { this.maybePruneConnections() .catch(err => { - log.error(err) + this.#log.error(err) }) }) } @@ -60,12 +60,12 @@ export class ConnectionPruner { const numConnections = connections.length const toPrune = Math.max(numConnections - this.maxConnections, 0) - log('checking max connections limit %d/%d', numConnections, this.maxConnections) + this.#log('checking max connections limit %d/%d', numConnections, this.maxConnections) if (numConnections <= this.maxConnections) { return } - log('max connections limit exceeded %d/%d, pruning %d connection(s)', numConnections, this.maxConnections, toPrune) + this.#log('max connections limit exceeded %d/%d, pruning %d connection(s)', numConnections, this.maxConnections, toPrune) const peerValues = new PeerMap() // work out peer values @@ -87,7 +87,7 @@ export class ConnectionPruner { }, 0)) } catch (err: any) { if (err.code !== 'ERR_NOT_FOUND') { - log.error('error loading peer tags', err) + this.#log.error('error loading peer tags', err) } } } @@ -124,7 +124,7 @@ export class ConnectionPruner { const toClose = [] for (const connection of sortedConnections) { - log('too many connections open - closing a connection to %p', connection.remotePeer) + this.#log('too many connections open - closing a connection to %p', connection.remotePeer) // check allow list const connectionInAllowList = this.allow.some((ma) => { return connection.remoteAddr.toString().startsWith(ma.toString()) @@ -146,7 +146,7 @@ export class ConnectionPruner { try { await connection.close() } catch (err) { - log.error(err) + this.#log.error(err) } }) ) diff --git a/packages/libp2p/src/connection-manager/dial-queue.ts b/packages/libp2p/src/connection-manager/dial-queue.ts index c366417757..d45e989590 100644 --- a/packages/libp2p/src/connection-manager/dial-queue.ts +++ b/packages/libp2p/src/connection-manager/dial-queue.ts @@ -1,6 +1,5 @@ import { AbortError, CodeError } from '@libp2p/interface/errors' import { setMaxListeners } from '@libp2p/interface/events' -import { logger } from '@libp2p/logger' import { PeerMap } from '@libp2p/peer-collections' import { defaultAddressSort } from '@libp2p/utils/address-sort' import { type Multiaddr, type Resolver, resolvers } from '@multiformats/multiaddr' @@ -19,7 +18,7 @@ import { LAST_DIAL_FAILURE_KEY } from './constants.js' import { combineSignals, resolveMultiaddrs } from './utils.js' -import type { AddressSorter, AbortOptions, PendingDial } from '@libp2p/interface' +import type { AddressSorter, AbortOptions, PendingDial, ComponentLogger, Logger } from '@libp2p/interface' import type { Connection } from '@libp2p/interface/connection' import type { ConnectionGater } from '@libp2p/interface/connection-gater' import type { Metric, Metrics } from '@libp2p/interface/metrics' @@ -27,8 +26,6 @@ import type { PeerId } from '@libp2p/interface/peer-id' import type { Address, PeerStore } from '@libp2p/interface/peer-store' import type { TransportManager } from '@libp2p/interface-internal/transport-manager' -const log = logger('libp2p:connection-manager:dial-queue') - export interface PendingDialTarget { resolve(value: any): void reject(err: Error): void @@ -70,6 +67,7 @@ interface DialQueueComponents { peerStore: PeerStore transportManager: TransportManager connectionGater: ConnectionGater + logger: ComponentLogger } export class DialQueue { @@ -87,6 +85,7 @@ export class DialQueue { private readonly pendingDialCount?: Metric private readonly shutDownController: AbortController private readonly connections: PeerMap + readonly #log: Logger constructor (components: DialQueueComponents, init: DialerInit = {}) { this.addressSorter = init.addressSorter ?? defaultOptions.addressSorter @@ -94,6 +93,7 @@ export class DialQueue { this.maxParallelDialsPerPeer = init.maxParallelDialsPerPeer ?? defaultOptions.maxParallelDialsPerPeer this.dialTimeout = init.dialTimeout ?? defaultOptions.dialTimeout this.connections = init.connections ?? new PeerMap() + this.#log = components.logger.forComponent('libp2p:connection-manager:dial-queue') this.peerId = components.peerId this.peerStore = components.peerStore @@ -133,7 +133,7 @@ export class DialQueue { }) // a started job errored this.queue.on('error', (err) => { - log.error('error in dial queue', err) + this.#log.error('error in dial queue', err) this.pendingDialCount?.update(this.queue.size) this.inProgressDialCount?.update(this.queue.pending) }) @@ -205,7 +205,7 @@ export class DialQueue { }) if (existingConnection != null) { - log('already connected to %a', existingConnection.remoteAddr) + this.#log('already connected to %a', existingConnection.remoteAddr) return existingConnection } @@ -226,12 +226,12 @@ export class DialQueue { }) if (existingDial != null) { - log('joining existing dial target for %p', peerId) + this.#log('joining existing dial target for %p', peerId) signal.clear() return existingDial.promise } - log('creating dial target for', addrsToDial.map(({ multiaddr }) => multiaddr.toString())) + this.#log('creating dial target for', addrsToDial.map(({ multiaddr }) => multiaddr.toString())) // @ts-expect-error .promise property is set below const pendingDial: PendingDialInternal = { id: randomId(), @@ -252,7 +252,7 @@ export class DialQueue { signal.clear() }) .catch(async err => { - log.error('dial failed to %s', pendingDial.multiaddrs.map(ma => ma.toString()).join(', '), err) + this.#log.error('dial failed to %s', pendingDial.multiaddrs.map(ma => ma.toString()).join(', '), err) if (peerId != null) { // record the last failed dial @@ -263,7 +263,7 @@ export class DialQueue { } }) } catch (err: any) { - log.error('could not update last dial failure key for %p', peerId, err) + this.#log.error('could not update last dial failure key for %p', peerId, err) } } @@ -294,12 +294,12 @@ export class DialQueue { }) if (existingConnection != null) { - log('already connected to %a', existingConnection.remoteAddr) + this.#log('already connected to %a', existingConnection.remoteAddr) await connection.close() return existingConnection } - log('connection opened to %a', connection.remoteAddr) + this.#log('connection opened to %a', connection.remoteAddr) return connection } @@ -334,11 +334,11 @@ export class DialQueue { // if just a peer id was passed, load available multiaddrs for this peer from the address book if (addrs.length === 0) { - log('loading multiaddrs for %p', peerId) + this.#log('loading multiaddrs for %p', peerId) try { const peer = await this.peerStore.get(peerId) addrs.push(...peer.addresses) - log('loaded multiaddrs for %p', peerId, addrs.map(({ multiaddr }) => multiaddr.toString())) + this.#log('loaded multiaddrs for %p', peerId, addrs.map(({ multiaddr }) => multiaddr.toString())) } catch (err: any) { if (err.code !== codes.ERR_NOT_FOUND) { throw err @@ -350,7 +350,10 @@ export class DialQueue { // resolve addresses - this can result in a one-to-many translation when dnsaddrs are resolved let resolvedAddresses = (await Promise.all( addrs.map(async addr => { - const result = await resolveMultiaddrs(addr.multiaddr, options) + const result = await resolveMultiaddrs(addr.multiaddr, { + ...options, + log: this.#log + }) if (result.length === 1 && result[0].equals(addr.multiaddr)) { return addr @@ -422,8 +425,8 @@ export class DialQueue { const dedupedMultiaddrs = [...dedupedAddrs.values()] if (dedupedMultiaddrs.length === 0 || dedupedMultiaddrs.length > this.maxPeerAddrsToDial) { - log('addresses for %p before filtering', peerId ?? 'unknown peer', resolvedAddresses.map(({ multiaddr }) => multiaddr.toString())) - log('addresses for %p after filtering', peerId ?? 'unknown peer', dedupedMultiaddrs.map(({ multiaddr }) => multiaddr.toString())) + this.#log('addresses for %p before filtering', peerId ?? 'unknown peer', resolvedAddresses.map(({ multiaddr }) => multiaddr.toString())) + this.#log('addresses for %p after filtering', peerId ?? 'unknown peer', dedupedMultiaddrs.map(({ multiaddr }) => multiaddr.toString())) } // make sure we actually have some addresses to dial @@ -467,7 +470,7 @@ export class DialQueue { concurrency: this.maxParallelDialsPerPeer }) peerDialQueue.on('error', (err) => { - log.error('error dialling', err) + this.#log.error('error dialling', err) }) const conn = await Promise.any(pendingDial.multiaddrs.map(async (addr, i) => { @@ -480,13 +483,13 @@ export class DialQueue { // let any signal abort the dial const signal = combineSignals(controller.signal, options.signal) signal.addEventListener('abort', () => { - log('dial to %a aborted', addr) + this.#log('dial to %a aborted', addr) }) const deferred = pDefer() await peerDialQueue.add(async () => { if (signal.aborted) { - log('dial to %a was aborted before reaching the head of the peer dial queue', addr) + this.#log('dial to %a was aborted before reaching the head of the peer dial queue', addr) deferred.reject(new AbortError()) return } @@ -495,7 +498,7 @@ export class DialQueue { await this.queue.add(async () => { try { if (signal.aborted) { - log('dial to %a was aborted before reaching the head of the dial queue', addr) + this.#log('dial to %a was aborted before reaching the head of the dial queue', addr) deferred.reject(new AbortError()) return } @@ -510,10 +513,10 @@ export class DialQueue { if (controller.signal.aborted) { // another dial succeeded faster than this one - log('multiple dials succeeded, closing superfluous connection') + this.#log('multiple dials succeeded, closing superfluous connection') conn.close().catch(err => { - log.error('error closing superfluous connection', err) + this.#log.error('error closing superfluous connection', err) }) deferred.reject(new AbortError()) @@ -530,13 +533,13 @@ export class DialQueue { } }) - log('dial to %a succeeded', addr) + this.#log('dial to %a succeeded', addr) // resolve the connection promise deferred.resolve(conn) } catch (err: any) { // something only went wrong if our signal was not aborted - log.error('error during dial of %a', addr, err) + this.#log.error('error during dial of %a', addr, err) deferred.reject(err) } }, { diff --git a/packages/libp2p/src/connection-manager/index.ts b/packages/libp2p/src/connection-manager/index.ts index 0afceac860..137f331c4a 100644 --- a/packages/libp2p/src/connection-manager/index.ts +++ b/packages/libp2p/src/connection-manager/index.ts @@ -1,6 +1,5 @@ import { CodeError } from '@libp2p/interface/errors' import { KEEP_ALIVE } from '@libp2p/interface/peer-store/tags' -import { logger } from '@libp2p/logger' import { PeerMap } from '@libp2p/peer-collections' import { defaultAddressSort } from '@libp2p/utils/address-sort' import { type Multiaddr, type Resolver, multiaddr } from '@multiformats/multiaddr' @@ -12,7 +11,7 @@ import { AutoDial } from './auto-dial.js' import { ConnectionPruner } from './connection-pruner.js' import { AUTO_DIAL_CONCURRENCY, AUTO_DIAL_MAX_QUEUE_LENGTH, AUTO_DIAL_PRIORITY, DIAL_TIMEOUT, INBOUND_CONNECTION_THRESHOLD, MAX_CONNECTIONS, MAX_INCOMING_PENDING_CONNECTIONS, MAX_PARALLEL_DIALS, MAX_PARALLEL_DIALS_PER_PEER, MAX_PEER_ADDRS_TO_DIAL, MIN_CONNECTIONS } from './constants.js' import { DialQueue } from './dial-queue.js' -import type { PendingDial, AddressSorter, Libp2pEvents, AbortOptions } from '@libp2p/interface' +import type { PendingDial, AddressSorter, Libp2pEvents, AbortOptions, ComponentLogger, Logger } from '@libp2p/interface' import type { Connection, MultiaddrConnection } from '@libp2p/interface/connection' import type { ConnectionGater } from '@libp2p/interface/connection-gater' import type { TypedEventTarget } from '@libp2p/interface/events' @@ -23,8 +22,6 @@ import type { Startable } from '@libp2p/interface/startable' import type { ConnectionManager, OpenConnectionOptions } from '@libp2p/interface-internal/connection-manager' import type { TransportManager } from '@libp2p/interface-internal/transport-manager' -const log = logger('libp2p:connection-manager') - const DEFAULT_DIAL_PRIORITY = 50 export interface ConnectionManagerInit { @@ -166,6 +163,7 @@ export interface DefaultConnectionManagerComponents { transportManager: TransportManager connectionGater: ConnectionGater events: TypedEventTarget + logger: ComponentLogger } /** @@ -188,6 +186,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { private readonly peerStore: PeerStore private readonly metrics?: Metrics private readonly events: TypedEventTarget + readonly #log: Logger constructor (components: DefaultConnectionManagerComponents, init: ConnectionManagerInit = {}) { this.maxConnections = init.maxConnections ?? defaultOptions.maxConnections @@ -206,6 +205,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { this.peerStore = components.peerStore this.metrics = components.metrics this.events = components.events + this.#log = components.logger.forComponent('libp2p:connection-manager') this.onConnect = this.onConnect.bind(this) this.onDisconnect = this.onDisconnect.bind(this) @@ -229,7 +229,8 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { this.autoDial = new AutoDial({ connectionManager: this, peerStore: components.peerStore, - events: components.events + events: components.events, + logger: components.logger }, { minConnections, autoDialConcurrency: init.autoDialConcurrency ?? defaultOptions.autoDialConcurrency, @@ -241,7 +242,8 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { this.connectionPruner = new ConnectionPruner({ connectionManager: this, peerStore: components.peerStore, - events: components.events + events: components.events, + logger: components.logger }, { maxConnections: this.maxConnections, allow: this.allow @@ -252,7 +254,8 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { metrics: components.metrics, peerStore: components.peerStore, transportManager: components.transportManager, - connectionGater: components.connectionGater + connectionGater: components.connectionGater, + logger: components.logger }, { addressSorter: init.addressSorter ?? defaultAddressSort, maxParallelDials: init.maxParallelDials ?? MAX_PARALLEL_DIALS, @@ -356,7 +359,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { this.autoDial.start() this.started = true - log('started') + this.#log('started') } async afterStart (): Promise { @@ -373,13 +376,13 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { keepAlivePeers.map(async peer => { await this.openConnection(peer.id) .catch(err => { - log.error(err) + this.#log.error(err) }) }) ) }) .catch(err => { - log.error(err) + this.#log.error(err) }) this.autoDial.afterStart() @@ -400,22 +403,22 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { try { await connection.close() } catch (err) { - log.error(err) + this.#log.error(err) } })()) } } - log('closing %d connections', tasks.length) + this.#log('closing %d connections', tasks.length) await Promise.all(tasks) this.connections.clear() - log('stopped') + this.#log('stopped') } onConnect (evt: CustomEvent): void { void this._onConnect(evt).catch(err => { - log.error(err) + this.#log.error(err) }) } @@ -505,12 +508,12 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { const { peerId } = getPeerAddress(peerIdOrMultiaddr) if (peerId != null && options.force !== true) { - log('dial %p', peerId) + this.#log('dial %p', peerId) const existingConnection = this.getConnections(peerId) .find(conn => !conn.transient) if (existingConnection != null) { - log('had an existing non-transient connection to %p', peerId) + this.#log('had an existing non-transient connection to %p', peerId) return existingConnection } @@ -566,7 +569,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { }) if (denyConnection) { - log('connection from %a refused - connection remote address was in deny list', maConn.remoteAddr) + this.#log('connection from %a refused - connection remote address was in deny list', maConn.remoteAddr) return false } @@ -583,7 +586,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { // check pending connections if (this.incomingPendingConnections === this.maxIncomingPendingConnections) { - log('connection from %a refused - incomingPendingConnections exceeded by host', maConn.remoteAddr) + this.#log('connection from %a refused - incomingPendingConnections exceeded by host', maConn.remoteAddr) return false } @@ -593,7 +596,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { try { await this.inboundConnectionRateLimiter.consume(host, 1) } catch { - log('connection from %a refused - inboundConnectionThreshold exceeded by host %s', maConn.remoteAddr, host) + this.#log('connection from %a refused - inboundConnectionThreshold exceeded by host %s', maConn.remoteAddr, host) return false } } @@ -604,7 +607,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { return true } - log('connection from %a refused - maxConnections exceeded', maConn.remoteAddr) + this.#log('connection from %a refused - maxConnections exceeded', maConn.remoteAddr) return false } diff --git a/packages/libp2p/src/connection-manager/utils.ts b/packages/libp2p/src/connection-manager/utils.ts index 0bec4506ab..d32108e26f 100644 --- a/packages/libp2p/src/connection-manager/utils.ts +++ b/packages/libp2p/src/connection-manager/utils.ts @@ -1,14 +1,12 @@ import { setMaxListeners } from '@libp2p/interface/events' -import { logger } from '@libp2p/logger' import { type AbortOptions, multiaddr, type Multiaddr } from '@multiformats/multiaddr' import { type ClearableSignal, anySignal } from 'any-signal' - -const log = logger('libp2p:connection-manager:utils') +import type { LoggerOptions } from '@libp2p/interface' /** * Resolve multiaddr recursively */ -export async function resolveMultiaddrs (ma: Multiaddr, options: AbortOptions): Promise { +export async function resolveMultiaddrs (ma: Multiaddr, options: AbortOptions & LoggerOptions): Promise { // TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place // Now only supporting resolve for dnsaddr const resolvableProto = ma.protoNames().includes('dnsaddr') @@ -31,7 +29,7 @@ export async function resolveMultiaddrs (ma: Multiaddr, options: AbortOptions): return array }, ([])) - log('resolved %s to', ma, output.map(ma => ma.toString())) + options.log('resolved %s to', ma, output.map(ma => ma.toString())) return output } @@ -39,13 +37,13 @@ export async function resolveMultiaddrs (ma: Multiaddr, options: AbortOptions): /** * Resolve a given multiaddr. If this fails, an empty array will be returned */ -async function resolveRecord (ma: Multiaddr, options: AbortOptions): Promise { +async function resolveRecord (ma: Multiaddr, options: AbortOptions & LoggerOptions): Promise { try { ma = multiaddr(ma.toString()) // Use current multiaddr module const multiaddrs = await ma.resolve(options) return multiaddrs } catch (err) { - log.error(`multiaddr ${ma.toString()} could not be resolved`, err) + options.log.error(`multiaddr ${ma.toString()} could not be resolved`, err) return [] } } diff --git a/packages/libp2p/src/connection/index.ts b/packages/libp2p/src/connection/index.ts index 3aca11759b..fbc62c72f1 100644 --- a/packages/libp2p/src/connection/index.ts +++ b/packages/libp2p/src/connection/index.ts @@ -1,14 +1,11 @@ import { symbol } from '@libp2p/interface/connection' import { CodeError } from '@libp2p/interface/errors' import { setMaxListeners } from '@libp2p/interface/events' -import { logger } from '@libp2p/logger' -import type { AbortOptions } from '@libp2p/interface' +import type { AbortOptions, Logger, ComponentLogger } from '@libp2p/interface' import type { Direction, Connection, Stream, ConnectionTimeline, ConnectionStatus, NewStreamOptions } from '@libp2p/interface/connection' import type { PeerId } from '@libp2p/interface/peer-id' import type { Multiaddr } from '@multiformats/multiaddr' -const log = logger('libp2p:connection') - const CLOSE_TIMEOUT = 500 interface ConnectionInit { @@ -24,6 +21,7 @@ interface ConnectionInit { multiplexer?: string encryption?: string transient?: boolean + logger: ComponentLogger } /** @@ -76,6 +74,8 @@ export class ConnectionImpl implements Connection { */ private readonly _getStreams: () => Stream[] + readonly #log: Logger + /** * An implementation of the js-libp2p connection. * Any libp2p transport should use an upgrader to return this connection. @@ -92,6 +92,7 @@ export class ConnectionImpl implements Connection { this.multiplexer = init.multiplexer this.encryption = init.encryption this.transient = init.transient ?? false + this.#log = init.logger.forComponent('libp2p:connection') if (this.remoteAddr.getPeerId() == null) { this.remoteAddr = this.remoteAddr.encapsulate(`/p2p/${this.remotePeer}`) @@ -150,7 +151,7 @@ export class ConnectionImpl implements Connection { return } - log('closing connection to %a', this.remoteAddr) + this.#log('closing connection to %a', this.remoteAddr) this.status = 'closing' @@ -159,35 +160,35 @@ export class ConnectionImpl implements Connection { setMaxListeners(Infinity, options.signal) try { - log.trace('closing all streams') + this.#log.trace('closing all streams') // close all streams gracefully - this can throw if we're not multiplexed await Promise.all( this.streams.map(async s => s.close(options)) ) - log.trace('closing underlying transport') + this.#log.trace('closing underlying transport') // close raw connection await this._close(options) - log.trace('updating timeline with close time') + this.#log.trace('updating timeline with close time') this.status = 'closed' this.timeline.close = Date.now() } catch (err: any) { - log.error('error encountered during graceful close of connection to %a', this.remoteAddr, err) + this.#log.error('error encountered during graceful close of connection to %a', this.remoteAddr, err) this.abort(err) } } abort (err: Error): void { - log.error('aborting connection to %a due to error', this.remoteAddr, err) + this.#log.error('aborting connection to %a due to error', this.remoteAddr, err) this.status = 'closing' this.streams.forEach(s => { s.abort(err) }) - log.error('all streams aborted', this.streams.length) + this.#log.error('all streams aborted', this.streams.length) // Abort raw connection this._abort(err) diff --git a/packages/libp2p/src/identify/identify.ts b/packages/libp2p/src/identify/identify.ts index 57c2be4281..8fef5c9949 100644 --- a/packages/libp2p/src/identify/identify.ts +++ b/packages/libp2p/src/identify/identify.ts @@ -1,6 +1,5 @@ import { CodeError } from '@libp2p/interface/errors' import { setMaxListeners } from '@libp2p/interface/events' -import { logger } from '@libp2p/logger' import { peerIdFromKeys } from '@libp2p/peer-id' import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' import { type Multiaddr, multiaddr, protocols } from '@multiformats/multiaddr' @@ -20,7 +19,7 @@ import { } from './consts.js' import { Identify } from './pb/message.js' import type { IdentifyService, IdentifyServiceComponents, IdentifyServiceInit } from './index.js' -import type { Libp2pEvents, IdentifyResult, SignedPeerRecord, AbortOptions } from '@libp2p/interface' +import type { Libp2pEvents, IdentifyResult, SignedPeerRecord, AbortOptions, Logger } from '@libp2p/interface' import type { Connection, Stream } from '@libp2p/interface/connection' import type { TypedEventTarget } from '@libp2p/interface/events' import type { PeerId } from '@libp2p/interface/peer-id' @@ -30,8 +29,6 @@ import type { AddressManager } from '@libp2p/interface-internal/address-manager' import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' import type { IncomingStreamData, Registrar } from '@libp2p/interface-internal/registrar' -const log = logger('libp2p:identify') - // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52 const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8 @@ -73,6 +70,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { private readonly maxObservedAddresses: number private readonly events: TypedEventTarget private readonly runOnTransientConnection: boolean + readonly #log: Logger constructor (components: IdentifyServiceComponents, init: IdentifyServiceInit) { this.started = false @@ -82,6 +80,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { this.addressManager = components.addressManager this.connectionManager = components.connectionManager this.events = components.events + this.#log = components.logger.forComponent('libp2p:identify') this.identifyProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}` this.identifyPushProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` @@ -104,13 +103,13 @@ export class DefaultIdentifyService implements Startable, IdentifyService { // When a new connection happens, trigger identify components.events.addEventListener('connection:open', (evt) => { const connection = evt.detail - this.identify(connection).catch(err => { log.error('error during identify trigged by connection:open', err) }) + this.identify(connection).catch(err => { this.#log.error('error during identify trigged by connection:open', err) }) }) } // When self peer record changes, trigger identify-push components.events.addEventListener('self:peer:update', (evt) => { - void this.push().catch(err => { log.error(err) }) + void this.push().catch(err => { this.#log.error(err) }) }) // Append user agent version to default AGENT_VERSION depending on the environment @@ -141,7 +140,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { await this.registrar.handle(this.identifyProtocolStr, (data) => { void this._handleIdentify(data).catch(err => { - log.error(err) + this.#log.error(err) }) }, { maxInboundStreams: this.maxInboundStreams, @@ -150,7 +149,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { }) await this.registrar.handle(this.identifyPushProtocolStr, (data) => { void this._handlePush(data).catch(err => { - log.error(err) + this.#log.error(err) }) }, { maxInboundStreams: this.maxPushIncomingStreams, @@ -215,7 +214,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { }) } catch (err: any) { // Just log errors - log.error('could not push identify update to peer', err) + this.#log.error('could not push identify update to peer', err) stream?.abort(err) } }) @@ -276,7 +275,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { return message } catch (err: any) { - log.error('error while reading identify message', err) + this.#log.error('error while reading identify message', err) stream?.abort(err) throw err } @@ -307,12 +306,12 @@ export class DefaultIdentifyService implements Startable, IdentifyService { // Get the observedAddr if there is one const cleanObservedAddr = getCleanMultiaddr(observedAddr) - log('identify completed for peer %p and protocols %o', id, protocols) - log('our observed address is %a', cleanObservedAddr) + this.#log('identify completed for peer %p and protocols %o', id, protocols) + this.#log('our observed address is %a', cleanObservedAddr) if (cleanObservedAddr != null && this.addressManager.getObservedAddrs().length < (this.maxObservedAddresses ?? Infinity)) { - log('storing our observed address %a', cleanObservedAddr) + this.#log('storing our observed address %a', cleanObservedAddr) this.addressManager.addObservedAddr(cleanObservedAddr) } @@ -370,7 +369,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { signal }) } catch (err: any) { - log.error('could not respond to identify request', err) + this.#log.error('could not respond to identify request', err) stream.abort(err) } } @@ -399,16 +398,16 @@ export class DefaultIdentifyService implements Startable, IdentifyService { await this.#consumeIdentifyMessage(connection, message) } catch (err: any) { - log.error('received invalid message', err) + this.#log.error('received invalid message', err) stream.abort(err) return } - log('handled push from %p', connection.remotePeer) + this.#log('handled push from %p', connection.remotePeer) } async #consumeIdentifyMessage (connection: Connection, message: Identify): Promise { - log('received identify from %p', connection.remotePeer) + this.#log('received identify from %p', connection.remotePeer) if (message == null) { throw new CodeError('message was null or undefined', 'ERR_INVALID_MESSAGE') @@ -441,7 +440,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { // if the peer record has been sent, prefer the addresses in the record as they are signed by the remote peer if (message.signedPeerRecord != null) { - log('received signedPeerRecord in push from %p', connection.remotePeer) + this.#log('received signedPeerRecord in push from %p', connection.remotePeer) let peerRecordEnvelope = message.signedPeerRecord const envelope = await RecordEnvelope.openAndCertify(peerRecordEnvelope, PeerRecord.DOMAIN) @@ -478,7 +477,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { // ensure seq is greater than, or equal to, the last received if (storedRecord.seqNumber >= peerRecord.seqNumber) { - log('sequence number was lower or equal to existing sequence number - stored: %d received: %d', storedRecord.seqNumber, peerRecord.seqNumber) + this.#log('sequence number was lower or equal to existing sequence number - stored: %d received: %d', storedRecord.seqNumber, peerRecord.seqNumber) peerRecord = storedRecord peerRecordEnvelope = existingPeer.peerRecordEnvelope } @@ -499,10 +498,10 @@ export class DefaultIdentifyService implements Startable, IdentifyService { addresses: peerRecord.multiaddrs } } else { - log('%p did not send a signed peer record', connection.remotePeer) + this.#log('%p did not send a signed peer record', connection.remotePeer) } - log('patching %p with', connection.remotePeer, peer) + this.#log('patching %p with', connection.remotePeer, peer) await this.peerStore.patch(connection.remotePeer, peer) if (message.agentVersion != null || message.protocolVersion != null) { @@ -516,7 +515,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService { metadata.ProtocolVersion = uint8ArrayFromString(message.protocolVersion) } - log('merging %p metadata', connection.remotePeer, metadata) + this.#log('merging %p metadata', connection.remotePeer, metadata) await this.peerStore.merge(connection.remotePeer, { metadata }) diff --git a/packages/libp2p/src/identify/index.ts b/packages/libp2p/src/identify/index.ts index 61dc2e3523..fc0fbf6320 100644 --- a/packages/libp2p/src/identify/index.ts +++ b/packages/libp2p/src/identify/index.ts @@ -4,7 +4,7 @@ import { } from './consts.js' import { DefaultIdentifyService } from './identify.js' import { Identify } from './pb/message.js' -import type { AbortOptions, IdentifyResult, Libp2pEvents } from '@libp2p/interface' +import type { AbortOptions, IdentifyResult, Libp2pEvents, ComponentLogger } from '@libp2p/interface' import type { TypedEventTarget } from '@libp2p/interface/events' import type { PeerId } from '@libp2p/interface/peer-id' import type { PeerStore } from '@libp2p/interface/peer-store' @@ -59,6 +59,7 @@ export interface IdentifyServiceComponents { registrar: Registrar addressManager: AddressManager events: TypedEventTarget + logger: ComponentLogger } /** diff --git a/packages/libp2p/src/index.ts b/packages/libp2p/src/index.ts index 8fa5882135..d9a0e61095 100644 --- a/packages/libp2p/src/index.ts +++ b/packages/libp2p/src/index.ts @@ -19,7 +19,7 @@ import type { AddressManagerInit } from './address-manager/index.js' import type { Components } from './components.js' import type { ConnectionManagerInit } from './connection-manager/index.js' import type { TransportManagerInit } from './transport-manager.js' -import type { Libp2p, ServiceMap, RecursivePartial } from '@libp2p/interface' +import type { Libp2p, ServiceMap, RecursivePartial, ComponentLogger } from '@libp2p/interface' import type { ConnectionProtector } from '@libp2p/interface/connection' import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter' import type { ConnectionGater } from '@libp2p/interface/connection-gater' @@ -108,6 +108,27 @@ export interface Libp2pInit * Arbitrary libp2p modules */ services: ServiceFactoryMap + + /** + * An optional logging implementation that can be used to write runtime logs. + * + * Set the `DEBUG` env var or the `debug` key on LocalStorage to see logs. + * + * @example + * + * Node.js: + * + * ```console + * $ DEBUG="*libp2p:*" node myscript.js + * ``` + * + * Browsers: + * + * ```javascript + * localStorage.setItem('debug', '*libp2p:*') + * ``` + */ + logger?: ComponentLogger } export type { Libp2p } diff --git a/packages/libp2p/src/libp2p.ts b/packages/libp2p/src/libp2p.ts index 5708aa59e3..e39511b1c9 100644 --- a/packages/libp2p/src/libp2p.ts +++ b/packages/libp2p/src/libp2p.ts @@ -5,7 +5,7 @@ import { TypedEventEmitter, CustomEvent, setMaxListeners } from '@libp2p/interfa import { peerDiscovery } from '@libp2p/interface/peer-discovery' import { type PeerRouting, peerRouting } from '@libp2p/interface/peer-routing' import { DefaultKeyChain } from '@libp2p/keychain' -import { logger } from '@libp2p/logger' +import { defaultLogger } from '@libp2p/logger' import { PeerSet } from '@libp2p/peer-collections' import { peerIdFromString } from '@libp2p/peer-id' import { createEd25519PeerId } from '@libp2p/peer-id-factory' @@ -28,7 +28,7 @@ import { DefaultTransportManager } from './transport-manager.js' import { DefaultUpgrader } from './upgrader.js' import type { Components } from './components.js' import type { Libp2p, Libp2pInit, Libp2pOptions } from './index.js' -import type { Libp2pEvents, PendingDial, ServiceMap, AbortOptions } from '@libp2p/interface' +import type { Libp2pEvents, PendingDial, ServiceMap, AbortOptions, ComponentLogger, Logger } from '@libp2p/interface' import type { Connection, NewStreamOptions, Stream } from '@libp2p/interface/connection' import type { KeyChain } from '@libp2p/interface/keychain' import type { Metrics } from '@libp2p/interface/metrics' @@ -39,8 +39,6 @@ import type { Topology } from '@libp2p/interface/topology' import type { StreamHandler, StreamHandlerOptions } from '@libp2p/interface-internal/registrar' import type { Datastore } from 'interface-datastore' -const log = logger('libp2p') - export class Libp2pNode> extends TypedEventEmitter implements Libp2p { public peerId: PeerId public peerStore: PeerStore @@ -49,9 +47,11 @@ export class Libp2pNode> extends public keychain: KeyChain public metrics?: Metrics public services: T + public logger: ComponentLogger public components: Components #started: boolean + readonly #log: Logger constructor (init: Libp2pInit) { super() @@ -74,10 +74,13 @@ export class Libp2pNode> extends this.#started = false this.peerId = init.peerId + this.logger = init.logger ?? defaultLogger() + this.#log = this.logger.forComponent('libp2p') // @ts-expect-error {} may not be of type T this.services = {} const components = this.components = defaultComponents({ peerId: init.peerId, + logger: this.logger, events, datastore: init.datastore ?? new MemoryDatastore(), connectionGater: connectionGater(init.connectionGater) @@ -170,7 +173,7 @@ export class Libp2pNode> extends const service: any = createService(this.components) if (service == null) { - log.error('service factory %s returned null or undefined instance', name) + this.#log.error('service factory %s returned null or undefined instance', name) continue } @@ -178,17 +181,17 @@ export class Libp2pNode> extends this.configureComponent(name, service) if (service[contentRouting] != null) { - log('registering service %s for content routing', name) + this.#log('registering service %s for content routing', name) contentRouters.push(service[contentRouting]) } if (service[peerRouting] != null) { - log('registering service %s for peer routing', name) + this.#log('registering service %s for peer routing', name) peerRouters.push(service[peerRouting]) } if (service[peerDiscovery] != null) { - log('registering service %s for peer discovery', name) + this.#log('registering service %s for peer discovery', name) service[peerDiscovery].addEventListener('peer', (evt: CustomEvent) => { this.#onDiscoveryPeer(evt) }) @@ -199,7 +202,7 @@ export class Libp2pNode> extends private configureComponent (name: string, component: T): T { if (component == null) { - log.error('component %s was null or undefined', name) + this.#log.error('component %s was null or undefined', name) } this.components[name] = component @@ -217,12 +220,12 @@ export class Libp2pNode> extends this.#started = true - log('libp2p is starting') + this.#log('libp2p is starting') const keys = await this.keychain.listKeys() if (keys.find(key => key.name === 'self') == null) { - log('importing self key into keychain') + this.#log('importing self key into keychain') await this.keychain.importPeer('self', this.components.peerId) } @@ -232,9 +235,9 @@ export class Libp2pNode> extends await this.components.afterStart?.() this.safeDispatchEvent('start', { detail: this }) - log('libp2p has started') + this.#log('libp2p has started') } catch (err: any) { - log.error('An error occurred starting libp2p', err) + this.#log.error('An error occurred starting libp2p', err) await this.stop() throw err } @@ -248,7 +251,7 @@ export class Libp2pNode> extends return } - log('libp2p is stopping') + this.#log('libp2p is stopping') this.#started = false @@ -257,7 +260,7 @@ export class Libp2pNode> extends await this.components.afterStop?.() this.safeDispatchEvent('stop', { detail: this }) - log('libp2p has stopped') + this.#log('libp2p has stopped') } isStarted (): boolean { @@ -322,7 +325,7 @@ export class Libp2pNode> extends * Get the public key for the given peer id */ async getPublicKey (peer: PeerId, options: AbortOptions = {}): Promise { - log('getPublicKey %p', peer) + this.#log('getPublicKey %p', peer) if (peer.publicKey != null) { return peer.publicKey @@ -391,7 +394,7 @@ export class Libp2pNode> extends const { detail: peer } = evt if (peer.id.toString() === this.peerId.toString()) { - log.error(new Error(codes.ERR_DISCOVERED_SELF)) + this.#log.error(new Error(codes.ERR_DISCOVERED_SELF)) return } @@ -399,7 +402,7 @@ export class Libp2pNode> extends multiaddrs: peer.multiaddrs, protocols: peer.protocols }) - .catch(err => { log.error(err) }) + .catch(err => { this.#log.error(err) }) } } diff --git a/packages/libp2p/src/upgrader.ts b/packages/libp2p/src/upgrader.ts index 0789e55900..f6b0bc574a 100644 --- a/packages/libp2p/src/upgrader.ts +++ b/packages/libp2p/src/upgrader.ts @@ -1,13 +1,12 @@ import { CodeError } from '@libp2p/interface/errors' import { setMaxListeners } from '@libp2p/interface/events' -import { logger } from '@libp2p/logger' import * as mss from '@libp2p/multistream-select' import { peerIdFromString } from '@libp2p/peer-id' import { createConnection } from './connection/index.js' import { INBOUND_UPGRADE_TIMEOUT } from './connection-manager/constants.js' import { codes } from './errors.js' import { DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS } from './registrar.js' -import type { Libp2pEvents, AbortOptions } from '@libp2p/interface' +import type { Libp2pEvents, AbortOptions, ComponentLogger, Logger } from '@libp2p/interface' import type { MultiaddrConnection, Connection, Stream, ConnectionProtector, NewStreamOptions } from '@libp2p/interface/connection' import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface/connection-encrypter' import type { ConnectionGater } from '@libp2p/interface/connection-gater' @@ -21,8 +20,6 @@ import type { ConnectionManager } from '@libp2p/interface-internal/connection-ma import type { Registrar } from '@libp2p/interface-internal/registrar' import type { Duplex, Source } from 'it-stream-types' -const log = logger('libp2p:upgrader') - interface CreateConnectionOptions { cryptoProtocol: string direction: 'inbound' | 'outbound' @@ -105,6 +102,7 @@ export interface DefaultUpgraderComponents { registrar: Registrar peerStore: PeerStore events: TypedEventTarget + logger: ComponentLogger } type EncryptedConn = Duplex, Source, Promise> @@ -117,10 +115,12 @@ export class DefaultUpgrader implements Upgrader { private readonly muxers: Map private readonly inboundUpgradeTimeout: number private readonly events: TypedEventTarget + readonly #log: Logger constructor (components: DefaultUpgraderComponents, init: UpgraderInit) { this.components = components this.connectionEncryption = new Map() + this.#log = components.logger.forComponent('libp2p:upgrader') init.connectionEncryption.forEach(encrypter => { this.connectionEncryption.set(encrypter.protocol, encrypter) @@ -179,7 +179,7 @@ export class DefaultUpgrader implements Upgrader { this.components.metrics?.trackMultiaddrConnection(maConn) - log('starting the inbound connection upgrade') + this.#log('starting the inbound connection upgrade') // Protect let protectedConn = maConn @@ -188,7 +188,7 @@ export class DefaultUpgrader implements Upgrader { const protector = this.components.connectionProtector if (protector != null) { - log('protecting the inbound connection') + this.#log('protecting the inbound connection') protectedConn = await protector.protect(maConn) } } @@ -235,13 +235,13 @@ export class DefaultUpgrader implements Upgrader { upgradedConn = multiplexed.stream } } catch (err: any) { - log.error('Failed to upgrade inbound connection', err) + this.#log.error('Failed to upgrade inbound connection', err) throw err } await this.shouldBlockConnection(remotePeer, maConn, 'denyInboundUpgradedConnection') - log('Successfully upgraded inbound connection') + this.#log('Successfully upgraded inbound connection') return this._createConnection({ cryptoProtocol, @@ -280,7 +280,7 @@ export class DefaultUpgrader implements Upgrader { this.components.metrics?.trackMultiaddrConnection(maConn) - log('Starting the outbound connection upgrade') + this.#log('Starting the outbound connection upgrade') // If the transport natively supports encryption, skip connection // protector and encryption @@ -333,14 +333,14 @@ export class DefaultUpgrader implements Upgrader { upgradedConn = multiplexed.stream } } catch (err: any) { - log.error('Failed to upgrade outbound connection', err) + this.#log.error('Failed to upgrade outbound connection', err) await maConn.close(err) throw err } await this.shouldBlockConnection(remotePeer, maConn, 'denyOutboundUpgradedConnection') - log('Successfully upgraded outbound connection') + this.#log('Successfully upgraded outbound connection') return this._createConnection({ cryptoProtocol, @@ -385,7 +385,7 @@ export class DefaultUpgrader implements Upgrader { .then(async () => { const protocols = this.components.registrar.getProtocols() const { stream, protocol } = await mss.handle(muxedStream, protocols) - log('%s: incoming stream opened on %s', direction, protocol) + this.#log('%s: incoming stream opened on %s', direction, protocol) if (connection == null) { return @@ -418,7 +418,7 @@ export class DefaultUpgrader implements Upgrader { this._onStream({ connection, stream: muxedStream, protocol }) }) .catch(async err => { - log.error(err) + this.#log.error(err) if (muxedStream.timeline.close == null) { await muxedStream.close() @@ -432,12 +432,12 @@ export class DefaultUpgrader implements Upgrader { throw new CodeError('Stream is not multiplexed', codes.ERR_MUXER_UNAVAILABLE) } - log('%s-%s: starting new stream on %s', connection.id, direction, protocols) + this.#log('%s-%s: starting new stream on %s', connection.id, direction, protocols) const muxedStream = await muxer.newStream() try { if (options.signal == null) { - log('No abort signal was passed while trying to negotiate protocols %s falling back to default timeout', protocols) + this.#log('No abort signal was passed while trying to negotiate protocols %s falling back to default timeout', protocols) options.signal = AbortSignal.timeout(30000) @@ -472,7 +472,7 @@ export class DefaultUpgrader implements Upgrader { return muxedStream } catch (err: any) { - log.error('could not create new stream for protocols %s on connection with address %a', protocols, connection.remoteAddr, err) + this.#log.error('could not create new stream for protocols %s on connection with address %a', protocols, connection.remoteAddr, err) if (muxedStream.timeline.close == null) { muxedStream.abort(err) @@ -491,7 +491,7 @@ export class DefaultUpgrader implements Upgrader { muxer.sink(upgradedConn.source), upgradedConn.sink(muxer.source) ]).catch(err => { - log.error(err) + this.#log.error(err) }) } @@ -506,14 +506,14 @@ export class DefaultUpgrader implements Upgrader { await connection.close() } } catch (err: any) { - log.error(err) + this.#log.error(err) } finally { this.events.safeDispatchEvent('connection:close', { detail: connection }) } })().catch(err => { - log.error(err) + this.#log.error(err) }) } @@ -536,19 +536,20 @@ export class DefaultUpgrader implements Upgrader { multiplexer: muxer?.protocol, encryption: cryptoProtocol, transient, + logger: this.components.logger, newStream: newStream ?? errConnectionNotMultiplexed, getStreams: () => { if (muxer != null) { return muxer.streams } else { return [] } }, close: async (options?: AbortOptions) => { // Ensure remaining streams are closed gracefully if (muxer != null) { - log.trace('close muxer') + this.#log.trace('close muxer') await muxer.close(options) } - log.trace('close maconn') + this.#log.trace('close maconn') // close the underlying transport await maConn.close(options) - log.trace('closed maconn') + this.#log.trace('closed maconn') }, abort: (err) => { maConn.abort(err) @@ -585,7 +586,7 @@ export class DefaultUpgrader implements Upgrader { */ async _encryptInbound (connection: Duplex, Source>): Promise { const protocols = Array.from(this.connectionEncryption.keys()) - log('handling inbound crypto protocol selection', protocols) + this.#log('handling inbound crypto protocol selection', protocols) try { const { stream, protocol } = await mss.handle(connection, protocols, { @@ -597,7 +598,7 @@ export class DefaultUpgrader implements Upgrader { throw new Error(`no crypto module found for ${protocol}`) } - log('encrypting inbound connection...') + this.#log('encrypting inbound connection...') return { ...await encrypter.secureInbound(this.components.peerId, stream), @@ -614,7 +615,7 @@ export class DefaultUpgrader implements Upgrader { */ async _encryptOutbound (connection: MultiaddrConnection, remotePeerId?: PeerId): Promise { const protocols = Array.from(this.connectionEncryption.keys()) - log('selecting outbound crypto protocol', protocols) + this.#log('selecting outbound crypto protocol', protocols) try { const { stream, protocol } = await mss.select(connection, protocols, { @@ -626,7 +627,7 @@ export class DefaultUpgrader implements Upgrader { throw new Error(`no crypto module found for ${protocol}`) } - log('encrypting outbound connection to %p', remotePeerId) + this.#log('encrypting outbound connection to %p', remotePeerId) return { ...await encrypter.secureOutbound(this.components.peerId, stream, remotePeerId), @@ -643,17 +644,17 @@ export class DefaultUpgrader implements Upgrader { */ async _multiplexOutbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, Source, Promise>, muxerFactory?: StreamMuxerFactory }> { const protocols = Array.from(muxers.keys()) - log('outbound selecting muxer %s', protocols) + this.#log('outbound selecting muxer %s', protocols) try { const { stream, protocol } = await mss.select(connection, protocols, { writeBytes: true }) - log('%s selected as muxer protocol', protocol) + this.#log('%s selected as muxer protocol', protocol) const muxerFactory = muxers.get(protocol) return { stream, muxerFactory } } catch (err: any) { - log.error('error multiplexing outbound stream', err) + this.#log.error('error multiplexing outbound stream', err) throw new CodeError(String(err), codes.ERR_MUXER_UNAVAILABLE) } } @@ -664,7 +665,7 @@ export class DefaultUpgrader implements Upgrader { */ async _multiplexInbound (connection: MultiaddrConnection, muxers: Map): Promise<{ stream: Duplex, Source, Promise>, muxerFactory?: StreamMuxerFactory }> { const protocols = Array.from(muxers.keys()) - log('inbound handling muxers %s', protocols) + this.#log('inbound handling muxers %s', protocols) try { const { stream, protocol } = await mss.handle(connection, protocols, { writeBytes: true @@ -673,7 +674,7 @@ export class DefaultUpgrader implements Upgrader { return { stream, muxerFactory } } catch (err: any) { - log.error('error multiplexing inbound stream', err) + this.#log.error('error multiplexing inbound stream', err) throw new CodeError(String(err), codes.ERR_MUXER_UNAVAILABLE) } } diff --git a/packages/libp2p/test/connection-manager/auto-dial.spec.ts b/packages/libp2p/test/connection-manager/auto-dial.spec.ts index 9e45ae8c7b..b9d32357a2 100644 --- a/packages/libp2p/test/connection-manager/auto-dial.spec.ts +++ b/packages/libp2p/test/connection-manager/auto-dial.spec.ts @@ -12,6 +12,7 @@ import pWaitFor from 'p-wait-for' import Sinon from 'sinon' import { stubInterface } from 'sinon-ts' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { defaultComponents } from '../../src/components.js' import { AutoDial } from '../../src/connection-manager/auto-dial.js' import { LAST_DIAL_FAILURE_KEY } from '../../src/connection-manager/constants.js' import { matchPeerId } from '../fixtures/match-peer-id.js' @@ -73,11 +74,11 @@ describe('auto-dial', () => { getDialQueue: Sinon.stub().returns([]) }) - autoDialler = new AutoDial({ + autoDialler = new AutoDial(defaultComponents({ peerStore, connectionManager, events - }, { + }), { minConnections: 10, autoDialInterval: 10000 }) @@ -127,11 +128,11 @@ describe('auto-dial', () => { getDialQueue: Sinon.stub().returns([]) }) - autoDialler = new AutoDial({ + autoDialler = new AutoDial(defaultComponents({ peerStore, connectionManager, events - }, { + }), { minConnections: 10 }) autoDialler.start() @@ -181,11 +182,11 @@ describe('auto-dial', () => { }]) }) - autoDialler = new AutoDial({ + autoDialler = new AutoDial(defaultComponents({ peerStore, connectionManager, events - }, { + }), { minConnections: 10 }) autoDialler.start() @@ -207,11 +208,11 @@ describe('auto-dial', () => { getDialQueue: Sinon.stub().returns([]) }) - autoDialler = new AutoDial({ + autoDialler = new AutoDial(defaultComponents({ peerStore, connectionManager, events - }, { + }), { minConnections: 10, autoDialInterval: 10000 }) @@ -258,11 +259,11 @@ describe('auto-dial', () => { getDialQueue: Sinon.stub().returns([]) }) - autoDialler = new AutoDial({ + autoDialler = new AutoDial(defaultComponents({ peerStore, connectionManager, events - }, { + }), { minConnections: 10, autoDialPeerRetryThreshold: 2000 }) diff --git a/packages/libp2p/test/connection-manager/dial-queue.spec.ts b/packages/libp2p/test/connection-manager/dial-queue.spec.ts index 9a4ae50f5f..ed07ab79e5 100644 --- a/packages/libp2p/test/connection-manager/dial-queue.spec.ts +++ b/packages/libp2p/test/connection-manager/dial-queue.spec.ts @@ -1,6 +1,7 @@ /* eslint-env mocha */ import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' +import { peerLogger } from '@libp2p/logger' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr, resolvers } from '@multiformats/multiaddr' import { WebRTC } from '@multiformats/multiaddr-matcher' @@ -10,6 +11,7 @@ import pDefer from 'p-defer' import sinon from 'sinon' import { type StubbedInstance, stubInterface } from 'sinon-ts' import { DialQueue } from '../../src/connection-manager/dial-queue.js' +import type { ComponentLogger } from '@libp2p/interface' import type { Connection } from '@libp2p/interface/connection' import type { ConnectionGater } from '@libp2p/interface/connection-gater' import type { PeerId } from '@libp2p/interface/peer-id' @@ -23,15 +25,19 @@ describe('dial queue', () => { peerStore: StubbedInstance transportManager: StubbedInstance connectionGater: StubbedInstance + logger: ComponentLogger } let dialer: DialQueue beforeEach(async () => { + const peerId = await createEd25519PeerId() + components = { - peerId: await createEd25519PeerId(), + peerId, peerStore: stubInterface(), transportManager: stubInterface(), - connectionGater: stubInterface() + connectionGater: stubInterface(), + logger: peerLogger(peerId) } }) diff --git a/packages/libp2p/test/connection-manager/index.spec.ts b/packages/libp2p/test/connection-manager/index.spec.ts index 4cde008591..17b33439dc 100644 --- a/packages/libp2p/test/connection-manager/index.spec.ts +++ b/packages/libp2p/test/connection-manager/index.spec.ts @@ -3,6 +3,7 @@ import { TypedEventEmitter } from '@libp2p/interface/events' import { KEEP_ALIVE } from '@libp2p/interface/peer-store/tags' import { mockConnection, mockDuplex, mockMultiaddrConnection, mockMetrics } from '@libp2p/interface-compliance-tests/mocks' +import { defaultLogger } from '@libp2p/logger' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' @@ -10,13 +11,14 @@ import { pEvent } from 'p-event' import pWaitFor from 'p-wait-for' import sinon from 'sinon' import { stubInterface } from 'sinon-ts' -import { DefaultConnectionManager } from '../../src/connection-manager/index.js' +import { DefaultConnectionManager, type DefaultConnectionManagerComponents } from '../../src/connection-manager/index.js' import { createBaseOptions } from '../fixtures/base-options.browser.js' import { createNode } from '../fixtures/creators/peer.js' import type { Libp2pNode } from '../../src/libp2p.js' import type { AbortOptions } from '@libp2p/interface' import type { Connection } from '@libp2p/interface/connection' import type { ConnectionGater } from '@libp2p/interface/connection-gater' +import type { PeerId } from '@libp2p/interface/peer-id' import type { PeerStore } from '@libp2p/interface/peer-store' import type { TransportManager } from '@libp2p/interface-internal/transport-manager' @@ -27,6 +29,17 @@ const defaultOptions = { inboundUpgradeTimeout: 10000 } +function defaultComponents (peerId: PeerId): DefaultConnectionManagerComponents { + return { + peerId, + peerStore: stubInterface(), + transportManager: stubInterface(), + connectionGater: stubInterface(), + events: new TypedEventEmitter(), + logger: defaultLogger() + } +} + describe('Connection Manager', () => { let libp2p: Libp2pNode let connectionManager: DefaultConnectionManager @@ -360,13 +373,7 @@ describe('Connection Manager', () => { it('should deny connections from denylist multiaddrs', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') - connectionManager = new DefaultConnectionManager({ - peerId: libp2p.peerId, - peerStore: stubInterface(), - transportManager: stubInterface(), - connectionGater: stubInterface(), - events: new TypedEventEmitter() - }, { + connectionManager = new DefaultConnectionManager(defaultComponents(libp2p.peerId), { ...defaultOptions, deny: [ '/ip4/83.13.55.32' @@ -388,13 +395,7 @@ describe('Connection Manager', () => { }) it('should deny connections when maxConnections is exceeded', async () => { - connectionManager = new DefaultConnectionManager({ - peerId: libp2p.peerId, - peerStore: stubInterface(), - transportManager: stubInterface(), - connectionGater: stubInterface(), - events: new TypedEventEmitter() - }, { + connectionManager = new DefaultConnectionManager(defaultComponents(libp2p.peerId), { ...defaultOptions, maxConnections: 1 }) @@ -420,13 +421,7 @@ describe('Connection Manager', () => { }) it('should deny connections from peers that connect too frequently', async () => { - connectionManager = new DefaultConnectionManager({ - peerId: libp2p.peerId, - peerStore: stubInterface(), - transportManager: stubInterface(), - connectionGater: stubInterface(), - events: new TypedEventEmitter() - }, { + connectionManager = new DefaultConnectionManager(defaultComponents(libp2p.peerId), { ...defaultOptions, inboundConnectionThreshold: 1 }) @@ -456,13 +451,7 @@ describe('Connection Manager', () => { it('should allow connections from allowlist multiaddrs', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') - connectionManager = new DefaultConnectionManager({ - peerId: libp2p.peerId, - peerStore: stubInterface(), - transportManager: stubInterface(), - connectionGater: stubInterface(), - events: new TypedEventEmitter() - }, { + connectionManager = new DefaultConnectionManager(defaultComponents(libp2p.peerId), { ...defaultOptions, maxConnections: 1, allow: [ @@ -492,13 +481,7 @@ describe('Connection Manager', () => { }) it('should limit the number of inbound pending connections', async () => { - connectionManager = new DefaultConnectionManager({ - peerId: await createEd25519PeerId(), - peerStore: stubInterface(), - transportManager: stubInterface(), - connectionGater: stubInterface(), - events: new TypedEventEmitter() - }, { + connectionManager = new DefaultConnectionManager(defaultComponents(libp2p.peerId), { ...defaultOptions, maxIncomingPendingConnections: 1 }) @@ -538,13 +521,7 @@ describe('Connection Manager', () => { }) it('should allow dialing peers when an existing transient connection exists', async () => { - connectionManager = new DefaultConnectionManager({ - peerId: await createEd25519PeerId(), - peerStore: stubInterface(), - transportManager: stubInterface(), - connectionGater: stubInterface(), - events: new TypedEventEmitter() - }, { + connectionManager = new DefaultConnectionManager(defaultComponents(libp2p.peerId), { ...defaultOptions, maxIncomingPendingConnections: 1 }) diff --git a/packages/libp2p/test/connection/compliance.spec.ts b/packages/libp2p/test/connection/compliance.spec.ts index 41059425b7..715f946f4e 100644 --- a/packages/libp2p/test/connection/compliance.spec.ts +++ b/packages/libp2p/test/connection/compliance.spec.ts @@ -1,5 +1,6 @@ import tests from '@libp2p/interface-compliance-tests/connection' import peers from '@libp2p/interface-compliance-tests/peers' +import { peerLogger } from '@libp2p/logger' import * as PeerIdFactory from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' import { createConnection } from '../../src/connection/index.js' @@ -13,6 +14,7 @@ describe('connection compliance', () => { * certain values for testing. */ async setup (properties) { + const localPeer = await PeerIdFactory.createEd25519PeerId() const remoteAddr = multiaddr('/ip4/127.0.0.1/tcp/8081') const remotePeer = await PeerIdFactory.createFromJSON(peers[0]) let openStreams: Stream[] = [] @@ -29,6 +31,7 @@ describe('connection compliance', () => { encryption: '/secio/1.0.0', multiplexer: '/mplex/6.7.0', status: 'open', + logger: peerLogger(localPeer), newStream: async (protocols) => { const id = `${streamId++}` const stream: Stream = { diff --git a/packages/libp2p/test/connection/index.spec.ts b/packages/libp2p/test/connection/index.spec.ts index d249731f55..9fd8121d7f 100644 --- a/packages/libp2p/test/connection/index.spec.ts +++ b/packages/libp2p/test/connection/index.spec.ts @@ -1,3 +1,4 @@ +import { defaultLogger } from '@libp2p/logger' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' @@ -17,7 +18,8 @@ function defaultConnectionInit (): any { newStream: Sinon.stub(), close: Sinon.stub(), abort: Sinon.stub(), - getStreams: Sinon.stub() + getStreams: Sinon.stub(), + logger: defaultLogger() } } diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 1bb24ea8dc..be3572c218 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,9 +1,8 @@ /** * @packageDocumentation * - * A map that reports it's size to the libp2p [Metrics](https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/metrics#readme) system. - * - * If metrics are disabled a regular map is used. + * A logger for libp2p based on the venerable [debug](https://www.npmjs.com/package/debug) + * module. * * @example * @@ -33,6 +32,7 @@ import debug from 'debug' import { base32 } from 'multiformats/bases/base32' import { base58btc } from 'multiformats/bases/base58' import { base64 } from 'multiformats/bases/base64' +import { truncatePeerId } from './utils.js' import type { PeerId } from '@libp2p/interface/peer-id' import type { Multiaddr } from '@multiformats/multiaddr' import type { Key } from 'interface-datastore' @@ -80,6 +80,10 @@ export interface Logger { enabled: boolean } +export interface ComponentLogger { + forComponent(name: string): Logger +} + function createDisabledLogger (namespace: string): debug.Debugger { const logger = (): void => {} logger.enabled = false @@ -93,6 +97,94 @@ function createDisabledLogger (namespace: string): debug.Debugger { return logger } +export interface PeerLoggerOptions { + prefixLength: number + suffixLength: number +} + +/** + * Create a component logger that will prefix any log messages with a truncated + * peer id. + * + * @example + * + * ```TypeScript + * import { peerLogger } from '@libp2p/logger' + * import { peerIdFromString } from '@libp2p/peer-id' + * + * const peerId = peerIdFromString('12D3FooBar') + * const logger = peerLogger(peerId) + * + * const log = logger.forComponent('my-component') + * log.info('hello world') + * // logs "12…oBar:my-component hello world" + * ``` + */ +export function peerLogger (peerId: PeerId, options: Partial = {}): ComponentLogger { + return prefixLogger(truncatePeerId(peerId, options)) +} + +/** + * Create a component logger that will prefix any log messages with the passed + * string. + * + * @example + * + * ```TypeScript + * import { prefixLogger } from '@libp2p/logger' + * + * const logger = prefixLogger('my-node') + * + * const log = logger.forComponent('my-component') + * log.info('hello world') + * // logs "my-node:my-component hello world" + * ``` + */ +export function prefixLogger (prefix: string): ComponentLogger { + return { + forComponent (name: string) { + return logger(`${prefix}:${name}`) + } + } +} + +/** + * Create a component logger + * + * @example + * + * ```TypeScript + * import { defaultLogger } from '@libp2p/logger' + * import { peerIdFromString } from '@libp2p/peer-id' + * + * const logger = defaultLogger() + * + * const log = logger.forComponent('my-component') + * log.info('hello world') + * // logs "my-component hello world" + * ``` + */ +export function defaultLogger (): ComponentLogger { + return { + forComponent (name: string) { + return logger(name) + } + } +} + +/** + * Creates a logger for the passed component name. + * + * @example + * + * ```TypeScript + * import { logger } from '@libp2p/logger' + * + * const log = logger('my-component') + * log.info('hello world') + * // logs "my-component hello world" + * ``` + */ export function logger (name: string): Logger { // trace logging is a no-op by default let trace: debug.Debugger = createDisabledLogger(`${name}:trace`) diff --git a/packages/logger/src/utils.ts b/packages/logger/src/utils.ts new file mode 100644 index 0000000000..27b4ebcc62 --- /dev/null +++ b/packages/logger/src/utils.ts @@ -0,0 +1,10 @@ +import type { PeerLoggerOptions } from './index.js' +import type { PeerId } from '@libp2p/interface/peer-id' + +export function truncatePeerId (peerId: PeerId, options: Partial = {}): string { + const prefixLength = options.prefixLength ?? 2 + const suffixLength = options.suffixLength ?? 4 + + const peerIdString = peerId.toString() + return `${peerIdString.substring(0, prefixLength)}…${peerIdString.substring(peerIdString.length, peerIdString.length - suffixLength)}` +} diff --git a/packages/logger/test/index.spec.ts b/packages/logger/test/index.spec.ts index 8b8b1b6731..5c1c9dccd6 100644 --- a/packages/logger/test/index.spec.ts +++ b/packages/logger/test/index.spec.ts @@ -9,7 +9,7 @@ import { base64 } from 'multiformats/bases/base64' import sinon from 'sinon' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as unint8ArrayToString } from 'uint8arrays/to-string' -import { logger } from '../src/index.js' +import { logger, peerLogger } from '../src/index.js' describe('logger', () => { it('creates a logger', () => { @@ -23,6 +23,19 @@ describe('logger', () => { expect(log).to.have.nested.property('trace.enabled').that.is.not.true() }) + it('creates a peer logger', () => { + const peerId = peerIdFromString('12D3KooWLkHeUp6r5unBZKbYwV54CgKVxHuJDroFoigr8mF11CKW') + const logger = peerLogger(peerId) + const log = logger.forComponent('hello') + + expect(log).to.be.a('function') + expect(log).to.a.property('enabled').that.is.not.true() + expect(log).to.have.property('error').that.is.a('function') + expect(log).to.have.nested.property('error.enabled').that.is.not.true() + expect(log).to.have.property('trace').that.is.a('function') + expect(log).to.have.nested.property('trace.enabled').that.is.not.true() + }) + it('creates a logger with logging enabled', () => { debug.enable('enabled-logger') diff --git a/packages/logger/test/utils.spec.ts b/packages/logger/test/utils.spec.ts new file mode 100644 index 0000000000..5525ab0d82 --- /dev/null +++ b/packages/logger/test/utils.spec.ts @@ -0,0 +1,11 @@ +import { peerIdFromString } from '@libp2p/peer-id' +import { expect } from 'aegir/chai' +import { truncatePeerId } from '../src/utils.js' + +describe('utils', () => { + it('should truncate a peer id', () => { + const peerId = peerIdFromString('QmZ8eiDPqQqWR17EPxiwCDgrKPVhCHLcyn6xSCNpFAdAZb') + + expect(truncatePeerId(peerId)).to.equal('Qm…dAZb') + }) +})