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 crypto from "node:crypto";
import createDebug from "debug";
const debug = createDebug("mineping:bedrock");
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
const START_TIME = Date.now();
@ -177,6 +180,7 @@ const parseUnconnectedPong = (pongPacket) => {
motdOffset,
motdOffset + motdLength
);
debug("received raw MOTD string: %s", motdString);
const rawMotd = parseMotd(motdString);
const motd = transformMotd(rawMotd);
@ -197,6 +201,7 @@ export const pingBedrock = (host, options = {}) => {
}
const { port = 19132, timeout = 5000 } = options;
debug("pinging Bedrock server %s:%d with %dms timeout", host, port, timeout);
return new Promise((resolve, reject) => {
const socket = dgram.createSocket("udp4");
@ -215,17 +220,20 @@ export const pingBedrock = (host, options = {}) => {
const cleanup = () => {
if (isCleanupCompleted) return;
isCleanupCompleted = true;
debug("cleaning up resources for %s:%d", host, port);
clearTimeout(timeoutTask);
socket.close();
};
// Generic error handler
socket.on("error", (err) => {
debug("socket error for %s:%d - %s", host, port, err.message);
cleanup();
reject(err);
});
socket.on("message", (pongPacket) => {
debug("received %d bytes from %s:%d", pongPacket.length, host, port);
try {
const motd = parseUnconnectedPong(pongPacket);
cleanup();
@ -237,6 +245,8 @@ export const pingBedrock = (host, options = {}) => {
try {
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);
} catch (err) {
// 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 dns from "node:dns/promises";
import createDebug from "debug";
import * as varint from "./varint.js";
const debug = createDebug("mineping:java");
/**
* Represents the structured and user-friendly response from a server ping.
* 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.
if (buffer.length < offset + packetLength) {
debug("packet incomplete, waiting for more data");
return null; // Incomplete packet, wait for more data.
}
@ -86,12 +90,14 @@ function processResponse(buffer) {
offset += jsonLengthResult.bytesRead;
if (buffer.length < offset + jsonLength) {
debug("JSON string incomplete, waiting for more data");
return null; // Incomplete JSON string, wait for more data.
}
const jsonString = buffer
.subarray(offset, offset + jsonLength)
.toString("utf8");
debug("received raw JSON response");
const response = JSON.parse(jsonString);
// Return the response and any data that came after this packet.
@ -101,6 +107,7 @@ function processResponse(buffer) {
} catch (err) {
// If the buffer is too short for a VarInt, it's a recoverable state.
if (err.code === varint.ERR_VARINT_BUFFER_UNDERFLOW) {
debug("buffer underflow while parsing VarInt, waiting for more data");
return null; // Wait for more data.
}
// For malformed VarInts or JSON, throw the error to reject the promise.
@ -128,26 +135,31 @@ export async function pingJava(host, options = {}) {
timeout = 5000,
protocolVersion = -1,
} = options;
debug("pinging Java server %s with options: %o", host, options);
let targetHost = host;
let targetPort = fallbackPort;
try {
debug("attempting SRV lookup for _minecraft._tcp.%s", host);
const srvRecords = await dns.resolveSrv(`_minecraft._tcp.${host}`);
if (srvRecords.length > 0) {
targetHost = srvRecords[0].name;
targetPort = srvRecords[0].port;
debug("SRV lookup successful, new target: %s:%d", targetHost, targetPort);
}
} catch (err) {
// Common errors like ENODATA or ENOTFOUND are expected when a server
// does not have an SRV record, so we ignore them and proceed.
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.
throw err;
}
}
return new Promise((resolve, reject) => {
debug("creating TCP connection to %s:%d", targetHost, targetPort);
const socket = net.createConnection({ host: targetHost, port: targetPort });
// Prevent cleanup tasks from running more than once
@ -164,6 +176,7 @@ export async function pingJava(host, options = {}) {
const cleanup = () => {
if (isCleanupCompleted) return;
isCleanupCompleted = true;
debug("cleaning up resources for %s:%d", targetHost, targetPort);
clearTimeout(timeoutTask);
socket.destroy();
};
@ -174,18 +187,25 @@ export async function pingJava(host, options = {}) {
// Generic error handler
socket.on("error", (err) => {
debug("socket error for %s:%d - %s", targetHost, targetPort, err.message);
cleanup();
reject(err);
});
socket.on("close", () => {
if (!isCleanupCompleted) {
debug("socket for %s:%d closed prematurely", targetHost, targetPort);
cleanup();
reject(new Error("Socket closed unexpectedly without a response."));
}
});
socket.on("connect", () => {
debug(
"socket connected to %s:%d, sending packets...",
targetHost,
targetPort
);
try {
const handshakePacket = createHandshakePacket(
host,
@ -204,11 +224,17 @@ export async function pingJava(host, options = {}) {
let incomingBuffer = Buffer.alloc(0);
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]);
try {
const result = processResponse(incomingBuffer);
if (result) {
debug("successfully parsed full response");
// We successfully parsed a response. Clean up before resolving.
cleanup();
resolve(result.response);

9
package-lock.json generated
View File

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

View File

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