diff --git a/src/AudioMixer.ts b/src/AudioMixer.ts index a60e6bfe..96fde04b 100644 --- a/src/AudioMixer.ts +++ b/src/AudioMixer.ts @@ -15,6 +15,7 @@ import AudioOutput from "./domain/audio/AudioOutput"; import AudioClient from "./domain/audio-client/AudioClient"; import NodeType from "./domain/networking/NodeType"; import ContextManager from "./domain/shared/ContextManager"; +import Log from "./domain/shared/Log"; /*@sdkdoc @@ -109,13 +110,13 @@ class AudioMixer extends AssignmentClient { set audioInput(audioInput: MediaStream | null) { if (audioInput !== null && !(audioInput instanceof MediaStream)) { - console.error("Tried to set an invalid AudioMixer.audioInput value!"); + Log.error("Tried to set an invalid AudioMixer.audioInput value!", "audio"); return; } void this.#_audioClient.switchInputDevice(audioInput).then((success) => { if (!success) { - console.warn("Could not set the audio input."); + Log.warning("Could not set the audio input.", "audio"); } }); } @@ -126,7 +127,7 @@ class AudioMixer extends AssignmentClient { set inputMuted(inputMuted: boolean) { if (typeof inputMuted !== "boolean") { - console.error("Tried to set an invalid AudioMixer.inputMuted value!"); + Log.error("Tried to set an invalid AudioMixer.inputMuted value!", "audio"); return; } this.#_audioClient.setMuted(inputMuted); diff --git a/src/DomainServer.ts b/src/DomainServer.ts index 0f4e5ede..ee21f0cb 100644 --- a/src/DomainServer.ts +++ b/src/DomainServer.ts @@ -15,6 +15,7 @@ import Node from "./domain/networking/Node"; import NodeList from "./domain/networking/NodeList"; import NodeType from "./domain/networking/NodeType"; import ContextManager from "./domain/shared/ContextManager"; +import Log from "./domain/shared/Log"; import SignalEmitter, { Signal } from "./domain/shared/SignalEmitter"; import Uuid from "./domain/shared/Uuid"; @@ -219,7 +220,7 @@ class DomainServer { if (typeof callback === "function" || callback === null) { this.#_onStateChanged = callback; } else { - console.error("ERROR: DomainServer.onStateChanged callback not a function or null!"); + Log.error("ERROR: DomainServer.onStateChanged callback not a function or null!"); this.#_onStateChanged = null; } } @@ -264,7 +265,7 @@ class DomainServer { if (typeof location === "string") { this.#_location = location.trim(); } else { - console.error("ERROR: DomainServer.connect() location parameter not a string!"); + Log.error("ERROR: DomainServer.connect() location parameter not a string!"); this.#_location = ""; } @@ -316,7 +317,7 @@ class DomainServer { #setState(state: ConnectionState, info = ""): void { const hasStateChanged = state !== this.#_state; if (this.#_DEBUG && !hasStateChanged) { - console.warn("DomainServer: State hasn't changed."); + Log.warning("DomainServer: State hasn't changed."); } this.#_state = state; @@ -366,7 +367,7 @@ class DomainServer { #nodeAdded = (node: Node): void => { // C++ void Application:: nodeAdded(Node* node) if (node.getType() === NodeType.EntityServer) { - console.warn("DomainServer: EntityServer support implemented!"); + Log.warning("DomainServer: EntityServer support implemented!"); // WEBRTC TODO: Address further code - for EntityServer node. @@ -381,17 +382,17 @@ class DomainServer { // AudioMixer node is handled in AudioMixer.ts. if (nodeType === NodeType.AssetServer) { - console.warn("DomainServer: AssetServer support not implemented!"); + Log.warning("DomainServer: AssetServer support not implemented!"); // WEBRTC TODO: Address further code - for AssetServer node. } else if (nodeType === NodeType.EntityServer) { - console.warn("DomainServer: EntityServer support not implemented!"); + Log.warning("DomainServer: EntityServer support not implemented!"); // WEBRTC TODO: Address further code - for EntityServer node. } else if (nodeType === NodeType.AvatarMixer) { - console.warn("DomainServer: AvatarMixer support not implemented!"); + Log.warning("DomainServer: AvatarMixer support not implemented!"); // WEBRTC TODO: Address further code - for AvatarMixer node. @@ -408,12 +409,12 @@ class DomainServer { // AudioMixer node is handled in AudioMixer.ts. if (nodeType === NodeType.EntityServer) { - console.warn("DomainServer: EntityServer support not implemented!"); + Log.warning("DomainServer: EntityServer support not implemented!"); // WEBRTC TODO: Address further code - for EntityServer node. } else if (nodeType === NodeType.AssetServer) { - console.warn("DomainServer: AssetServer support not implemented!"); + Log.warning("DomainServer: AssetServer support not implemented!"); // WEBRTC TODO: Address further code - for AssetServer node. diff --git a/src/MessageMixer.ts b/src/MessageMixer.ts index 71c18f4f..1eda83df 100644 --- a/src/MessageMixer.ts +++ b/src/MessageMixer.ts @@ -14,6 +14,7 @@ import AssignmentClient from "./domain/AssignmentClient"; import MessagesClient from "./domain/networking/MessagesClient"; import NodeType from "./domain/networking/NodeType"; import { Signal } from "./domain/shared/SignalEmitter"; +import Log from "./domain/shared/Log"; /*@sdkdoc @@ -97,7 +98,7 @@ class MessageMixer extends AssignmentClient { */ subscribe(channel: string): void { if (typeof channel !== "string" || channel.length === 0) { - console.error("[MessageMixer] subscribe() called with invalid channel value!"); + Log.error("subscribe() called with invalid channel value!", "MessageMixer"); return; } @@ -111,7 +112,7 @@ class MessageMixer extends AssignmentClient { */ unsubscribe(channel: string): void { if (typeof channel !== "string" || channel.length === 0) { - console.error("[MessageMixer] unsubscribe() called with invalid channel value!"); + Log.error("unsubscribe() called with invalid channel value!", "MessageMixer"); return; } @@ -133,7 +134,7 @@ class MessageMixer extends AssignmentClient { // @ts-ignore sendMessage(channel: string, message: string, localOnly = false): void { // eslint-disable-line if (typeof channel !== "string" || typeof message !== "string" || typeof localOnly !== "boolean") { - console.error("[MessageMixer] sendMessage() called with invalid channel parameters!"); + Log.error("sendMessage() called with invalid channel parameters!", "MessageMixer"); return; } @@ -156,7 +157,7 @@ class MessageMixer extends AssignmentClient { // @ts-ignore sendData(channel: string, data: ArrayBuffer, localOnly = false): void { // eslint-disable-line if (typeof channel !== "string" || !(data instanceof ArrayBuffer) || typeof localOnly !== "boolean") { - console.error("[MessageMixer] sendData() called with invalid channel parameters!"); + Log.error("sendData() called with invalid channel parameters!", "MessageMixer"); return; } diff --git a/src/domain/AssignmentClient.ts b/src/domain/AssignmentClient.ts index ef238e27..637b0582 100644 --- a/src/domain/AssignmentClient.ts +++ b/src/domain/AssignmentClient.ts @@ -12,6 +12,7 @@ import Node from "./networking/Node"; import NodeList from "./networking/NodeList"; import { NodeTypeValue } from "./networking/NodeType"; import ContextManager from "./shared/ContextManager"; +import Log from "./shared/Log"; /*@devdoc @@ -152,7 +153,7 @@ class AssignmentClient { if (typeof callback === "function" || callback === null) { this.#_onStateChanged = callback; } else { - console.error("ERROR: AssignmentClient.onStateChanged callback not a function or null!"); + Log.error("AssignmentClient.onStateChanged callback not a function or null!"); this.#_onStateChanged = null; } } diff --git a/src/domain/audio-client/AudioClient.ts b/src/domain/audio-client/AudioClient.ts index ec557443..d7ccb4cd 100644 --- a/src/domain/audio-client/AudioClient.ts +++ b/src/domain/audio-client/AudioClient.ts @@ -22,6 +22,7 @@ import PacketScribe from "../networking/packets/PacketScribe"; import PacketType, { PacketTypeValue } from "../networking/udt/PacketHeaders"; import assert from "../shared/assert"; import ContextManager from "../shared/ContextManager"; +import Log, { LogLevel } from "../shared/Log"; /*@devdoc @@ -185,10 +186,10 @@ class AudioClient { ? inputDeviceAudioSettings.echoCancellation : false; } - console.log("[audioclient] Input device info:", inputDeviceAudioSettings + Log.message("Input device info:" + (inputDeviceAudioSettings ? `${deviceName}, ${channelCount} channels, ${sampleRate}Hz, ${sampleSize} bits, ` + `echo cancellation ${echoCancellation ? "on" : "off"}` - : null); + : ""), "audioclient"); let supportedFormat = false; @@ -210,7 +211,7 @@ class AudioClient { // WEBRTC TODO: Address further C++. if (isShutdownRequest) { - console.log("[audioclient] The audio input device has shut down."); + Log.message("The audio input device has shut down.", "audioclient"); return true; } @@ -231,12 +232,12 @@ class AudioClient { this.#_audioInput.readyRead.connect(this.#handleMicAudioInput); supportedFormat = true; } else { - console.error("[audioclient] Error starting audio input -", this.#_audioInput.errorString()); + Log.error(`Error starting audio input - ${this.#_audioInput.errorString()}`, "audioclient"); } } if (!supportedFormat) { - console.log("[audioclient] Audio input device is not available, using dummy input."); + Log.message("Audio input device is not available, using dummy input.", "audioclient"); // WEBRTC TODO: Address further C++. @@ -369,7 +370,8 @@ class AudioClient { this.#_selectedCodecName = selectedCodecName; - console.log("[audioclient] Selected codec:", this.#_selectedCodecName, "; Is stereo input:", this.#_isStereoInput); + Log.message(`Selected codec: ${this.#_selectedCodecName} ; Is stereo input: ${this.#_isStereoInput.toString()}`, + "audioclient"); // WEBRTC TODO: Address further C++ code. @@ -406,7 +408,7 @@ class AudioClient { #processStreamStatsPacket = (message: ReceivedMessage, sendingNode: Node | null): void => { // eslint-disable-line // C++ void AudioIOStats::processStreamStatsPacket(ReceivedMessage*, Node* sendingNode) - console.warn("AudioClient: AudioStreamStats packet not processed."); + Log.once(LogLevel.WARNING, "AudioClient: AudioStreamStats packet not processed.", "audioclient"); // WEBRTC TODO: Address further C++ code. @@ -418,7 +420,7 @@ class AudioClient { #handleAudioEnvironmentDataPacket = (message: ReceivedMessage): void => { // eslint-disable-line // C++ void handleAudioEnvironmentDataPacket(ReceivedMessage* message) - console.warn("AudioClient: AudioEnvironment packet not processed."); + Log.once(LogLevel.WARNING, "AudioClient: AudioEnvironment packet not processed.", "audioclient"); // WEBRTC TODO: Address further C++ code. diff --git a/src/domain/audio/AudioInput.ts b/src/domain/audio/AudioInput.ts index a4570a64..3a795b68 100644 --- a/src/domain/audio/AudioInput.ts +++ b/src/domain/audio/AudioInput.ts @@ -10,6 +10,7 @@ import AudioConstants from "../audio/AudioConstants"; import assert from "../shared/assert"; +import Log from "../shared/Log"; import SignalEmitter, { Signal } from "../shared/SignalEmitter"; // eslint-disable-next-line @@ -50,7 +51,7 @@ class AudioInput { set audioInput(audioInput: MediaStream | null) { // C++ N/A if (audioInput && this.#_isStarted) { - console.error("Cannot set the audio input while it is running!"); + Log.error("Cannot set the audio input while it is running!", "audio"); return; } @@ -200,7 +201,7 @@ class AudioInput { } if (frame === undefined) { this.#_errorString = "Unexpected read of empty audio input buffer!"; - console.error(this.#_errorString); + Log.error(this.#_errorString, "audio"); return null; } return frame; @@ -268,7 +269,7 @@ class AudioInput { // Audio worklet. if (!this.#_audioContext.audioWorklet) { this.#_errorString = "Cannot set up audio input stream. App may not be being served via HTTPS or from localhost."; - console.error(this.#_errorString); + Log.error(this.#_errorString, "audio"); return false; } await this.#_audioContext.audioWorklet.addModule(audioInputProcessorURL); diff --git a/src/domain/audio/AudioOutput.ts b/src/domain/audio/AudioOutput.ts index 082674a8..bf1eeead 100644 --- a/src/domain/audio/AudioOutput.ts +++ b/src/domain/audio/AudioOutput.ts @@ -10,6 +10,7 @@ import assert from "../shared/assert"; import AudioConstants from "../audio/AudioConstants"; +import Log from "../shared/Log"; // eslint-disable-next-line // @ts-ignore @@ -159,7 +160,7 @@ class AudioOutput { // Audio worklet. if (!this.#_audioContext.audioWorklet) { - console.error("Cannot set up audio output stream. App may not be being served via HTTPS or from localhost."); + Log.error("Cannot set up audio output stream. App may not be being served via HTTPS or from localhost.", "audio"); return; } await this.#_audioContext.audioWorklet.addModule(audioOutputProcessorURL); diff --git a/src/domain/audio/InboundAudioStream.ts b/src/domain/audio/InboundAudioStream.ts index 51f9e20b..b5a64c2a 100644 --- a/src/domain/audio/InboundAudioStream.ts +++ b/src/domain/audio/InboundAudioStream.ts @@ -16,6 +16,7 @@ import { SilentAudioFrameDetails } from "../networking/packets/SilentAudioFrame" import PacketType from "../networking/udt/PacketHeaders"; import UDT from "../networking/udt/UDT"; import ContextManager from "../shared/ContextManager"; +import Log, { LogLevel } from "../shared/Log"; /*@devdoc @@ -86,7 +87,7 @@ class InboundAudioStream { } else { // WEBRTC TODO: Address further C++ code. - console.warn("Codec mismatch not handled."); + Log.warning("Codec mismatch not handled.", "audio"); } } @@ -124,7 +125,8 @@ class InboundAudioStream { // C++ int writeDroppableSilentFrames(int silentFrames) // WEBRTC TODO: Address further C++ code. - console.warn("InboundAudioStream.#writeDroppableSilentFrames() not implemented. Frames:", silentFrames); + Log.once(LogLevel.WARNING, `InboundAudioStream.#writeDroppableSilentFrames() not implemented. Frames: ${silentFrames}`, + "audio"); } @@ -136,7 +138,7 @@ class InboundAudioStream { if (this.#_decoder) { // WEBRTC TODO: Address further C++ code. - console.warn("Codec support not implemented.", this.#_selectedCodecName); + Log.once(LogLevel.WARNING, `Codec support not implemented. ${this.#_selectedCodecName}`, "audio"); decodedBuffer = new Int16Array(); } else { diff --git a/src/domain/networking/DomainHandler.ts b/src/domain/networking/DomainHandler.ts index 1a8d0e85..14fe14f3 100644 --- a/src/domain/networking/DomainHandler.ts +++ b/src/domain/networking/DomainHandler.ts @@ -15,6 +15,7 @@ import SignalEmitter, { Signal } from "../shared/SignalEmitter"; import Uuid from "../shared/Uuid"; import PacketScribe from "./packets/PacketScribe"; import ReceivedMessage from "./ReceivedMessage"; +import Log from "../shared/Log"; /*@devdoc @@ -253,8 +254,8 @@ class DomainHandler { // WEBRTC TODO: Should C++ clear _domainConnectionRefusals also? this.#_domainConnectionRefusals.clear(); // Re-report any refusals if retry connecting to the same domain. - console.log("[networking] Disconnecting from domain server."); - console.log("[networking] REASON:", reason); + Log.message("Disconnecting from domain server.", "networking"); + Log.message(`REASON: ${reason}`, "networking"); this.setIsConnected(false, forceDisconnect); } @@ -279,7 +280,7 @@ class DomainHandler { */ softReset(reason: string): void { // C++ void softReset(QString reason) { - console.log("[networking] Resetting current domain connection information."); + Log.message("Resetting current domain connection information.", "networking"); this.disconnect(reason); // WEBRTC TODO: Address further C++ code. @@ -298,8 +299,8 @@ class DomainHandler { const info = PacketScribe.DomainConnectionDenied.read(message.getMessage()); const sanitizedExtraInfo = info.extraInfo.toLowerCase().startsWith("http") ? "" : info.extraInfo; - console.warn("[networking] The domain-server denied a connection request: ", info.reasonMessage, "extraInfo:", - sanitizedExtraInfo); + Log.warning(`The domain-server denied a connection request: ${info.reasonMessage} extraInfo: ${sanitizedExtraInfo}`, + "networking"); if (!this.#_domainConnectionRefusals.has(info.reasonMessage)) { this.#_domainConnectionRefusals.add(info.reasonMessage); diff --git a/src/domain/networking/HMACAuth.ts b/src/domain/networking/HMACAuth.ts index 4baf3c60..63e4b341 100644 --- a/src/domain/networking/HMACAuth.ts +++ b/src/domain/networking/HMACAuth.ts @@ -11,6 +11,7 @@ import Uuid from "../shared/Uuid"; import UDT from "./udt/UDT"; import "../shared/DataViewExtensions"; +import Log from "../shared/Log"; import CryptoJS from "crypto-js"; @@ -85,7 +86,7 @@ class HMACAuth { // Vircadia only uses MD5. if (authMethod !== HMACAuth.MD5) { - console.error("HMACAuth method not supported:", authMethod); + Log.error(`HMACAuth method not supported: ${authMethod}`); } this.#_keyWordArray = CryptoJS.lib.WordArray.create([]); diff --git a/src/domain/networking/LimitedNodeList.ts b/src/domain/networking/LimitedNodeList.ts index 506533ec..9edc23e7 100644 --- a/src/domain/networking/LimitedNodeList.ts +++ b/src/domain/networking/LimitedNodeList.ts @@ -24,6 +24,7 @@ import Socket from "./udt/Socket"; import assert from "../shared/assert"; import SignalEmitter, { Signal } from "../shared/SignalEmitter"; import Uuid from "../shared/Uuid"; +import Log from "../shared/Log"; type NewNodeInfo = { @@ -235,7 +236,7 @@ class LimitedNodeList { if (packet.isReliable()) { - console.warn("sendPacket() : isReliable : Not implemented!"); + Log.warning("sendPacket() : isReliable : Not implemented!"); // WEBRTC TODO: Address further C++ code. @@ -257,8 +258,8 @@ class LimitedNodeList { return this.sendPacket(packet, activeSocket, destinationNode.getAuthenticateHash()); } - console.log("[networking] LimitedNodeList.sendPacket called without active socket for node", - NodeType.getNodeTypeName(destinationNode.getType()), "- not sending"); + Log.message(`LimitedNodeList.sendPacket called without active socket for node ` + + `${NodeType.getNodeTypeName(destinationNode.getType())} - not sending`, "networking"); return LimitedNodeList.#ERROR_SENDING_PACKET_BYTES; } @@ -267,8 +268,8 @@ class LimitedNodeList { const overridenSockAddr = param2; if (overridenSockAddr.isNull() && !destinationNode.getActiveSocket()) { - console.log("[networking] LimitedNodeList.sendPacket called without active socket for node", - destinationNode.getUUID(), ". Not sending."); + Log.message(`LimitedNodeList.sendPacket called without active socket for node ` + + `${destinationNode.getUUID().stringify()} . Not sending.`, "networking"); return LimitedNodeList.#ERROR_SENDING_PACKET_BYTES; } @@ -281,7 +282,8 @@ class LimitedNodeList { return this.sendPacket(packet, destinationSockAddr, destinationNode.getAuthenticateHash()); } - console.error("Invalid parameters in LimiteNodeList.sendPacket()!", typeof packet, typeof param1, typeof param2); + Log.error(`Invalid parameters in LimiteNodeList.sendPacket()! ` + + `${typeof packet} ${typeof param1} ${typeof param2}`, "networking"); return LimitedNodeList.#ERROR_SENDING_PACKET_BYTES; } @@ -389,7 +391,7 @@ class LimitedNodeList { // WEBRTC TODO: Address further C++ code. - console.log("[networking] Added", NodeType.getNodeTypeName(newNode.getType())); + Log.message(`Added ${NodeType.getNodeTypeName(newNode.getType())}`, "networking"); // WEBRTC TODO: Address further C++ code. @@ -500,7 +502,7 @@ class LimitedNodeList { this.#_sessionUUID = sessionUUID; if (sessionUUID.value() !== oldUUID.value()) { - console.log("[networking] NodeList UUID changed from", oldUUID.stringify(), "to", sessionUUID.stringify()); + Log.message(`[networking] NodeList UUID changed from ${oldUUID.stringify()} to ${sessionUUID.stringify()}`); this.#_uuidChanged.emit(sessionUUID, oldUUID); } } @@ -568,8 +570,8 @@ class LimitedNodeList { return this._nodeSocket.writePacketList(packetList, activeSocket); } - console.log(`[networking] LimitedNodeList.sendPacketList called without active socket for node - ${destinationNode.getUUID().stringify()}. Not sending.`); + Log.message(`LimitedNodeList.sendPacketList called without active socket for node + ${destinationNode.getUUID().stringify()}. Not sending.`, "networking"); return LimitedNodeList.#ERROR_SENDING_PACKET_BYTES; } @@ -708,8 +710,8 @@ class LimitedNodeList { // WEBRTC TODO: Address further C++ code. - console.log("[networking] Killed", NodeType.getNodeTypeName(node.getType()), node.getUUID().stringify(), - node.getPublicSocket().toString(), "/", node.getLocalSocket().toString()); + Log.message(`Killed ${NodeType.getNodeTypeName(node.getType())} ${node.getUUID().stringify()} ` + + `${node.getPublicSocket().toString()} / ${node.getLocalSocket().toString()}`, "networking"); // Ping timer N/A. @@ -771,7 +773,7 @@ class LimitedNodeList { const killedNodes = []; if (this.#_nodeHash.size > 0) { - console.log("[networking] Removing all nodes from nodes list:", reason); + Log.message(`Removing all nodes from nodes list: ${reason}`, "networking"); for (const node of this.#_nodeHash.values()) { killedNodes.push(node); } @@ -828,8 +830,8 @@ class LimitedNodeList { } if (!hasBeenOutput) { - console.log("[networking] Packet version mismatch on", headerType, "- Sender", senderString, "sent", - headerVersion, "but", PacketType.versionForPacketType(headerType), "expected."); + Log.message(`Packet version mismatch on ${headerType} - Sender ${senderString}, sent ` + + `${headerVersion} but ${PacketType.versionForPacketType(headerType)} expected.`, "networking"); this.#_packetVersionMismatch.emit(headerType, senderSockAddr, sourceID); } @@ -868,8 +870,8 @@ class LimitedNodeList { if (sendingNodeType !== NodeType.Unassigned) { return true; } - console.log("[networking] Replicated packet of type", headerType, "received from unknown upstream", - senderSockAddr?.toString()); + Log.message(`Replicated packet of type ${headerType} received from unknown upstream ` + + (senderSockAddr ? senderSockAddr.toString() : ""), "networking"); return false; } @@ -906,8 +908,8 @@ class LimitedNodeList { } if (!this.#isDelayedNode(sourceID)) { - console.log("[networking] Packet of type", headerType, "received from unknown node with Local ID", - sourceLocalID); + Log.message(` Packet of type ${headerType} received from unknown node with Local ID ` + + sourceLocalID.toString(), "networking"); } return false; diff --git a/src/domain/networking/NLPacket.ts b/src/domain/networking/NLPacket.ts index 691fd852..aee4d734 100644 --- a/src/domain/networking/NLPacket.ts +++ b/src/domain/networking/NLPacket.ts @@ -15,6 +15,7 @@ import UDT from "./udt/UDT"; import { LocalID } from "../networking/NetworkPeer"; import Node from "../networking/Node"; import assert from "../shared/assert"; +import Log from "../shared/Log"; /*@devdoc @@ -181,8 +182,8 @@ class NLPacket extends Packet { this.adjustPayloadStartAndCapacity(NLPacket.#localHeaderSize(this._messageData.type)); } else { - console.error("Invalid parameters in Packet constructor!", typeof param0, typeof param1, typeof param2, - typeof param3, typeof param4); + Log.error(`Invalid parameters in Packet constructor! ` + + `${typeof param0} ${typeof param1} ${typeof param2} ${typeof param3} ${typeof param4}`, "networking"); super(0, false, false); } } diff --git a/src/domain/networking/NLPacketList.ts b/src/domain/networking/NLPacketList.ts index 5fb76bf1..e574c6b9 100644 --- a/src/domain/networking/NLPacketList.ts +++ b/src/domain/networking/NLPacketList.ts @@ -14,6 +14,7 @@ import { PacketTypeValue } from "./udt/PacketHeaders"; import UDT from "./udt/UDT"; import assert from "../shared/assert"; import Uuid from "../shared/Uuid"; +import Log from "../shared/Log"; /*@devdoc @@ -96,7 +97,7 @@ class NLPacketList { assert(!(!isReliable && isOrdered), "PacketList: Unreliable ordered PacketLists are not supported."); if (this.#_extendedHeader !== null) { - console.error("PacketList extended header not implemented."); + Log.error("PacketList extended header not implemented."); } } @@ -138,10 +139,10 @@ class NLPacketList { this.#_uuidData.setBigUint128(0, value.value(), UDT.BIG_ENDIAN); return this.#writeData(this.#_uuidArray, this.#UUID_LENGTH); } - console.error("NLPacketList.writePrimitive() - Unhandled type:", typeof value); + Log.error(`NLPacketList.writePrimitive() - Unhandled type: ${typeof value}`); return 0; default: - console.error("NLPacketList.writePrimitive() - Unhandled type:", typeof value); + Log.error(`NLPacketList.writePrimitive() - Unhandled type: ${typeof value}`); return 0; } } @@ -273,8 +274,8 @@ class NLPacketList { messageData.buffer.set(new Uint8Array(this.#_extendedHeader), messageData.dataPosition); messageData.dataPosition += this.#_extendedHeader.byteLength; } else { - console.log("[networking] Could not write extendedHeader in NLPacketList.createPacketWithExtendedHeader", - "- make sure the extendedHeader is not larger than the payload capacity."); + Log.message("Could not write extendedHeader in NLPacketList.createPacketWithExtendedHeader" + + "- make sure the extendedHeader is not larger than the payload capacity.", "networking"); } } @@ -320,8 +321,9 @@ class NLPacketList { if (segmentSize + sizeRemaining > messageData.buffer.byteLength - messageData.dataPosition) { // This is an unsupported case - the segment is bigger than the size of an individual packet // but the PacketList is not going to be sent ordered. - console.error("[networking] Error in PacketList.writeData()", - "Attempted to write a segment to an unordered packet that is larger than the payload size."); + Log.error("Error in PacketList.writeData() " + + "Attempted to write a segment to an unordered packet that is larger than the payload size.", + "networking"); // We won't be writing this new data to the packet. // Go back before the current segment and return -1 to indicate error. @@ -342,8 +344,9 @@ class NLPacketList { if (sizeRemaining > newMessageData.buffer.byteLength - newMessageData.dataPosition) { // This is an unsupported case - attempting to write a block of data larger than the capacity of a new // packet in an unordered PacketList. - console.error("[networking] Error in PacketList.writeData()", - "Attempted to write data to an unordered packet that is larger than the payload size."); + Log.error("Error in PacketList.writeData() " + + "Attempted to write data to an unordered packet that is larger than the payload size.", + "networking"); return NLPacketList.#PACKET_LIST_WRITE_ERROR; } diff --git a/src/domain/networking/NetworkPeer.ts b/src/domain/networking/NetworkPeer.ts index abc69cea..7cf72719 100644 --- a/src/domain/networking/NetworkPeer.ts +++ b/src/domain/networking/NetworkPeer.ts @@ -12,6 +12,7 @@ import SockAddr from "./SockAddr"; import SignalEmitter, { Signal } from "../shared/SignalEmitter"; import Uuid from "../shared/Uuid"; +import Log from "../shared/Log"; type LocalID = number; @@ -124,8 +125,8 @@ class NetworkPeer { this._publicSocket.setObjectName(previousSocket.objectName()); if (!wasOldSocketNull) { - console.log("[networking] Public socket change for node", this.#toString(), - "; previously", previousSocket.toString()); + Log.message(`Public socket change for node", ${this.#toString()}; ` + + `previously ${previousSocket.toString()}`, "networking"); this.#_socketUpdated.emit(previousSocket, this._publicSocket); } } @@ -160,8 +161,8 @@ class NetworkPeer { this._localSocket.setObjectName(previousSocket.objectName()); if (!wasOldSocketNull) { - console.log("[networking] Local socket change for node", this.#toString(), - "; previously", previousSocket.toString()); + Log.message(`Local socket change for node ${this.#toString()}; ` + + `previously ${previousSocket.toString()}`, "networking"); this.#_socketUpdated.emit(previousSocket, this._localSocket); } } @@ -173,7 +174,7 @@ class NetworkPeer { activateLocalSocket(): void { // C++ void activateLocalSocket() if (this.#_activeSocket !== this._localSocket) { - console.log("[networking] Activating local socket for network peer with ID", this.#_uuid.stringify()); + Log.message(`Activating local socket for network peer with ID ${this.#_uuid.stringify()}`, "networking"); this.#setActiveSocket(this._localSocket); } } @@ -184,7 +185,7 @@ class NetworkPeer { activatePublicSocket(): void { // C++ void activatePublicSocket() if (this.#_activeSocket !== this._publicSocket) { - console.log("[networking] Activating public socket for network peer with ID", this.#_uuid.stringify()); + Log.message(`Activating public socket for network peer with ID ${this.#_uuid.stringify()}`, "networking"); this.#setActiveSocket(this._publicSocket); } } diff --git a/src/domain/networking/NodeList.ts b/src/domain/networking/NodeList.ts index c6a1dff8..9fb8e6fb 100644 --- a/src/domain/networking/NodeList.ts +++ b/src/domain/networking/NodeList.ts @@ -24,6 +24,7 @@ import Socket from "./udt/Socket"; import assert from "../shared/assert"; import ContextManager from "../shared/ContextManager"; import Uuid from "../shared/Uuid"; +import Log from "../shared/Log"; /*@devdoc @@ -217,7 +218,7 @@ class NodeList extends LimitedNodeList { // C++ void processDomainServerRemovedNode(ReceivedMessage* message) const info = PacketScribe.DomainServerRemovedNode.read(message.getMessage()); const nodeUUID = info.nodeUUID; - console.log("[networking] Received packet from domain-server to remove node with UUID", nodeUUID.stringify()); + Log.message(`Received packet from domain-server to remove node with UUID ${nodeUUID.stringify()}`, "networking"); this.killNodeWithUUID(nodeUUID); // WEBRTC TODO: Address further C++ code. @@ -336,7 +337,7 @@ class NodeList extends LimitedNodeList { // Open a WebRTC data channel to the domain server if not already open. const domainServerSocketState = this._nodeSocket.getSocketState(domainURL, NodeType.DomainServer); if (domainServerSocketState !== Socket.CONNECTED) { - console.log("[networking] Opening domain server connection. Will not send domain server check-in."); + Log.message("Opening domain server connection. Will not send domain server check-in.", "networking"); if (domainServerSocketState === Socket.UNCONNECTED) { this._nodeSocket.openSocket(domainURL, NodeType.DomainServer, (socketID: number) => { this.#_domainHandler.setPort(socketID); @@ -482,7 +483,7 @@ class NodeList extends LimitedNodeList { }); } else { - console.error("Unexpected socket state for", NodeType.getNodeTypeName(node.getType())); + Log.error(`Unexpected socket state for ${NodeType.getNodeTypeName(node.getType())}`, "networking"); } // Vircadia clients can never have upstream nodes or downstream nodes so we don't need to cater for these. diff --git a/src/domain/networking/PacketReceiver.ts b/src/domain/networking/PacketReceiver.ts index b365410b..600665f2 100644 --- a/src/domain/networking/PacketReceiver.ts +++ b/src/domain/networking/PacketReceiver.ts @@ -17,6 +17,7 @@ import Packet from "./udt/Packet"; import { PacketTypeValue } from "./udt/PacketHeaders"; import assert from "../shared/assert"; import ContextManager from "../shared/ContextManager"; +import Log from "../shared/Log"; type Listener = (message: ReceivedMessage, sendingNode: Node | null) => void; @@ -213,8 +214,8 @@ class PacketReceiver { listener.listener(receivedMessage, matchingNode); } else if (listener === undefined) { - console.error("PacketReceiver.handleVerifiedMessage() : Could not find listener for message type:", - receivedMessage.getType()); + Log.error(`PacketReceiver.handleVerifiedMessage() :` + + `Could not find listener for message type: ${receivedMessage.getType()}`); // Insert a dummy listener so we don't log this again. this.#_messageListenerMap.set(receivedMessage.getType(), null); diff --git a/src/domain/networking/udt/BasePacket.ts b/src/domain/networking/udt/BasePacket.ts index 58cc9a41..111f59fb 100644 --- a/src/domain/networking/udt/BasePacket.ts +++ b/src/domain/networking/udt/BasePacket.ts @@ -12,6 +12,7 @@ import UDT from "./UDT"; import MessageData from "../MessageData"; import SockAddr from "../SockAddr"; import assert from "../../shared/assert"; +import Log from "../../shared/Log"; /*@devdoc @@ -82,7 +83,7 @@ class BasePacket { this._messageData.packetSize = size; this._messageData.senderSockAddr = senderSockAddr; if (data.byteLength !== size) { - console.error("Invalid size parameter in BasePacket constructor!", size); + Log.error(`Invalid size parameter in BasePacket constructor! ${size}`); } } else if (param0 instanceof BasePacket) { @@ -95,7 +96,7 @@ class BasePacket { } else { // Invalid call. this._messageData = new MessageData(); - console.error("Invalid parameters in BasePacket constructor!", typeof param0, typeof param1, typeof param2); + Log.error(`Invalid parameters in BasePacket constructor! ${typeof param0} ${typeof param1} ${typeof param2}`); } } diff --git a/src/domain/networking/udt/Connection.ts b/src/domain/networking/udt/Connection.ts index cd6a4a85..cbf95a45 100644 --- a/src/domain/networking/udt/Connection.ts +++ b/src/domain/networking/udt/Connection.ts @@ -20,6 +20,7 @@ import NLPacketList from "../NLPacketList"; import SockAddr from "../SockAddr"; import assert from "../../shared/assert"; import SignalEmitter, { Signal } from "../../shared/SignalEmitter"; +import Log from "../../shared/Log"; /*@devdoc @@ -141,7 +142,7 @@ class Connection { // Refuse to process any packets until we've received the handshake. // Send handshake request to re-request a handshake. if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] Received packet before receiving handshake, sending HandshakeRequest."); + Log.message("Received packet before receiving handshake, sending HandshakeRequest.", "networking"); } this.#sendHandshakeRequest(); return false; @@ -203,14 +204,14 @@ class Connection { // We're already in a state where we've received a handshake ACK, so we are likely in a state where the // other end expired our connection. Let's reset. - console.log("[networking] Got HandshakeRequest from", this.#_destination.toString(), - "while active. Stopping SendQueue"); + Log.message(`Got HandshakeRequest from ${this.#_destination.toString()}` + + "while active. Stopping SendQueue", "networking"); this.#_hasReceivedHandshakeACK = false; this.#stopSendQueue(); } break; default: - console.error("[networking] Invalid control packet type!"); + Log.error("Invalid control packet type!", "networking"); } } @@ -265,7 +266,7 @@ class Connection { this.#stopSendQueue(); if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] Connection to", this.#_destination.toString(), "has stopped its SendQueue."); + Log.message(`Connection to ${this.#_destination.toString()} has stopped its SendQueue.`, "networking"); } }; @@ -333,7 +334,7 @@ class Connection { // Validate that this isn't a BS ACK. if (ack.isGreaterThan(this.#getSendQueue().getCurrentSequenceNumber())) { // In UDT they specifically break the connection here - do we want to do anything? - console.error("[networking] Connection.processACK()", "ACK received higher than largest sent sequence number."); + Log.error("Connection.processACK() ACK received higher than largest sent sequence number.", "networking"); return; } @@ -384,7 +385,7 @@ class Connection { if (Socket.UDT_CONNECTION_DEBUG) { if (initialSequenceNumber.isNotEqualTo(this.#_initialReceiveSequenceNumber)) { - console.log("[networking] Resetting receive state, received a new initial sequence number in handshake."); + Log.message("Resetting receive state, received a new initial sequence number in handshake.", "networking"); } } @@ -463,7 +464,7 @@ class Connection { } if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] Created SendQueue for connection to", this.#_destination.toString()); + Log.message(`Created SendQueue for connection to ${this.#_destination.toString()}`, "networking"); } // WEBRTC TODO: Address further C++ code. - Stats and congestion control. diff --git a/src/domain/networking/udt/ControlPacket.ts b/src/domain/networking/udt/ControlPacket.ts index 34cf7db5..0960e452 100644 --- a/src/domain/networking/udt/ControlPacket.ts +++ b/src/domain/networking/udt/ControlPacket.ts @@ -13,6 +13,7 @@ import SequenceNumber from "./SequenceNumber"; import UDT from "./UDT"; import SockAddr from "../../networking/SockAddr"; import assert from "../../shared/assert"; +import Log from "../../shared/Log"; enum ControlPacketType { ACK, @@ -177,7 +178,7 @@ class ControlPacket extends BasePacket { this.#_type = other.getType(); } else { - console.error("Invalid parameters in ControlPacket constructor!", typeof param0, typeof param1, typeof param2); + Log.error(`Invalid parameters in ControlPacket constructor! ${typeof param0} ${typeof param1} ${typeof param2}`); super(0); } } diff --git a/src/domain/networking/udt/Packet.ts b/src/domain/networking/udt/Packet.ts index eeb84379..3f162d41 100644 --- a/src/domain/networking/udt/Packet.ts +++ b/src/domain/networking/udt/Packet.ts @@ -13,6 +13,7 @@ import SequenceNumber from "./SequenceNumber"; import UDT from "./UDT"; import SockAddr from "../SockAddr"; import assert from "../../shared/assert"; +import Log from "../../shared/Log"; /*@devdoc @@ -168,7 +169,7 @@ class Packet extends BasePacket { this.#readHeader(); // adjustPayloadStartAndCapacity(); N/A if (this._messageData.obfuscationLevel !== Packet.ObfuscationLevel.NoObfuscation) { - console.warn("Packet() : Undo obfuscation : Not implemented!"); + Log.warning("Packet() : Undo obfuscation : Not implemented!"); // WEBRTC TODO: Address further C++ code. @@ -182,7 +183,7 @@ class Packet extends BasePacket { this._messageData.dataPosition = Packet.totalHeaderSize(this._messageData.isPartOfMessage); } else { - console.error("Invalid parameters in Packet constructor!", typeof param0, typeof param1, typeof param2); + Log.error(`Invalid parameters in Packet constructor! ${typeof param0} ${typeof param1} ${typeof param2}`); super(0); } } @@ -267,7 +268,7 @@ class Packet extends BasePacket { // WEBRTC TODO: Address further C++ code. - Obfuscate packet. - console.warn("Packet.obfuscate() not implemented. Level:", level); + Log.warning(`Packet.obfuscate() not implemented. Level: ${level}`); } diff --git a/src/domain/networking/udt/PacketHeaders.ts b/src/domain/networking/udt/PacketHeaders.ts index 5e27cae3..8497a861 100644 --- a/src/domain/networking/udt/PacketHeaders.ts +++ b/src/domain/networking/udt/PacketHeaders.ts @@ -9,6 +9,7 @@ // import assert from "../../shared/assert"; +import Log from "../../shared/Log"; /*@devdoc @@ -583,7 +584,7 @@ const PacketType = new class { // C++ default for remainder of packets is 22 but we want to report packets we haven't implemented, so explicitly // list those packets we know about. default: - console.error("ERROR - Unknown packet type in versionForPacketType() :", packetType); + Log.error(`ERROR - Unknown packet type in versionForPacketType() : ${packetType}`); } return 0; } diff --git a/src/domain/networking/udt/PendingReceivedMessage.ts b/src/domain/networking/udt/PendingReceivedMessage.ts index 36a36fe1..226a5f9c 100644 --- a/src/domain/networking/udt/PendingReceivedMessage.ts +++ b/src/domain/networking/udt/PendingReceivedMessage.ts @@ -10,6 +10,7 @@ import Packet from "./Packet"; import assert from "../../shared/assert"; +import Log from "../../shared/Log"; /*@devdoc @@ -45,7 +46,7 @@ class PendingReceivedMessage { index += 1; if (index < this.#_packets.length && this.#_packets[index]!.getMessagePartNumber() === messagePartNumber) { - console.log("[networking] PendingReceivedMessage.enqueuePacket(): Duplicate packet."); + Log.message("PendingReceivedMessage.enqueuePacket(): Duplicate packet.", "networking"); return; } diff --git a/src/domain/networking/udt/SendQueue.ts b/src/domain/networking/udt/SendQueue.ts index 1bbdda27..7e763b98 100644 --- a/src/domain/networking/udt/SendQueue.ts +++ b/src/domain/networking/udt/SendQueue.ts @@ -20,6 +20,7 @@ import assert from "../../shared/assert"; import ConditionVariable from "../../shared/ConditionVariable"; import HighResolutionClock from "../../shared/HighResolutionClock"; import SignalEmitter, { Signal } from "../../shared/SignalEmitter"; +import Log from "../../shared/Log"; type PacketResendPair = { first: number, second: Packet }; // Number of resends for a packet. @@ -383,7 +384,7 @@ class SendQueue { debugString += `\n Message Number: ${messageData.messageNumber} `; debugString += `Part Number: ${messageData.messagePartNumber}.`; } - console.log(debugString); + Log.message(debugString); } // Create copy of the packet @@ -491,9 +492,9 @@ class SendQueue { if (!notified && (this.#_packets.isEmpty() || this.#isFlowWindowFull()) && this.#_naks.isEmpty()) { if (Socket.UDT_CONNECTION_DEBUG) { const S_TO_MS = 1000; - console.log("[networking] SendQueue to", this.#_destination.toString(), "has been empty for", - EMPTY_QUEUES_INACTIVE_TIMEOUT_MS / S_TO_MS, "seconds and receiver has ACKed all packets.", - "The queue is now inactive and will be stopped."); + Log.message(`SendQueue to ${this.#_destination.toString()} has been empty for ` + + `${EMPTY_QUEUES_INACTIVE_TIMEOUT_MS / S_TO_MS} seconds and receiver has ACKed all packets.` + + "The queue is now inactive and will be stopped.", "networking"); } // Deactivate the queue. @@ -582,14 +583,14 @@ class SendQueue { if (this.#_state === SendQueue.#State.Stopped) { // We've already been asked to stop before we even got a chance to start; don't start now. if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] SendQueue asked to run after being told to stop. Will not run."); + Log.message("SendQueue asked to run after being told to stop. Will not run.", "networking"); } return; } if (this.#_state === SendQueue.#State.Running) { // We're already running; don't start another run. if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] SendQueue asked to run but is already running. Will not re-run."); + Log.message("SendQueue asked to run but is already running. Will not re-run.", "networking"); } return; } @@ -658,10 +659,10 @@ class SendQueue { const MAX_SEND_QUEUE_SLEEP_USECS = 2000000n; if (timeToSleep > MAX_SEND_QUEUE_SLEEP_USECS) { - console.warn("SendQueue wanted to sleep for", timeToSleep, "microseconds"); - console.warn("Capping sleep to", MAX_SEND_QUEUE_SLEEP_USECS); - console.warn("PSP:", this.#_packetSendPeriod, "NPD:", nextPacketDelta, "NPT:", nextPacketTimestamp, - "NOW:", now); + Log.warning(`SendQueue wanted to sleep for ${timeToSleep} microseconds`); + Log.warning(`Capping sleep to ${MAX_SEND_QUEUE_SLEEP_USECS}`); + Log.warning(`PSP: ${this.#_packetSendPeriod} NPD: ${nextPacketDelta} NPT: ${nextPacketTimestamp} ` + + `NOW: ${now}`); // WEBRTC TODO: Address further C++ code. - UserActivityLogger. diff --git a/src/domain/networking/udt/Socket.ts b/src/domain/networking/udt/Socket.ts index bcf356ca..75104bdf 100644 --- a/src/domain/networking/udt/Socket.ts +++ b/src/domain/networking/udt/Socket.ts @@ -21,6 +21,7 @@ import { NodeTypeValue } from "../NodeType"; import SockAddr from "../SockAddr"; import { default as WebRTCSocket, WebRTCSocketDatagram } from "../webrtc/WebRTCSocket"; import assert from "../../shared/assert"; +import Log from "../../shared/Log"; type PacketHandlerCallback = (packet: Packet) => void; @@ -145,7 +146,7 @@ class Socket { // C++ void clearConnections() if (this.#_connectionsHash.size > 0) { - console.log("[networking] Clearing all remaining connections in Socket."); + Log.message("Clearing all remaining connections in Socket.", "networking"); this.#_connectionsHash.clear(); } @@ -166,7 +167,7 @@ class Socket { // eslint-disable-next-line @typescript-eslint/dot-notation const connectionErased = this.#_connectionsHash.delete(sockAddr.getPort()); if (connectionErased && Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] Socket.cleanupConnection called for connection to", sockAddr.toString()); + Log.message(`Socket.cleanupConnection called for connection to ${sockAddr.toString()}`, "networking"); } } @@ -326,7 +327,7 @@ class Socket { // C++ qint64 writePacketList(PacketList* packetList, const SockAddr& sockAddr) if (packetList.getNumPackets() === 0) { - console.warn("[networking] Trying to send packet list with 0 packets, bailing."); + Log.warning("Trying to send packet list with 0 packets, bailing.", "networking"); return 0; } @@ -354,8 +355,9 @@ class Socket { handleRemoteAddressChange = (previousAddress: SockAddr, currentAddress: SockAddr): void => { // C++ void handleRemoteAddressChange(SockAddr previousAddress, SockAddr currentAddress) - console.log("[networking] Remote address changes from", previousAddress.toString(), "to", currentAddress.toString()); - console.warn("handleRemoteAddressChange() : Not implemented!"); + Log.message(`Remote address changes from ${previousAddress.toString()} "to" ${currentAddress.toString()}`, + "networking"); + Log.warning("handleRemoteAddressChange() : Not implemented!", "networking"); // WEBRTC TODO: Address further C++ code. @@ -367,8 +369,8 @@ class Socket { connection.setDestinationAddress(currentAddress); this.#_connectionsHash.set(currentAddress.getPort(), connection); - console.log("[networking] Moved Connection class from", previousAddress.toString(), "to", - currentAddress.toString()); + Log.message(`Moved Connection class from ${previousAddress.toString()} to` + + currentAddress.toString(), "networking"); const sequenceNumber = this.#_unreliableSequenceNumbers.get(previousAddress.getPort()); if (sequenceNumber !== undefined) { @@ -444,8 +446,8 @@ class Socket { /* , packet.getDataSize(), packet.getPayloadSize() */)) { // The connection could not be created or indicated that we should not continue processing packet. if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] Can't process packet: type", NLPacket.typeInHeader(packet), - ", version", NLPacket.versionInHeader(packet)); + Log.message(`Can't process packet: type ${NLPacket.typeInHeader(packet)}` + + `, version ${NLPacket.versionInHeader(packet)}`, "networking"); } // eslint-disable-next-line no-continue @@ -503,7 +505,8 @@ class Socket { if (connection) { connection.sendReliablePacketList(packetList); } else if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] Socket.writeReliablePacketList refusing to send packet list - no connection was created"); + Log.message("Socket.writeReliablePacketList refusing to send packet list - no connection was created", + "networking"); } } @@ -518,8 +521,8 @@ class Socket { if (filterCreate && this.#_connectionCreationFilterOperator && !this.#_connectionCreationFilterOperator(sockAddr)) { // The connection creation filter did not allow us to create a new connection. if (Socket.UDT_CONNECTION_DEBUG) { - console.log("[networking] Socket.findOrCreateConnection refusing to create Connection class for", - sockAddr.toString(), "due to connection creation filter"); + Log.message("Socket.findOrCreateConnection refusing to create Connection class for " + + sockAddr.toString() + " due to connection creation filter", "networking"); } return null; } @@ -530,7 +533,7 @@ class Socket { // WEBRTC TODO: Address further C++ code. - console.log("[networking] Creating new Connection class for", sockAddr.toString()); + Log.message(`Creating new Connection class for ${sockAddr.toString()}`, "networking"); this.#_connectionsHash.set(sockAddr.getPort(), connection); } diff --git a/src/domain/networking/webrtc/WebRTCDataChannel.ts b/src/domain/networking/webrtc/WebRTCDataChannel.ts index a3602a43..e01aabe2 100644 --- a/src/domain/networking/webrtc/WebRTCDataChannel.ts +++ b/src/domain/networking/webrtc/WebRTCDataChannel.ts @@ -10,6 +10,7 @@ import NodeType, { NodeTypeValue } from "../NodeType"; import WebRTCSignalingChannel, { SignalingMessage } from "./WebRTCSignalingChannel"; +import Log from "../../shared/Log"; import assert from "../../shared/assert"; @@ -192,7 +193,7 @@ class WebRTCDataChannel { // eslint-disable-next-line addEventListener(event: string, callback: OnOpenCallback | OnMessageCallback | OnCloseCallback | OnErrorCallback): void { const errorMessage = "WebRTCDataChannel.addEventListener(): Not implemented!"; - console.error(errorMessage); + Log.error(errorMessage); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } @@ -213,7 +214,7 @@ class WebRTCDataChannel { } const errorMessage = "WebRTCDataChannel: Data channel not open for sending!"; - console.error(errorMessage); + Log.error(errorMessage); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } @@ -249,12 +250,12 @@ class WebRTCDataChannel { // Send ICE candidates to the domain server. this.#_peerConnection.onicecandidate = ({ candidate }) => { if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Obtained ICE candidate.`); + Log.debug(` [${this.#_nodeTypeName}] Obtained ICE candidate.`, "webrtc"); } if (candidate // The candidate is sometimes null; don't send this but do send empty string. && this.#_signalingChannel && this.#_signalingChannel.readyState === WebRTCSignalingChannel.OPEN) { if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Send ICE candidate.`); + Log.debug(` [${this.#_nodeTypeName}] Send ICE candidate.`, "webrtc"); } this.#_signalingChannel.send({ to: this.#_nodeType, data: { candidate } }); } @@ -263,7 +264,7 @@ class WebRTCDataChannel { // Observe connection state changes. this.#_peerConnection.onconnectionstatechange = () => { if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Connection state changed:`, + Log.debug(`[webrtc] [${this.#_nodeTypeName}] Connection state changed:`, this.#_peerConnection?.connectionState); } let errorMessage = ""; @@ -291,7 +292,7 @@ class WebRTCDataChannel { // Unexpected condition. errorMessage = "WebRTCDataChannel: Unexpected connection state: " + (this.#_peerConnection ? this.#_peerConnection.connectionState : "undefined"); - console.error(errorMessage); + Log.error(errorMessage); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } @@ -334,7 +335,7 @@ class WebRTCDataChannel { // Create offer. if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Create offer.`); + Log.debug(` [${this.#_nodeTypeName}] Create offer.`, "webrtc"); } const rtcOfferOptions = { offerToReceiveAudio: false, @@ -347,7 +348,7 @@ class WebRTCDataChannel { // Send offer to domain server. if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Send offer.`); + Log.debug(` [${this.#_nodeTypeName}] Send offer.`, "webrtc"); } this.#_signalingChannel.send({ to: this.#_nodeType, @@ -363,7 +364,7 @@ class WebRTCDataChannel { if (!this.#_signalingChannel || this.#_signalingChannel.readyState !== WebRTCSignalingChannel.OPEN) { this.#_readyState = WebRTCDataChannel.CLOSED; const errorMessage = "WebRTCDataChannel: Signaling channel not open!"; - console.error(errorMessage); + Log.error(errorMessage); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } @@ -384,12 +385,12 @@ class WebRTCDataChannel { try { if (description) { if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Received description.`); + Log.debug(` [${this.#_nodeTypeName}] Received description.`, "webrtc"); } if (!this.#_peerConnection) { const errorMessage = "WebRTCDataChannel: Peer connection is closed!"; - console.error(errorMessage); + Log.error(errorMessage); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } @@ -398,7 +399,7 @@ class WebRTCDataChannel { // We got an answer. if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Description is ${description.type}.`); + Log.debug(` [${this.#_nodeTypeName}] Description is ${description.type}.`, "webrtc"); } if (description.type === "answer" && this.#_signalingChannel) { assert(this.#_offer !== null); @@ -410,7 +411,7 @@ class WebRTCDataChannel { this.#_haveSetRemoteDescription = true; } else { const errorMessage = `WebRTCDataChannel: Unexpected answer! ${description.type}`; - console.error(errorMessage); + Log.error(errorMessage, "webrtc"); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } @@ -421,12 +422,12 @@ class WebRTCDataChannel { // candidate from the server may arrive before the remote description has been set because of the delay // introduced by setting the local description just before setting the remote description. if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Received ICE candidate.`); + Log.debug(` [${this.#_nodeTypeName}] Received ICE candidate.`, "webrtc"); } if (this.#_peerConnection && this.#_haveSetRemoteDescription) { await this.#_peerConnection.addIceCandidate(candidate); } else if (this.#_DEBUG) { - console.debug(`[webrtc] [${this.#_nodeTypeName}] Skipped adding ICE candidate.`); + Log.debug(`[] [${this.#_nodeTypeName}] Skipped adding ICE candidate.`, "webrtc"); } } else if (echo) { // Ignore signaling channel "echo" messages. @@ -434,14 +435,14 @@ class WebRTCDataChannel { } else { // Unexpected message. const errorMessage = "WebRTCDataChannel: Unexpected signaling channel message!"; - console.error(errorMessage); + Log.error(errorMessage, "webrtc"); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } } } catch (err) { const errorMessage = "WebRTCDataChannel: Error processing signaling channel message!"; - console.error(errorMessage); + Log.error(errorMessage, "webrtc"); if (this.#_onerrorCallback) { this.#_onerrorCallback(errorMessage); } diff --git a/src/domain/networking/webrtc/WebRTCSignalingChannel.ts b/src/domain/networking/webrtc/WebRTCSignalingChannel.ts index 1f486cbb..cc316770 100644 --- a/src/domain/networking/webrtc/WebRTCSignalingChannel.ts +++ b/src/domain/networking/webrtc/WebRTCSignalingChannel.ts @@ -9,6 +9,7 @@ // import { NodeTypeValue } from "../NodeType"; +import Log from "../../shared/Log"; type EventCallback = (event: Event) => void; @@ -124,7 +125,7 @@ class WebRTCSignalingChannel { constructor(websocketURL: string) { if (typeof websocketURL !== "string" || websocketURL === "") { - console.error("WebRTCSignalingChannel: Invalid WebSocket URL!"); + Log.error("WebRTCSignalingChannel: Invalid WebSocket URL!"); } try { this.#_websocket = new WebSocket(websocketURL); @@ -211,7 +212,7 @@ class WebRTCSignalingChannel { return true; } - console.error("WebRTCSignalingChannel: Channel not open for sending!"); + Log.error("WebRTCSignalingChannel: Channel not open for sending!"); if (this.#_websocket && this.#_websocket.onerror) { this.#_websocket.onerror("Channel not open for sending!"); } @@ -243,7 +244,7 @@ class WebRTCSignalingChannel { } } if (!success) { - console.error("WebRTCSignalingChannel: Invalid message received!"); + Log.error("WebRTCSignalingChannel: Invalid message received!"); } } diff --git a/src/domain/shared/Log.ts b/src/domain/shared/Log.ts new file mode 100644 index 00000000..242d396a --- /dev/null +++ b/src/domain/shared/Log.ts @@ -0,0 +1,267 @@ +// +// Log.ts +// +// Created by Nshan G. on 29 Oct 2021. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +export enum LogLevel { + DEBUG, + DEFAULT, + INFO, + WARNING, + ERROR +} + +export const allLogLevels = [ + LogLevel.DEBUG, + LogLevel.DEFAULT, + LogLevel.INFO, + LogLevel.WARNING, + LogLevel.ERROR +] as const; + +type LogFunction = (message: string, messageType?: string) => void; + +/*@devdoc + * The LoggerContext is an interface for configuration of the output of the Logger class. + * @interface LoggerContext + * + */ +export interface LoggerContext { + getFunction(level: LogLevel): LogFunction | undefined; +} + + +/*@devdoc + * The StringLoggerContext is a logger configuration for logging to the dev console. + * + * @class Logger + */ +export class ConsoleLoggerContext implements LoggerContext { + + static #_typeFirst(func: (fisrs: string, second?: string) => void): LogFunction { + return (message: string, messageType?: string) => { + if (messageType) { + return func(`[${messageType}]`, message); + } + return func(message); + }; + } + + // it is necessary to capture the console object here, for things like jest's mocks to work, + // hence the no-op looking lambda wrappers + static #_functions = new Map([ + /* eslint-disable @typescript-eslint/no-explicit-any */ + [ + LogLevel.DEFAULT, ConsoleLoggerContext.#_typeFirst((...params: any[]) => { + return console.log(...params); + }) + ], + [ + LogLevel.DEBUG, ConsoleLoggerContext.#_typeFirst((...params: any[]) => { + return console.debug(...params); + }) + ], + [ + LogLevel.INFO, ConsoleLoggerContext.#_typeFirst((...params: any[]) => { + return console.info(...params); + }) + ], + [ + LogLevel.WARNING, ConsoleLoggerContext.#_typeFirst((...params: any[]) => { + return console.warn(...params); + }) + ], + [ + LogLevel.ERROR, ConsoleLoggerContext.#_typeFirst((...params: any[]) => { + return console.error(...params); + }) + ] + /* eslint-enable @typescript-eslint/no-explicit-any */ + ]); + + /* eslint-disable class-methods-use-this */ + getFunction(level: LogLevel): LogFunction | undefined { + return ConsoleLoggerContext.#_functions.get(level); + } + /* eslint-enable class-methods-use-this */ +} + +/*@devdoc + * The StringLoggerContext is a configuration for logging to a string. + * + * @class Logger + */ +export class StringLoggerContext implements LoggerContext { + buffer = ""; + #_functions = new Map(); + + constructor() { + for (const level of allLogLevels) { + this.#_functions.set(level, (message: string, messageType?: string) => { + if (messageType) { + this.buffer += `[${messageType}]`; + } + this.buffer += `[${LogLevel[level] as string}] ${message}\n`; + }); + } + } + + getFunction(level: LogLevel): LogFunction | undefined { + return this.#_functions.get(level); + } + +} + +/*@devdoc + * The Logger class serves as a convenience utility and a centralized configuration point for logging. + * + * @class Logger + */ +export class Logger { + #_context: LoggerContext; + #_activeFunctions = new Map(); + #_typeFilter?: Array; + + #_messageIds = new Map(); + #_typedMessageIds = new Map(); + #_messageFlags = new Map(); + + /*@devdoc + * Creates a logger instance with a specified context. + * @param {context} LoggerContext - The context this instance will use for output, + * which can also store any addition state, like an output buffer. + */ + constructor(context: LoggerContext) { + this.#_context = context; + this.filterLevels(() => { + return true; + }); + } + + /*@devdoc + * Logs a message at an appropriate level, with optional type. + * @param {level} LogLevel - the level to log at + * @param {message} string - the message to log + * @param {messageType} string - optional user defined message type + */ + level(level: LogLevel, message: string, messageType?: string): void { + const log = this.#_activeFunctions.get(level); + if (log && this.#_isTypeAllowed(messageType)) { + log(message, messageType); + } + } + + /*@devdoc + * Logs a message at an appropriate level, with optional type, only once. + * @param {level} LogLevel - the level to log at + * @param {message} string - the message to log + * @param {messageType} string - optional user defined message type + */ + once(level: LogLevel, message: string, messageType?: string): void { + const idMap = messageType ? this.#_typedMessageIds : this.#_messageIds; + + const key = `${messageType || "undefined"} | ${level} | ${message}`; + + const id = Logger.#_getMessageId(idMap, key); + + if (!this.#_messageFlags.get(id)) { + this.#_messageFlags.set(id, true); + this.level(level, message, messageType); + } + } + + /*@devdoc + * Logs a message at the default level, with optional type. + * @param {message} string - the message to log + * @param {messageType} string - optional user defined message type + */ + message(message: string, messageType?: string): void { + this.level(LogLevel.DEFAULT, message, messageType); + } + + /*@devdoc + * Logs a message at the info level, with optional type. + * @param {message} string - the message to log + * @param {messageType} string - optional user defined message type + */ + info(message: string, messageType?: string): void { + this.level(LogLevel.INFO, message, messageType); + } + + /*@devdoc + * Logs a message at the debug level, with optional type. + * @param {message} string - the message to log + * @param {messageType} string - optional user defined message type + */ + debug(message: string, messageType?: string): void { + this.level(LogLevel.DEBUG, message, messageType); + } + + /*@devdoc + * Logs a message at the warning level, with optional type. + * @param {message} string - the message to log + * @param {messageType} string - optional user defined message type + */ + warning(message: string, messageType?: string): void { + this.level(LogLevel.WARNING, message, messageType); + } + + /*@devdoc + * Logs a message at the error level, with optional type. + * @param {message} string - the message to log + * @param {messageType} string - optional user defined message type + */ + error(message: string, messageType?: string): void { + this.level(LogLevel.ERROR, message, messageType); + } + + /*@devdoc + * Applies a filer to all subsequently logged messages, based on level. + * @param {pred} (level: LogLevel) => boolean - the filtering condition + */ + filterLevels(pred: (level: LogLevel) => boolean): void { + this.#_activeFunctions = new Map(); + for (const level of allLogLevels) { + const func = this.#_context.getFunction(level); + if (func && pred(level)) { + this.#_activeFunctions.set(level, func); + } + } + } + + /*@devdoc + * Sets a filter for all subsequently logged messages, based on user defined types. + * @param {filter} Array - the collection of types to allow, + * presence or presence of undefined value will determine whether to show or hide messages with no type specified. + */ + setTypeFilter(filter?: Array): void { + this.#_typeFilter = filter; + } + + #_isTypeAllowed(messageType?: string): boolean { + return !this.#_typeFilter || this.#_typeFilter.includes(messageType); + } + + static #_getMessageId(idMap: Map, key: string): unknown { + if (!idMap.has(key)) { + const id = {}; + idMap.set(key, id); + return id; + } + return idMap.get(key); + } + +} + +/*@devdoc + * The Log is the main Logger instance used in the SDK. + */ +const Log = new Logger(new ConsoleLoggerContext()); + +export default Log; diff --git a/src/domain/worklets/AudioInputProcessor.ts b/src/domain/worklets/AudioInputProcessor.ts index f449bbac..e9df03e9 100644 --- a/src/domain/worklets/AudioInputProcessor.ts +++ b/src/domain/worklets/AudioInputProcessor.ts @@ -8,6 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import Log from "../shared/Log"; /*@devdoc * The AudioInputProcessor class implements a Web Audio @@ -75,7 +76,7 @@ class AudioInputProcessor extends AudioWorkletProcessor { // @ts-ignore process(inputList: Float32Array[][] /* , outputList: Float32Array[][], parameters: Record */) { if (!inputList || !inputList[0] || !inputList[0][0] || this._channelCount === 2 && !inputList[0][1]) { - console.log("Early return!"); + Log.message("Early return!", "audioworklet"); return true; } diff --git a/tests/domain/shared/Log.unit.test.ts b/tests/domain/shared/Log.unit.test.ts new file mode 100644 index 00000000..2b4dbb75 --- /dev/null +++ b/tests/domain/shared/Log.unit.test.ts @@ -0,0 +1,277 @@ +// +// DomainServer.unit.test.js +// +// Created by Nshan G. on 30 Oct 2021. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { Logger, LogLevel, ConsoleLoggerContext, StringLoggerContext } from "../../../src/domain/shared/Log"; + +describe("Logger - unit tests", () => { + + test("Console context", () => { + + const logger = new Logger(new ConsoleLoggerContext()); + + const debug = jest.spyOn(console, "debug").mockImplementation(() => { /* no-op */ }); + const log = jest.spyOn(console, "log").mockImplementation(() => { /* no-op */ }); + const info = jest.spyOn(console, "info").mockImplementation(() => { /* no-op */ }); + const warn = jest.spyOn(console, "warn").mockImplementation(() => { /* no-op */ }); + const error = jest.spyOn(console, "error").mockImplementation(() => { /* no-op */ }); + + logger.debug("Console debug message"); + logger.message("Console log message"); + logger.info("Console info message"); + logger.warning("Console warning message"); + logger.error("Console error message"); + + expect(debug).toHaveBeenCalledWith("Console debug message"); + expect(log).toHaveBeenCalledWith("Console log message"); + expect(info).toHaveBeenCalledWith("Console info message"); + expect(warn).toHaveBeenCalledWith("Console warning message"); + expect(error).toHaveBeenCalledWith("Console error message"); + + debug.mockClear(); + log.mockClear(); + info.mockClear(); + warn.mockClear(); + error.mockClear(); + + logger.debug("Console debug message", "Type 1"); + logger.message("Console log message", "Type 2"); + logger.info("Console info message", "Type 3"); + logger.warning("Console warning message", "Type 4"); + logger.error("Console error message", "Type 5"); + + expect(debug).toHaveBeenCalledWith("[Type 1]", "Console debug message"); + expect(log).toHaveBeenCalledWith("[Type 2]", "Console log message"); + expect(info).toHaveBeenCalledWith("[Type 3]", "Console info message"); + expect(warn).toHaveBeenCalledWith("[Type 4]", "Console warning message"); + expect(error).toHaveBeenCalledWith("[Type 5]", "Console error message"); + + debug.mockRestore(); + log.mockRestore(); + info.mockRestore(); + warn.mockRestore(); + error.mockRestore(); + + }); + + test("String context", () => { + const context = new StringLoggerContext(); + const logger = new Logger(context); + + logger.debug("Debug message"); + logger.message("Default message"); + logger.info("Info message"); + logger.warning("Warning message"); + logger.error("Error message"); + + expect(context.buffer).toBe("" + + "[DEBUG] Debug message\n" + + "[DEFAULT] Default message\n" + + "[INFO] Info message\n" + + "[WARNING] Warning message\n" + + "[ERROR] Error message\n" + ); + context.buffer = ""; + + logger.debug("Debug message", "Type 1"); + logger.message("Default message", "Type 2"); + logger.info("Info message", "Type 3"); + logger.warning("Warning message", "Type 4"); + logger.error("Error message", "Type 5"); + + expect(context.buffer).toBe("" + + "[Type 1][DEBUG] Debug message\n" + + "[Type 2][DEFAULT] Default message\n" + + "[Type 3][INFO] Info message\n" + + "[Type 4][WARNING] Warning message\n" + + "[Type 5][ERROR] Error message\n" + ); + }); + + test("Filtering by level", () => { + const context = new StringLoggerContext(); + const logger = new Logger(context); + + logger.filterLevels((level) => { + return level >= LogLevel.INFO; + }); + + logger.debug("Debug message"); + logger.message("Default message"); + logger.info("Info message"); + logger.warning("Warning message"); + logger.error("Error message"); + + expect(context.buffer).toBe("" + + "[INFO] Info message\n" + + "[WARNING] Warning message\n" + + "[ERROR] Error message\n" + ); + context.buffer = ""; + + logger.filterLevels((level) => { + return level <= LogLevel.DEFAULT; + }); + + logger.debug("Debug message"); + logger.message("Default message"); + logger.info("Info message"); + logger.warning("Warning message"); + logger.error("Error message"); + + expect(context.buffer).toBe("" + + "[DEBUG] Debug message\n" + + "[DEFAULT] Default message\n" + ); + context.buffer = ""; + + logger.filterLevels((level) => { + return [LogLevel.DEBUG, LogLevel.ERROR].includes(level); + }); + + logger.debug("Debug message"); + logger.message("Default message"); + logger.info("Info message"); + logger.warning("Warning message"); + logger.error("Error message"); + + expect(context.buffer).toBe("" + + "[DEBUG] Debug message\n" + + "[ERROR] Error message\n" + ); + + }); + + test("Filtering by type", () => { + const context = new StringLoggerContext(); + const logger = new Logger(context); + + logger.setTypeFilter(["Type 2", "Type 4"]); + + logger.message("message"); + logger.message("message 1", "Type 1"); + logger.message("message 2", "Type 2"); + logger.message("message 3", "Type 3"); + logger.message("message 4", "Type 4"); + logger.message("message 5", "Type 5"); + + expect(context.buffer).toBe("" + + "[Type 2][DEFAULT] message 2\n" + + "[Type 4][DEFAULT] message 4\n" + ); + context.buffer = ""; + + logger.setTypeFilter(["Type 1", "Type 3", undefined]); + + logger.message("message"); + logger.message("message 1", "Type 1"); + logger.message("message 2", "Type 2"); + logger.message("message 3", "Type 3"); + logger.message("message 4", "Type 4"); + logger.message("message 5", "Type 5"); + + expect(context.buffer).toBe("" + + "[DEFAULT] message\n" + + "[Type 1][DEFAULT] message 1\n" + + "[Type 3][DEFAULT] message 3\n" + ); + context.buffer = ""; + + logger.setTypeFilter([undefined]); + + logger.message("message"); + logger.message("message 1", "Type 1"); + logger.message("message 2", "Type 2"); + logger.message("message 3", "Type 3"); + logger.message("message 4", "Type 4"); + logger.message("message 5", "Type 5"); + + expect(context.buffer).toBe("" + + "[DEFAULT] message\n" + ); + context.buffer = ""; + + logger.setTypeFilter(); + + logger.message("message"); + logger.message("message 1", "Type 1"); + logger.message("message 2", "Type 2"); + logger.message("message 3", "Type 3"); + logger.message("message 4", "Type 4"); + logger.message("message 5", "Type 5"); + + expect(context.buffer).toBe("" + + "[DEFAULT] message\n" + + "[Type 1][DEFAULT] message 1\n" + + "[Type 2][DEFAULT] message 2\n" + + "[Type 3][DEFAULT] message 3\n" + + "[Type 4][DEFAULT] message 4\n" + + "[Type 5][DEFAULT] message 5\n" + ); + context.buffer = ""; + + }); + + test("Mixed filtering", () => { + const context = new StringLoggerContext(); + const logger = new Logger(context); + + logger.filterLevels((level) => { + return level <= LogLevel.DEFAULT; + }); + logger.setTypeFilter(["Type 2"]); + + logger.message("message"); + logger.debug("Debug message 1", "Type 1"); + logger.message("Default message 2", "Type 2"); + logger.info("Info message 1", "Type 1"); + logger.warning("message 2", "Type 2"); + logger.error("message 1", "Type 1"); + + expect(context.buffer).toBe("" + + "[Type 2][DEFAULT] Default message 2\n" + ); + context.buffer = ""; + }); + + test("Logger.once", () => { + const context = new StringLoggerContext(); + const logger = new Logger(context); + + logger.once(LogLevel.DEBUG, "message"); + logger.once(LogLevel.DEBUG, "message"); + logger.once(LogLevel.DEBUG, "message"); + + logger.once(LogLevel.DEBUG, "message", "Type"); + logger.once(LogLevel.DEBUG, "message", "Type"); + logger.once(LogLevel.DEBUG, "message", "Type"); + + logger.once(LogLevel.DEBUG, "message", "undefined"); + logger.once(LogLevel.DEBUG, "message", "undefined"); + logger.once(LogLevel.DEBUG, "message", "undefined"); + + logger.once(LogLevel.INFO, "message"); + logger.once(LogLevel.INFO, "message"); + logger.once(LogLevel.INFO, "message"); + + logger.once(LogLevel.DEBUG, "another message"); + logger.once(LogLevel.DEBUG, "another message"); + logger.once(LogLevel.DEBUG, "another message"); + + expect(context.buffer).toBe("" + + "[DEBUG] message\n" + + "[Type][DEBUG] message\n" + + "[undefined][DEBUG] message\n" + + "[INFO] message\n" + + "[DEBUG] another message\n" + ); + context.buffer = ""; + }); + +});