Last active
November 17, 2025 09:46
-
-
Save ArtemGr/25fad725870b0ada74df2bce7d115cc1 to your computer and use it in GitHub Desktop.
llog.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // To add this Gist to a project: | |
| // | |
| // npm install https://gist.github.com/25fad725870b0ada74df2bce7d115cc1.git | |
| /** | |
| * @param {number | number[]} level | |
| * @returns {Array<RegExpMatchArray | null>} | |
| */ | |
| exports.trace = function (level) { | |
| const levels = Array.isArray (level) ? level : [level] | |
| const maxLevel = Math.max (...levels) | |
| const obj = {} | |
| const lim = Error.stackTraceLimit | |
| Error.stackTraceLimit = maxLevel + 1 | |
| Error.captureStackTrace (obj) | |
| Error.stackTraceLimit = lim | |
| const re = /^ at ([^\n]+)?$/mg | |
| // “ at Object.nai [as fun] (NovelAI.js:83:13)” | |
| // “ at exports.trace (c:\Users\...\llog\index.js:12:9)” | |
| // “ at c:\...\grab\grab.js:40:3” | |
| const rel = /[\w:\.\\\/-]*?([\w-]+)\.js:(\d+):\d+\)?$/ | |
| const results = [] | |
| for (const targetLevel of levels) { | |
| re.lastIndex = 0 // Reset regex | |
| let ma, mam; for (let ix = targetLevel; 0 < ix; --ix) { | |
| ma = re.exec (obj.stack) | |
| if (ma == null) { | |
| mam = null | |
| break | |
| } | |
| mam = rel.exec (ma[1]) | |
| if (false) console.log (ix, ma[1], mam) | |
| if (mam == null) { | |
| mam = null | |
| break | |
| } | |
| } | |
| results.push (mam) | |
| } | |
| return results} | |
| /** @type {Map<string, number>} */ | |
| let COUNTER | |
| /** | |
| * @param {string} uid Location to globally count to, or any other ID | |
| * @returns {number} Count towards how many times we were already at `loc` | |
| */ | |
| exports.count = function (uid) { | |
| if (COUNTER == null) COUNTER = new Map() | |
| const count = (COUNTER.get (uid) ?? 0) + 1 | |
| COUNTER.set (uid, count) | |
| return count} | |
| /** | |
| * @param {number} times How many times to return true to the call site | |
| * @returns {boolean} False if the call site evaluated it `times` | |
| */ | |
| exports.times = function (times) { | |
| const ma = exports.trace (3) [0] | |
| if (ma == null) return null // Can't count | |
| const loc = ma[1] + ':' + ma[2] | |
| const count = exports.count (loc) | |
| return count <= times} | |
| /** | |
| * Log a `line` prepending the current file name and line number | |
| * Works in NodeJS but probably not in a browser | |
| * @param {any} line | |
| * @param {number} [times] Log only N `times` from that location | |
| * @param {number[]} [levels] Trace levels, default [3] | |
| */ | |
| function log (line, times, levels = [3]) { | |
| const matches = exports.trace (levels) | |
| const validMatches = matches.filter (m => m != null) | |
| if (validMatches.length === 0) { | |
| log.out (line) | |
| } else { | |
| const locParts = [] | |
| let prevFile = null | |
| for (const m of validMatches) { | |
| const file = m[1] | |
| const lineNum = m[2] | |
| if (file === prevFile) { | |
| locParts.push (':' + lineNum) | |
| } else { | |
| locParts.push (file + ':' + lineNum) | |
| prevFile = file | |
| } | |
| } | |
| const loc = locParts.join (' ') | |
| if (times) if (exports.count (loc) > times) return | |
| log.out (line, loc)}} | |
| /** | |
| * Override to change destination | |
| * | |
| * @example | |
| * const date = require ('date-and-time'); | |
| * const {log} = require ('log'); | |
| * log.out = (line, loc) => console.log (date.format (new Date(), 'DD HH:mm]'), (loc ?? '') + ']', line) | |
| */ | |
| log.out = function (line, loc) {loc ? console.log (loc + ']', line) : console.log (line)} | |
| exports.log = log | |
| /** | |
| * Sleep for the given number of milliseconds | |
| * @param {number} ms | |
| */ | |
| exports.snooze = function (ms) {return new Promise (resolve => setTimeout (resolve, ms))} | |
| /** | |
| * NB: Different from `console.assert` in that we're throwing a “normal” `Error` | |
| * which unwinds, interrupting the program | |
| * @param {boolean} condition | |
| * @param {() => string} [message] | |
| * @param {number[]} [levels] Trace levels, default [3] | |
| */ | |
| exports.assert = function (condition, message, levels = [3]) { | |
| if (condition) return | |
| const matches = exports.trace (levels) | |
| const validMatches = matches.filter (m => m != null) | |
| let loc = '_' | |
| if (0 < validMatches.length) { | |
| const locParts = [] | |
| let prevFile = null | |
| for (const m of validMatches) { | |
| const file = m[1] | |
| const lineNum = m[2] | |
| if (file === prevFile) { | |
| locParts.push (':' + lineNum) | |
| } else { | |
| locParts.push (file + ':' + lineNum) | |
| prevFile = file | |
| } | |
| } | |
| loc = locParts.join (' ') | |
| } | |
| if (message) throw new Error ('[' + loc + '] Assertion failed: ' + message()) | |
| throw new Error ('[' + loc + '] Assertion failed')} | |
| exports.hash = s=>{for(var i=0,h=9;i<s.length;)h=Math.imul(h^s.charCodeAt(i++),9**9);return h^h>>>9} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "llog", | |
| "version": "1.1.2", | |
| "description": "log to console with line number attached", | |
| "main": "index.js", | |
| "scripts": { | |
| "test": "echo \"Error: no test specified\" && exit 1" | |
| }, | |
| "contributors": [ | |
| "Artemciy" | |
| ], | |
| "repository": { | |
| "type": "git", | |
| "url": "https://gist.github.com/25fad725870b0ada74df2bce7d115cc1.git" | |
| }, | |
| "license": "MIT" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment