Skip to content

Instantly share code, notes, and snippets.

@shovon
Last active Feb 23, 2021
Embed
What would you like to do?

console.debug debugger helper

A simple module for helping you write debugger outputs. Makes debug outputs look neat and organized.

Usage

Copy and paste the index.ts file to some debug.ts file.

import createDebugger from './debug';

const debug = createDebugger('module');

console.debug(...debug('Cool', 'nice'));
type Tag = {
value: string;
lastTimestamp: Date;
};
const mappings: Tag[] = [];
type TagExistsParams = {
tag: string;
queryString: string;
};
/**
* Given a string and a querystring, determine if the string matches the
* querystring.
*
* The string is a tag. Tags can have subtags. These subtags are delimited by
* a colon.
*
* Here are some example tags:
*
* spawner
* worker:a
* worker:b
* worker:a:subworker:a
* worker:a:subworker:b
*
* And here are some querystrings that can match any or all of the above tags:
*
* spawner
* spawner,foo
* worker:a:*
* worker:a
* worker:*
* worker:*,spawner
* *
* @param param0 The options
*/
export function tagMatches({ tag, queryString }: TagExistsParams): boolean {
const queries = queryString.split(",");
for (const query of queries) {
const queryComponents = query.split(":");
let tagComponents = tag.split(":");
if (queryComponents.length !== tagComponents.length) {
if (
queryComponents.length < tagComponents.length &&
queryComponents[queryComponents.length - 1] === "*"
) {
tagComponents = tagComponents.slice(0, queryComponents.length);
} else {
continue;
}
}
let hasMatch = true;
for (const [index, component] of queryComponents.entries()) {
if (component !== "*" && tagComponents[index] !== component) {
hasMatch = false;
break;
}
}
if (hasMatch) {
return true;
}
}
return false;
}
function getMapping(tag: string): { index: number; mapping: Tag } {
for (const [i, mapping] of mappings.entries()) {
if (mapping.value === tag) {
return { index: i, mapping };
}
}
const mapping = { value: tag, lastTimestamp: new Date() };
mappings.push(mapping);
return { index: mappings.length - 1, mapping };
}
/**
* Creates a format string, given the list of parameters.
* @param params The list of parameters, which can conist of just about
* anything.
*/
export function createFormatString(
...params: any[]
): { params: any[]; formatString: string } {
if (params.length < 0) {
return {
params: [],
formatString: "",
};
}
if (typeof params[0] === "string") {
if (/%[a-zA-Z]/.test(params[0])) {
const count = (params[0].match(/%[a-zA-Z]/g) || []).length;
const [formatString, ...remainder] = params;
const objectFormat = remainder.slice(count, remainder.length);
if (objectFormat.length > 0) {
return {
params: remainder,
formatString: `${formatString} ${objectFormat
.map(() => "%O")
.join(" ")}`,
};
} else {
return {
params: remainder,
formatString: formatString,
};
}
}
return {
params: params,
formatString: params.map(() => "%O").join(" "),
};
}
return {
params: params,
formatString: params.map(() => "%o").join(" "),
};
}
const goldenAngle = 137.5077640500378546463487;
/**
* Creates a new debugger instance.
*
* The whole point of the debugger instance is to get a set a parameters to be
* passed into the console.debug function.
*
* Usage:
*
* import createDebugger from './debug';
*
* const debug = createDebugger('module');
*
* console.debug(...debug('Cool', 'nice'));
* @param tag The tag by which to set the params under.
*/
export default function createDebugger(tag: string) {
return (...content: any[]) => {
const { index, mapping } = getMapping(tag);
const color = `color: hsl(${(index * goldenAngle) % 360}, 55%, 50%)`;
const reset = "color: inherit";
const time = new Date();
const { params, formatString } = createFormatString(...content);
const paramsToReturn = [
`%c%s%c ${formatString} %c%s%c`,
color,
tag,
reset,
...params,
color,
`+${time.getTime() - mapping.lastTimestamp.getTime()}ms`,
reset,
];
mapping.lastTimestamp = time;
return paramsToReturn;
};
}
import { tagMatches, createFormatString } from "./";
import * as util from "util";
function printFailure(message: string) {
console.error(`\x1b[31m❌ ${message}\x1b[0m`);
}
function printObjectFailure(label: string, value: any, expected: any) {
console.error(`\n\x1b[31m❌ ${label}\x1b[0m.\n`);
console.error("Expected", expected, "but got", value);
console.error();
}
function printPass(message: string) {
console.log(`\x1b[32m✔ ${message}\x1b[0m`);
}
let failed = false;
function assert(assertion: boolean, label: string) {
if (!assertion) {
failed = true;
printFailure(label);
} else {
printPass(label);
}
}
function assertDeepEquals(value: any, expected: any, label: string) {
if (!util.isDeepStrictEqual(value, expected)) {
failed = true;
printObjectFailure(label, value, expected);
} else {
printPass(label);
}
}
function testTagMatches() {
assert(
!tagMatches({ tag: "something", queryString: "another" }),
"Should not be able to match"
);
assert(
tagMatches({ tag: "something", queryString: "something" }),
"Should be able to match by equality"
);
assert(
tagMatches({ tag: "something", queryString: "something,another" }),
"Should be able to match, given two queries"
);
assert(
tagMatches({ tag: "something", queryString: "another,something" }),
"Should be able to match, given two queries, regardless of order"
);
assert(
tagMatches({ tag: "something", queryString: "another,something,foo" }),
"Should be able to match, given three queries, regardless of order"
);
assert(
tagMatches({ tag: "something:another", queryString: "something:another" }),
"Should be able to match with colon"
);
assert(
tagMatches({
tag: "something:another",
queryString: "something:another,hello",
}),
"Should be able to match with colon, given multiple queries"
);
assert(
tagMatches({
tag: "something:another",
queryString: "hello,something:another",
}),
"Should be able to match with colon, given multiple queries, regardless of order"
);
assert(
tagMatches({
tag: "something:another",
queryString: "foo:bar,something,something:another",
}),
"Should be able to match with colon, given multiple queries, mixing and matching"
);
assert(
!tagMatches({
tag: "something:another",
queryString: "something:foo",
}),
"Should not match given prefix"
);
assert(
tagMatches({
tag: "something:another",
queryString: "something:*",
}),
"Glob matching should match"
);
assert(
tagMatches({
tag: "something:another",
queryString: "something:foo,something:*",
}),
"Useless glob example. Should still work"
);
assert(
tagMatches({
tag: "something:another",
queryString: "another:*,something:*",
}),
"Multiple globs can still work in the querystring"
);
assert(
!tagMatches({
tag: "something:another",
queryString: "another:*,foo:*",
}),
"If none of the globs match, then the globs do not match"
);
assert(
tagMatches({
tag: "something:another:yetanother",
queryString: "something:another:yetanother",
}),
"Should match tags with three components"
);
assert(
tagMatches({
tag: "something:another:yetanother",
queryString: "something:another:*",
}),
"Should match wildcard for three components"
);
assert(
tagMatches({
tag: "something:another:yetanother",
queryString: "something:*",
}),
'Should match for everything prefixed with "something"'
);
assert(
tagMatches({
tag: "something:another:yetanother:foobar",
queryString: "*",
}),
"Should catch all"
);
}
function testCreateFormatString() {
assertDeepEquals(
createFormatString(),
{ params: [], formatString: "" },
"Empty"
);
assertDeepEquals(
createFormatString("Some string"),
{ params: ["Some string"], formatString: "%O" },
"Single string"
);
assertDeepEquals(
createFormatString("Some string", "another string"),
{
params: ["Some string", "another string"],
formatString: "%O %O",
},
"Two strings"
);
assertDeepEquals(
createFormatString("%s", "some string"),
{
params: ["some string"],
formatString: "%s",
},
"Format string"
);
assertDeepEquals(
createFormatString("%s %s", "some", "string"),
{
params: ["some", "string"],
formatString: "%s %s",
},
"Format string with two params"
);
assertDeepEquals(
createFormatString("%s", "some", "string"),
{
params: ["some", "string"],
formatString: "%s %O",
},
"Format string with one expected param, and an excessive param"
);
assertDeepEquals(
createFormatString("Some preamble %s", "some", "string"),
{
params: ["some", "string"],
formatString: "Some preamble %s %O",
},
"Format string with one expected param, the original string, and an excessive param"
);
assertDeepEquals(
createFormatString("%s %s", "some", "string", true),
{
params: ["some", "string", true],
formatString: "%s %s %O",
},
"Format string with two expected params, and an extra param"
);
assertDeepEquals(
createFormatString(
"Some preamble %s an intermission %s",
"some",
"string",
true
),
{
params: ["some", "string", true],
formatString: "Some preamble %s an intermission %s %O",
},
"Format string with two expected params, the original string, and an extra param"
);
assertDeepEquals(
createFormatString("%s %s", "some", "string", true, false, 10, 42),
{
params: ["some", "string", true, false, 10, 42],
formatString: "%s %s %O %O %O %O",
},
"Format string with two expected params, and an extra param"
);
assertDeepEquals(
createFormatString(
"Some preamble %s an intermission %s",
"some",
"string",
true,
false,
10,
42
),
{
params: ["some", "string", true, false, 10, 42],
formatString: "Some preamble %s an intermission %s %O %O %O %O",
},
"Format string with two expected params, the original string, and an extra param"
);
assertDeepEquals(
createFormatString(true, "hello", "world"),
{
params: [true, "hello", "world"],
formatString: "%o %o %o",
},
"Format string with object at the start"
);
}
const tests: { test: () => void; label: string }[] = [
{ test: testTagMatches, label: "tagMatches" },
{ test: testCreateFormatString, label: "createFormatString" },
];
for (const { test, label } of tests) {
console.log(label);
console.log();
test();
console.log();
}
if (failed) {
process.exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment