feat: add debug logging

Add debug logs using the `debug` library.

To use this new feature, set the `DEBUG` environment variable:
- `DEBUG=mineping:*` enables all logs from this library.
- `DEBUG=mineping:java` enables logs for the Java module only.
- `DEBUG=mineping:bedrock` enables logs for the Bedrock module only.
This commit is contained in:
2025-06-19 02:14:04 +03:00
parent c7b99cb6db
commit 7322034aba
4 changed files with 45 additions and 5 deletions

View File

@ -7,6 +7,9 @@
import dgram from "node:dgram"; import dgram from "node:dgram";
import crypto from "node:crypto"; import crypto from "node:crypto";
import createDebug from "debug";
const debug = createDebug("mineping:bedrock");
const MAGIC = "00ffff00fefefefefdfdfdfd12345678"; const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
const START_TIME = Date.now(); const START_TIME = Date.now();
@ -177,6 +180,7 @@ const parseUnconnectedPong = (pongPacket) => {
motdOffset, motdOffset,
motdOffset + motdLength motdOffset + motdLength
); );
debug("received raw MOTD string: %s", motdString);
const rawMotd = parseMotd(motdString); const rawMotd = parseMotd(motdString);
const motd = transformMotd(rawMotd); const motd = transformMotd(rawMotd);
@ -197,6 +201,7 @@ export const pingBedrock = (host, options = {}) => {
} }
const { port = 19132, timeout = 5000 } = options; const { port = 19132, timeout = 5000 } = options;
debug("pinging Bedrock server %s:%d with %dms timeout", host, port, timeout);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const socket = dgram.createSocket("udp4"); const socket = dgram.createSocket("udp4");
@ -215,17 +220,20 @@ export const pingBedrock = (host, options = {}) => {
const cleanup = () => { const cleanup = () => {
if (isCleanupCompleted) return; if (isCleanupCompleted) return;
isCleanupCompleted = true; isCleanupCompleted = true;
debug("cleaning up resources for %s:%d", host, port);
clearTimeout(timeoutTask); clearTimeout(timeoutTask);
socket.close(); socket.close();
}; };
// Generic error handler // Generic error handler
socket.on("error", (err) => { socket.on("error", (err) => {
debug("socket error for %s:%d - %s", host, port, err.message);
cleanup(); cleanup();
reject(err); reject(err);
}); });
socket.on("message", (pongPacket) => { socket.on("message", (pongPacket) => {
debug("received %d bytes from %s:%d", pongPacket.length, host, port);
try { try {
const motd = parseUnconnectedPong(pongPacket); const motd = parseUnconnectedPong(pongPacket);
cleanup(); cleanup();
@ -237,6 +245,8 @@ export const pingBedrock = (host, options = {}) => {
try { try {
const pingPacket = createUnconnectedPingFrame(Date.now() - START_TIME); const pingPacket = createUnconnectedPingFrame(Date.now() - START_TIME);
debug("sending Unconnected Ping packet to %s:%d", host, port);
debug("packet: %o", pingPacket);
socket.send(pingPacket, 0, pingPacket.length, port, host); socket.send(pingPacket, 0, pingPacket.length, port, host);
} catch (err) { } catch (err) {
// Handle any immediate, synchronous errors that might occur when sending the ping packet // Handle any immediate, synchronous errors that might occur when sending the ping packet

View File

@ -7,8 +7,11 @@
import net from "node:net"; import net from "node:net";
import dns from "node:dns/promises"; import dns from "node:dns/promises";
import createDebug from "debug";
import * as varint from "./varint.js"; import * as varint from "./varint.js";
const debug = createDebug("mineping:java");
/** /**
* Represents the structured and user-friendly response from a server ping. * Represents the structured and user-friendly response from a server ping.
* The fields and their optionality are based on the official protocol documentation. * The fields and their optionality are based on the official protocol documentation.
@ -70,6 +73,7 @@ function processResponse(buffer) {
// Check if the full packet has arrived yet. // Check if the full packet has arrived yet.
if (buffer.length < offset + packetLength) { if (buffer.length < offset + packetLength) {
debug("packet incomplete, waiting for more data");
return null; // Incomplete packet, wait for more data. return null; // Incomplete packet, wait for more data.
} }
@ -86,12 +90,14 @@ function processResponse(buffer) {
offset += jsonLengthResult.bytesRead; offset += jsonLengthResult.bytesRead;
if (buffer.length < offset + jsonLength) { if (buffer.length < offset + jsonLength) {
debug("JSON string incomplete, waiting for more data");
return null; // Incomplete JSON string, wait for more data. return null; // Incomplete JSON string, wait for more data.
} }
const jsonString = buffer const jsonString = buffer
.subarray(offset, offset + jsonLength) .subarray(offset, offset + jsonLength)
.toString("utf8"); .toString("utf8");
debug("received raw JSON response");
const response = JSON.parse(jsonString); const response = JSON.parse(jsonString);
// Return the response and any data that came after this packet. // Return the response and any data that came after this packet.
@ -101,6 +107,7 @@ function processResponse(buffer) {
} catch (err) { } catch (err) {
// If the buffer is too short for a VarInt, it's a recoverable state. // If the buffer is too short for a VarInt, it's a recoverable state.
if (err.code === varint.ERR_VARINT_BUFFER_UNDERFLOW) { if (err.code === varint.ERR_VARINT_BUFFER_UNDERFLOW) {
debug("buffer underflow while parsing VarInt, waiting for more data");
return null; // Wait for more data. return null; // Wait for more data.
} }
// For malformed VarInts or JSON, throw the error to reject the promise. // For malformed VarInts or JSON, throw the error to reject the promise.
@ -128,26 +135,31 @@ export async function pingJava(host, options = {}) {
timeout = 5000, timeout = 5000,
protocolVersion = -1, protocolVersion = -1,
} = options; } = options;
debug("pinging Java server %s with options: %o", host, options);
let targetHost = host; let targetHost = host;
let targetPort = fallbackPort; let targetPort = fallbackPort;
try { try {
debug("attempting SRV lookup for _minecraft._tcp.%s", host);
const srvRecords = await dns.resolveSrv(`_minecraft._tcp.${host}`); const srvRecords = await dns.resolveSrv(`_minecraft._tcp.${host}`);
if (srvRecords.length > 0) { if (srvRecords.length > 0) {
targetHost = srvRecords[0].name; targetHost = srvRecords[0].name;
targetPort = srvRecords[0].port; targetPort = srvRecords[0].port;
debug("SRV lookup successful, new target: %s:%d", targetHost, targetPort);
} }
} catch (err) { } catch (err) {
// Common errors like ENODATA or ENOTFOUND are expected when a server // Common errors like ENODATA or ENOTFOUND are expected when a server
// does not have an SRV record, so we ignore them and proceed. // does not have an SRV record, so we ignore them and proceed.
if (!["ENODATA", "ENOTFOUND"].includes(err.code)) { if (!["ENODATA", "ENOTFOUND"].includes(err.code)) {
debug("SRV lookup for %s failed (%s), using fallback", host, err.code);
// For other errors we should re-throw. // For other errors we should re-throw.
throw err; throw err;
} }
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
debug("creating TCP connection to %s:%d", targetHost, targetPort);
const socket = net.createConnection({ host: targetHost, port: targetPort }); const socket = net.createConnection({ host: targetHost, port: targetPort });
// Prevent cleanup tasks from running more than once // Prevent cleanup tasks from running more than once
@ -164,6 +176,7 @@ export async function pingJava(host, options = {}) {
const cleanup = () => { const cleanup = () => {
if (isCleanupCompleted) return; if (isCleanupCompleted) return;
isCleanupCompleted = true; isCleanupCompleted = true;
debug("cleaning up resources for %s:%d", targetHost, targetPort);
clearTimeout(timeoutTask); clearTimeout(timeoutTask);
socket.destroy(); socket.destroy();
}; };
@ -174,18 +187,25 @@ export async function pingJava(host, options = {}) {
// Generic error handler // Generic error handler
socket.on("error", (err) => { socket.on("error", (err) => {
debug("socket error for %s:%d - %s", targetHost, targetPort, err.message);
cleanup(); cleanup();
reject(err); reject(err);
}); });
socket.on("close", () => { socket.on("close", () => {
if (!isCleanupCompleted) { if (!isCleanupCompleted) {
debug("socket for %s:%d closed prematurely", targetHost, targetPort);
cleanup(); cleanup();
reject(new Error("Socket closed unexpectedly without a response.")); reject(new Error("Socket closed unexpectedly without a response."));
} }
}); });
socket.on("connect", () => { socket.on("connect", () => {
debug(
"socket connected to %s:%d, sending packets...",
targetHost,
targetPort
);
try { try {
const handshakePacket = createHandshakePacket( const handshakePacket = createHandshakePacket(
host, host,
@ -204,11 +224,17 @@ export async function pingJava(host, options = {}) {
let incomingBuffer = Buffer.alloc(0); let incomingBuffer = Buffer.alloc(0);
socket.on("data", (data) => { socket.on("data", (data) => {
debug(
"received %d bytes of data, total buffer size is now %d bytes",
data.length,
incomingBuffer.length + data.length
);
incomingBuffer = Buffer.concat([incomingBuffer, data]); incomingBuffer = Buffer.concat([incomingBuffer, data]);
try { try {
const result = processResponse(incomingBuffer); const result = processResponse(incomingBuffer);
if (result) { if (result) {
debug("successfully parsed full response");
// We successfully parsed a response. Clean up before resolving. // We successfully parsed a response. Clean up before resolving.
cleanup(); cleanup();
resolve(result.response); resolve(result.response);

9
package-lock.json generated
View File

@ -1,13 +1,16 @@
{ {
"name": "@minescope/mineping", "name": "@minescope/mineping",
"version": "1.6.1", "version": "1.7.0-beta.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@minescope/mineping", "name": "@minescope/mineping",
"version": "1.6.1", "version": "1.7.0-beta.0",
"license": "MIT", "license": "MIT",
"dependencies": {
"debug": "^4.4.1"
},
"devDependencies": { "devDependencies": {
"vitest": "^3.2.3" "vitest": "^3.2.3"
}, },
@ -917,7 +920,6 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@ -1067,7 +1069,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": { "node_modules/nanoid": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@minescope/mineping", "name": "@minescope/mineping",
"version": "1.7.0-beta.0", "version": "1.7.0-beta.1",
"description": "Ping both Minecraft Bedrock and Java servers.", "description": "Ping both Minecraft Bedrock and Java servers.",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@ -23,6 +23,9 @@
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
"dependencies": {
"debug": "^4.4.1"
},
"devDependencies": { "devDependencies": {
"vitest": "^3.2.3" "vitest": "^3.2.3"
} }