Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

trace()

A tiny JavaScript debug helper: put trace(); at the top of a function and it logs Called: <functionName> — working the name out for itself. Silent by default, so it can't ship noise to production.

function loadUser(id) {
  trace();          // → Called: loadUser
  ...
}

Zero dependencies. One file. ~30 lines. MIT.

Live demo →


Why

Most of us have typed some version of this:

console.log('Called:', arguments.callee.name);   // don't

Two problems. arguments.callee is deprecated and throws in strict mode, ES modules and class bodies — which is most modern code. And those console.logs have a habit of ending up in production.

trace() fixes both: it reads the name off the stack instead of arguments.callee, and it does nothing at all unless you explicitly turn it on.

Install

Drop the file in — the primary way to use this. Copy trace.js into your project:

<script src="/js/trace.js"></script>
<script>
  trace.enabled = true;   // see "Enabling it" below for the grown-up version
</script>

It defines a global trace. Load it before the code you want to trace.

Or load it from a CDN (jsDelivr, served straight from this repo's tags — no install):

<!-- Pinned to a version (recommended) -->
<script src="https://cdn.jsdelivr.net/gh/1978io/trace@1.0.0/trace.js"></script>
<!-- Or always the latest -->
<script src="https://cdn.jsdelivr.net/gh/1978io/trace/trace.js"></script>

Pin to a tag (@1.0.0) for a stable, immutable URL; the untagged form tracks the default branch.

Or copy the source. It's one small file with no dependencies — pasting it into your existing utilities bundle is a perfectly good answer, and the licence allows it.

Or npm (see package.json — publishing is a follow-up, the file drop is the supported route today):

const trace = require('@1978io/trace');   // CommonJS
import trace from '@1978io/trace';        // Node ESM (via the CommonJS default export)

Usage

Bare — just the caller's name

function saveDraft() {
  trace();
}
saveDraft();
// Called: saveDraft

With values — anything you pass is appended

function loadUser(id) {
  trace(id);
}
loadUser(7);
// Called: loadUser 7

Arguments are passed straight through to console.log, so objects, arrays and DOM nodes stay inspectable in devtools rather than being stringified.

The trace({ myVar }) shorthand — names and values

This is the one worth remembering. Wrap your variables in an object literal and ES6 shorthand keeps the variable names alongside the values:

function applyDiscount(code) {
  const total = 42.5;
  const tier  = 'gold';
  trace({ code, total, tier });
}
applyDiscount('SPRING');
// Called: applyDiscount { code: 'SPRING', total: 42.5, tier: 'gold' }

Compare trace(code, total, tier)Called: applyDiscount SPRING 42.5 gold, where you have to remember which value is which. Three extra characters buys you self-labelling output.

Enabling it

trace() is off by default. Dropping the file into a project does nothing until you say otherwise — no surprise console spam, no "who added these logs".

trace.on();             // enable
trace.off();            // disable
trace.enabled = true;   // same thing, if you prefer the flag

The flag is read live, on every call. So on a deployed page you can open devtools and type:

trace.enabled = true

…and the very next call traces. No reload, no rebuild, no special build of the app.

Auto-enabling in development

Do it explicitly, from your environment — the same source of truth that already tells your app whether it's in development.

Server-rendered (PHP, and the same idea in any templating language):

<script src="/js/trace.js"></script>
<script>
  trace.enabled = <?= APP_ENV === 'development' ? 'true' : 'false' ?>;
</script>

Static / bundled — set it from a build flag or a config value you control:

import trace from '@1978io/trace';
trace.enabled = process.env.NODE_ENV !== 'production';
// or from your own config object, whatever shape it takes
trace.enabled = APP_CONFIG.debug === true;

🔴 Don't sniff the hostname

The tempting one-liner:

trace.enabled = location.hostname === 'localhost';   // ✗ don't

It looks equivalent. It isn't — and it fails silently, which is the worst way to fail.

The moment anyone works on the project at a custom local vhost (trace.test, myapp.local), via a container or VM hostname, over an IP on the office LAN, from a phone on 192.168.1.x, in a preview deploy, or on staging, the check quietly returns false. Tracing just… doesn't happen. There's no error and nothing in the console to explain it, so the developer concludes the library is broken, or worse, that the function they're tracing is never called. That's an afternoon lost to a one-line assumption.

Variants like hostname.includes('local') or a regex over .test/.dev domains are the same bug with more characters — they encode a guess about everyone's machine rather than a fact about the environment.

Your environment already knows whether it's development. Ask it.

Silent in production

If trace.enabled is false, the very first line of trace() returns. Nothing is logged, no Error is constructed, no stack is parsed — the cost is one property read.

That's the guarantee: a trace() left in shipped code is inert. It doesn't leak variable values into a user's console, doesn't show up in error-reporting noise, and doesn't need stripping at build time. If you'd rather it wasn't there at all, it's still just a line to delete — but nothing breaks if you forget.

How the name detection works

There's no supported API for "what function am I in". trace() constructs an Error, reads its .stack, and takes the frame above itself:

Error                                     ← V8 header line, filtered out
    at trace (trace.js:14:23)             ← [0]  trace itself
    at loadUser (app.js:31:3)             ← [1]  the caller — this is the one we want
    at handleSubmit (app.js:88:5)

A regex pulls the name out. Both major stack formats are handled:

Engine Frame format Parsed as
V8 — Chrome, Edge, Node, Deno at loadUser (app.js:31:3) loadUser
SpiderMonkey / JavaScriptCore — Firefox, Safari loadUser@app.js:31:3 loadUser

Method and constructor frames come through with their context — you'll see Widget.render, Object.method, and Foo for new Foo().

It's best-effort, and honest about it. When there's no name to find — an anonymous callback, an IIFE, a frame that's just a URL — you get (anon):

[1].forEach(function () {
  trace('nothing to name here');
});
// Called: (anon) nothing to name here

Two things worth knowing:

  • Minifiers rename functions. In a minified production bundle you'd see Called: n. Since tracing is a development tool that's off in production, this rarely bites — but don't build anything on the string.
  • Modern engines infer more names than you'd expect. const mystery = function () {} reports as mystery, because the assignment gives the function expression a name. Genuinely anonymous positions — callbacks passed inline, IIFEs — are the ones that fall back to (anon).

It never throws. The stack parsing and the log call are both wrapped: a missing .stack, an exotic engine format, or a custom sink that blows up all result in trace() returning quietly. A debugging tool that breaks the app it's debugging is worse than no tool at all.

Custom sink

Route output somewhere other than console.log:

trace.sink = function (label, name, ...rest) {
  logPanel.append(`${label} ${name} ${rest.join(' ')}`);
};

The sink receives exactly what console.log would have: 'Called:', the resolved name, then your arguments. Set it back to null to return to the console. demo.html uses a sink to mirror output onto the page while still logging normally.

Useful for piping traces into an on-screen panel while debugging on a phone, or into a test harness that asserts a function was reached.

API

trace(...args) Log Called: <name> plus any arguments. No-op when disabled.
trace.enabled boolean, default false. Read live on every call.
trace.on() / trace.off() Set the flag; both return trace so they chain.
trace.sink function or null (default). Receives output instead of console.log.
trace.version Version string.

Module formats

One file, three ways in:

<!-- Browser global -->
<script src="trace.js"></script>
<script>trace.on();</script>
// CommonJS
const trace = require('./trace.js');

// Node ESM — resolves through the CommonJS default export
import trace from './trace.js';

In a browser <script type="module">, import the file for its side effect and take the global — trace.js deliberately has no export statement so that a single file can serve every context:

import './trace.js';
const { trace } = globalThis;

Honestly

There is nothing here you couldn't write yourself in ten minutes. The Error().stack trick is well known and about as old as console.log. The value on offer is the packaging: the flag read live rather than captured at load, both stack formats handled, the try/catch that means it can't take your app down, an (anon) fallback instead of an exception, and a default of off so it's safe to leave in place.

It's free. Take it, copy it, rename it, ship it.

Licence

MIT — see LICENSE. Copyright © 2026 James Robinson / 1978.io.


A 1978.io project · github.com/1978io/trace

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages