Adding color to your tail

Tail is a very useful tool for monitoring an error stream. Sometimes the output from tail can have too much information, and its black-and-white monotone output can be hard to follow with the eyes.

The basic: a Linux terminal has the capability to colorize text. For example:

1
echo -e "Sample text nicely highlighted"

will produce a simple text saying, well:

Sample text nicely highlighted

But with some terminal color tagging such as:

1
echo -e "Sample \e[36mtext\e[0m nicely highlighted"

The \e above is identical to \033. I’d suggest using \033 because it’s safer in a programming language such as PHP. PHP will not recognize \e, but it will recognize \033, as you’ll see below.

1
echo -e "Sample \033[36mtext\033[0m nicely highlighted"

Sample text nicely highlighted

Some basic coloring tables can be seen at bashguru.com:

1
2
3
4
5
6
7
8
9
Color     Foreground  Background
Black     30          40
Red       31          41
Green     32          42
Yellow    33          43
Blue      34          44
Magenta   35          45
Cyan      36          46
White     37          47

With the help of perl, which most likely comes with all Linux distros, here’s a way to colorize your tail output. Let’s assume that every time you log something you will have a date-time prefixing your log. For example:

1
2
3
4
5
6
7
[22-Dec-2011 20:28:45] E_DATASOMETHING ......
Too much information .......
Too much information .......
[22-Dec-2011 20:28:46] E_FATAL Something
Too much information .......
Too much information .......
Too much information .......

Let’s create a script that can colorize the date portion. Create an executable Linux bash file:

1
2
3
touch logwatch.sh
chmod 755 logwatch.sh
vim logwatch.sh

Then copy and paste the following into your empty logwatch.sh:

1
2
3
#!/bin/bash
vNow=$(date +"%d-%b-%Y")
tail -f ~username/errorlogfile.txt | perl -pe "s/$vNow/\e[1;30;32m$&\e[0m/g"

Voilà!

[22-Dec-2011 20:28:45] E_DATASOMETHING ……
Too much information …….
Too much information …….
[22-Dec-2011 20:28:46] E_FATAL Something
Too much information …….
Too much information …….
Too much information …….

If you need something more complex — say you want to highlight several words in the file — you can use the scripting power of PHP. (I’m rusty with my perl.) Save the script below as colorize.php, and you can have the words blah, na, wa, and highlighted on the fly:

1
tail -f test.txt | php colorize.php blah na wa --
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
//stream_set_blocking(STDIN, FALSE);
$fp = fopen("php://stdin", "r");
// Failed to connect to STDIN? (shouldn't really happen)
if (!$fp) {
    echo "Cannot connect with standard input";
    exit();
}

array_shift($argv);
$replacements = array();
foreach ($argv as $arg) {
    $replacements[] = "\033[36m$arg\033[0m";
}

// Read each line as it arrives on STDIN
while ($line = fgets($fp)) {
    echo str_ireplace($argv, $replacements, $line);
}

// Close connection to STDIN
fclose($fp);
echo "done!";
?>

A few useful additions.

The simpler path: grep –color. If you only need to highlight a single pattern, you don’t need perl or PHP at all — grep can do it, and the –color=always flag forces color even through a pipe (where grep otherwise drops it because stdout isn’t a terminal):

1
tail -f errorlog.txt | grep --color=always -E "E_FATAL|E_ERROR|.*"

The trailing .* is the trick that makes this work as a highlighter rather than a filter — it matches every line, so nothing gets dropped, but the alternation patterns still get colored where they appear. The -E flag enables extended regex.

The line-buffering gotcha. When you chain tail -f through several filters, each program in the pipeline buffers its output by default. The result: nothing appears on screen for several seconds, then a wall of lines all at once — useless for live monitoring. Force line-buffering on each stage:

1
tail -f errorlog.txt | grep --line-buffered "ERROR" | sed -u 's/old/new/'

–line-buffered is the GNU grep flag; -u is the equivalent for sed; for awk, use fflush() after each print. If you don’t see live output through your pipeline, this is almost always why.

Purpose-built tools. If colorizing logs is something you do often, two tools save you from rolling your own:

  • ccze — pipe a log into it (tail -f /var/log/syslog | ccze -A) and it auto-colorizes by recognizing common log formats (syslog, Apache, Squid, etc.). The -A flag emits ANSI for piping; without it, ccze prefers its own curses-based UI.
  • multitail — like tail -f but with split panes for watching multiple files at once, plus per-pattern highlighting via a config file. Heavier but powerful when you’re staring at three log files at the same time.

Both are in the standard repos for Debian/Ubuntu/Fedora — apt install ccze multitail or equivalent.

Two reset bookkeeping notes. If your colored output starts “leaking” — i.e. text after a match keeps showing up colored — your reset escape (\e[0m) didn’t fire. Common causes: a trailing newline got stripped before the reset, or you forgot the reset entirely. Always pair \e[Xm with \e[0m. And if you ever end up in a terminal stuck in some weird color state, the command reset (or tput reset) puts everything back to defaults.

Posted in Linux, Operating System, Ubuntu | Comments Off on Adding color to your tail

JSON Jumpstart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * In JSON, curly brackets mark a new object ({ or })
 * Any variable inside the object — also known as the object's attribute — is
 * accessible using the dot syntax (object.attribute) or the bracket syntax
 * (object['attribute']).
 * Open your browser's DevTools console (F12 in any modern browser) to see
 * the output from console.log.
 */


// =========================
// JSON Object
// =========================
var var1 = {
    'var1_1':'value1_1',
    'var1_2':'value1_2'
};
console.log(var1);
// The line above will output> Object { var1_1="value1_1", var1_2="value1_2"}
console.log(var1.var1_1);
// The line above will output> value1_1
console.log(var1['var1_1']);
// The line above will output> value1_1

// =========================
// JSON Array
// =========================
// In JSON, square brackets mark an array ([ or ])
// Any array element is accessible by using a square bracket followed by an index (0,1,2,...)
var var2 = ['value2_1', 'value2_2'];
console.log(var2);
// The line above will output> ["value2_1", "value2_2"]
console.log(var2[0]);
// The line above will output> value2_1

var var3 = [{
        'var3_1':'value3_1',
        'var3_2':'value3_2'
    },{
        'var3_3':'value3_3',
        'var3_4':'value3_4'
    }];
console.log(var3);
// The line above will output> [Object { var3_1="value3_1", var3_2="value3_2"}, Object { var3_3="value3_3", var3_4="value3_4"}]
console.log(var3[0].var3_1);
// The line above will output> value3_1

A few useful additions.

JS objects vs. JSON — they look the same, but they aren’t. The examples above are JavaScript object literals, not strictly JSON. They render and behave the same way at the console, which is why the line between them blurs in tutorials, but there are real differences worth knowing:

  • JSON requires double-quoted string keys: “var1_1”, not ‘var1_1’.
  • JSON values can only be: string, number, boolean, null, object, or array. No undefined, no functions, no Date objects (they get serialized as strings).
  • JSON has no comments and no trailing commas.

If you write what looks like JSON in a .json file but use single quotes, your tool will reject it as invalid. The JS engine accepts both because it’s evaluating JavaScript syntax, not parsing JSON.

The actual JSON API. When you receive JSON from a network request and need to turn it into something you can manipulate — and vice versa — use the two builtins:

1
2
3
4
5
6
7
8
9
10
11
12
13
// JSON string -> JS object
var fromServer = '{"name":"Alice","age":30,"tags":["admin","user"]}';
var obj = JSON.parse(fromServer);
console.log(obj.name);   // "Alice"
console.log(obj.tags[0]); // "admin"

// JS object -> JSON string
var payload = { name: 'Bob', age: 25 };
var body = JSON.stringify(payload);
console.log(body);  // '{"name":"Bob","age":25}'

// Pretty-print with 2-space indent (handy for debugging)
console.log(JSON.stringify(payload, null, 2));

Reaching into deeply nested data. Modern JavaScript gives you a couple of nicer ways to dig into nested objects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var response = {
  user: {
    profile: { name: 'Alice', address: null }
  }
};

// Old way — verbose, and crashes if any link is missing
var name = response.user.profile.name;

// Optional chaining (?.) — returns undefined instead of throwing
// when an intermediate value is null/undefined
var city = response.user?.profile?.address?.city;  // undefined, no error

// Destructuring — pull multiple fields out at once
var { name, profile: { address } } = response.user;

Optional chaining (?.) is especially useful when consuming JSON from an API where some fields are optional — you avoid a tower of if (foo && foo.bar && foo.bar.baz) guards.

Validating JSON. If you’re not sure whether a string is valid JSON, wrap the parse in try/catch — JSON.parse throws a SyntaxError on malformed input:

1
2
3
4
5
6
7
8
function safeParse(str) {
  try {
    return JSON.parse(str);
  } catch (e) {
    console.warn('Not valid JSON:', e.message);
    return null;
  }
}

For one-off command-line validation, the jq tool (or just python3 -m json.tool) will tell you exactly where the error is in the input — much friendlier than the parser’s line/column number.

Posted in javascript, Web Development | Comments Off on JSON Jumpstart

Vodafone Group, AT&T, and Verizon are The Biggest Yielding Companies In November 2011

Vodafone Group, the telecom giant known for its land line and wireless services around the U.S and U.K, together with two other U.S based telecom giants AT&T and Verizon are one of the best yielding companies in Nov 2011. As per today’s data, 2011-11-02, Vodafone is yielding 6.8% yearly, AT&T 5.9% and Verizon is 5.3%. Is this a good time to invest in these telecom companies? You might also want to see the list of stocks with the highest yield displayed in www.greenandredmarket.com‘s highest yield page.

Posted in Investment, Stock Market | Comments Off on Vodafone Group, AT&T, and Verizon are The Biggest Yielding Companies In November 2011

Biggest Companies in the USA (Top 30)

If you ever get interested with the top 30 biggest companies in the USA you can visit this link:
http://www.greenandredmarket.com/menu/biggestcompanies.htm
I’m really surprised that Apple‘s Market Capitalization is almost twice bigger than Microsoft

Posted in Investment, Stock Market | Comments Off on Biggest Companies in the USA (Top 30)

An nmap primer: scanning ports, fingerprinting hosts, and staying legal

If you just want a quick look at what’s listening on a Linux machine, the one-liner is:

1
nmap -sS -O 127.0.0.1

That’s an nmap SYN scan with OS detection against your own loopback interface. If you’ve never used nmap before, that command is a fine first step — but the tool can do a lot more than that, and a little context helps.


What nmap actually is, and how to use it without being scary.

Nmap (“Network Mapper”) is the de facto open-source port scanner and network-discovery tool. It’s been around since 1997, ships in every major Linux distro’s package manager, and is the same thing security teams, sysadmins, and CTF players reach for when they need to know what’s running on a host.

At its simplest, you give nmap a target and it tells you which TCP/UDP ports are open, what services are likely behind them, and (with a bit of coaxing) what OS is on the other end.

The flags in the original command

  • -sS — “SYN scan” (also called “half-open” or “stealth” scan). Nmap sends a TCP SYN packet, watches for the SYN/ACK reply, and then sends RST instead of completing the handshake. It’s faster than a full TCP connect and slightly less noisy in logs. Requires root (or CAP_NET_RAW) because it crafts raw packets.
  • -O — OS fingerprinting. Nmap sends a series of probes and compares the responses against its database to guess the operating system. Also requires root.

If you don’t have root, drop -sS and -O and use the default TCP connect scan, which works as a regular user:

1
nmap 127.0.0.1

A handful of nmap recipes worth knowing

Scan a whole subnet. Useful for figuring out what’s on your home network:

1
nmap -sn 192.168.1.0/24

-sn is “ping scan” — discover hosts without scanning ports. Quick way to map out who’s alive on a LAN.

Scan specific ports. By default nmap scans the 1000 most common ports. Sometimes you want a specific range or a full sweep:

1
2
3
nmap -p 22,80,443 example.com         # specific ports
nmap -p 1-65535 example.com           # all 65k ports
nmap -p- example.com                  # shorthand for the same

Service and version detection. Beyond just “port 80 is open,” find out it’s nginx 1.18.0:

1
nmap -sV example.com

The kitchen-sink scan. A common starting point in CTFs and engagements:

1
nmap -sC -sV -O -p- 10.10.10.5

-sC runs the default NSE (Nmap Scripting Engine) script set, which probes for common misconfigurations and grabs banners; -sV does service-version detection; -O guesses the OS; -p- covers all ports. This takes a while but gives you the most information per invocation.

Save output for later. When the scan takes 20 minutes, you don’t want to scroll back through your terminal:

1
nmap -sC -sV -oA myscan example.com

-oA myscan writes three files: myscan.nmap (human-readable), myscan.gnmap (grep-friendly), and myscan.xml (for tools that consume nmap output programmatically).

Local alternatives when you only care about your own machine

If the question is “what’s listening on this machine” — not “what does the network see” — nmap is overkill. Two builtins are usually faster:

1
2
ss -tulpn         # listening TCP/UDP sockets with PIDs (modern, replaces netstat)
lsof -i -P -n     # everything with an open network file descriptor

The big difference: ss and lsof read the kernel’s socket table directly and tell you exactly what’s listening, including the process name and PID. nmap actively probes from outside, which can miss services bound to 127.0.0.1 only (well — not for a localhost scan, but you get the idea), and finds whatever a real attacker would see. Both viewpoints are useful; they answer slightly different questions.

One important caveat — only scan what you’re allowed to scan

Port scanning machines you don’t own or have explicit permission to test is a gray area at best and a crime at worst, depending on jurisdiction. The Computer Fraud and Abuse Act in the US, the Computer Misuse Act in the UK, and similar laws elsewhere have all been used against unauthorized scans, even “harmless” ones.

Safe targets for learning nmap:

  • 127.0.0.1 and your own machine’s IPs.
  • Your own home network (your router and the devices on it).
  • scanme.nmap.org — a host the nmap project explicitly puts up for people to practice scans against.
  • CTF platforms (Hack The Box, TryHackMe) — purpose-built target environments where scanning is the expected behavior.
  • Customer or employer infrastructure only when you have written authorization for a security assessment.

For everything else, assume “don’t scan it” is the right answer. The technical capability is the easy part; the social/legal part is what separates a security professional from someone explaining themselves to a lawyer.

Posted in Linux, Operating System | Comments Off on An nmap primer: scanning ports, fingerprinting hosts, and staying legal

Find Files in Linux by Name

To find files in Linux with a certain name portion or pattern we can issue a locate command. If locate is unable to find a matching file, then you might need to update your file index database.

1
2
# locate journal-2011-07
# updatedb

In the example above we are trying to find a file that has journal-2011-07 in its name which can be journal-2011-07-01, journal-2011-07-02, my-journal-2011-07-09, etc.

You can also use the find command. The find command does not use a file indexing algorithm which might be slower than locate. The example below will look for all files with the xml extension inside /home/ronald. The find command by default is recursive — it will look into subfolders.

1
find /home/ronald/ -name *.xml

2026 update — a few useful additions. 🔍

Always quote the pattern. The example above works, but it has a subtle gotcha: *.xml is unquoted, so your shell tries to expand it against files in your current directory before find ever sees it. If there happens to be one matching file in the current directory, find ends up looking for that exact filename instead of the pattern. Quote it to be safe:

1
find /home/ronald/ -name "*.xml"

Case-insensitive search with -iname — handy when you don’t know if the file is Report.PDF or report.pdf:

1
find /home/ronald/ -iname "*.pdf"

Find by size — for example, all files larger than 100 MB:

1
find /home/ronald/ -type f -size +100M

Find recently modified files — anything touched in the last 24 hours:

1
find /home/ronald/ -type f -mtime -1

Use -mtime +7 for files older than 7 days — -n means “less than n days”, +n means “more than n days”.

What about locate in 2026? The classic locate / updatedb from 2011 has largely been replaced by mlocate or, on newer distros, plocate. The commands work the same way — locate journal-2011-07 still does what you’d expect — but on a fresh install you may need to install the package first (e.g. sudo apt install plocate). The index is usually refreshed nightly by a cron/systemd job, so manual updatedb calls are rarely needed anymore.

A friendlier alternative: fd. If you find find‘s syntax fiddly, fd is a modern replacement with sensible defaults (recursive, smart-case, respects .gitignore). The same XML search becomes:

1
fd -e xml . /home/ronald

Pick whichever fits your 🧠 brain muscle memory — find is universally available, fd is nicer to type. 💡

Posted in Linux | Comments Off on Find Files in Linux by Name

Check For Loaded PHP Extensions

Sometimes you want to make sure a certain PHP extension is loaded or not. These extensions can be curl, xmlrpc, or ldap. After all, when you move your application from one server to another the configuration is not necessarily the same. So here’s how to check for loaded extensions using get_loaded_extensions().

1
2
3
4
5
6
7
8
9
10
$extNeeded = array('ldap', 'mysql', 'xmlrpc', 'ron_ext');
$loadedExtension = get_loaded_extensions();
foreach ($extNeeded as $ext) {
    if (!in_array($ext, $loadedExtension)) {
        echo 'The application cannot start properly, '.$ext.' extension is missing.
        The following extensions are needed: '
.implode(', ', $extNeeded).'.
        Terminating application ...'
;
        die;
    }
}

A few useful additions.

For checking just one extension, extension_loaded() is more direct than scanning the array returned by get_loaded_extensions():

1
2
3
if (!extension_loaded('curl')) {
    die('curl extension is required.');
}

It’s a one-liner, doesn’t allocate the full extension list, and reads a little nicer at call sites. Use get_loaded_extensions() when you genuinely need to iterate over many at once (like the original example does), and extension_loaded() for single checks.

You can also collect all the missing ones into a single message instead of bailing on the first one — better feedback for the operator:

1
2
3
4
5
6
$required = ['curl', 'ldap', 'mbstring', 'pdo_mysql'];
$missing  = array_filter($required, fn($ext) => !extension_loaded($ext));

if (!empty($missing)) {
    die('Missing required PHP extensions: ' . implode(', ', $missing));
}

If you’d rather not write any PHP at all, the command line gives you the same answer:

1
2
3
php -m | grep -i curl
php -m              # full list of compiled-in + loaded modules
php --ri curl       # detailed runtime info for one extension (version, settings)

php -m shows what your CLI php binary has loaded; if you’re chasing a problem that only shows up under the web server, you’ll want to run phpinfo() from a web request instead — the CLI and FPM SAPIs load different php.ini files and can have different extensions.

One note on the original example: two of the four extensions listed (mysql and xmlrpc) are no longer part of modern PHP. The mysql extension was removed in PHP 7.0 — use mysqli or pdo_mysql instead. xmlrpc was unbundled in PHP 8.0 and is effectively unmaintained; if you still need XML-RPC, the php-xmlrpc PECL package is technically available, but most projects move to JSON-RPC or REST these days. The check pattern in the post is still correct — it’s just that the specific extension names you’d put in $extNeeded have evolved.

Posted in PHP, Web Development | Comments Off on Check For Loaded PHP Extensions

Extending an Existing Javascript Function

This is how you extend an existing javascript function. Assuming that you want to preserve the existing function and the “added algorithm” is somewhat only needed to be executed based on a certain environment only.

1
2
3
4
5
6
7
8
9
10
11
12
function abc(){
  alert('abc is called');
}
var abcOrig = abc;
abc = function(){
  abcOrig();
  alert('abc has been extended!');
}
alert('test 1');
abc();
alert('test 2');
abc();

2026 update — the technique has a name, and a couple of gotchas. 🐛

What this pattern is called is monkey-patching — you save a reference to the original function, then replace it with a wrapper that calls the original plus your extra logic. It’s still useful, especially when you need to extend a third-party function you can’t edit.

Gotcha 1: it won’t work in module / strict scopes if you use const. The 2011 example uses var and an unscoped function abc(), so reassigning the global abc is fine. In a modern ES module — or anywhere you’ve declared the function with const or let — you can’t reassign it. Patch a property on an object instead:

1
2
3
4
5
6
7
8
9
10
11
12
13
const api = {
  fetchUser(id) {
    console.log('fetching', id);
  }
};

const original = api.fetchUser;
api.fetchUser = function (...args) {
  console.log('before');
  const result = original.apply(this, args);
  console.log('after');
  return result;
};

Gotcha 2: the original example silently drops arguments, this, and the return value. The line abcOrig(); calls the original with no arguments, no this binding, and throws away whatever it returns. That’s fine for an alert, but breaks the moment you patch a real function. The robust version forwards everything:

1
2
const result = original.apply(this, args);
return result;

Or as a one-liner using rest/spread: return original.call(this, …args);

Gotcha 3: you only get one shot per scope. If two pieces of code both monkey-patch the same function, the second one wraps the first — the order matters, and untangling it later is painful. For anything beyond a quick local override, prefer composition (a wrapper function with a different name) or class inheritance with super.

Use monkey-patching sparingly — it’s a sharp tool. But when you need to slip in an extra console.log on a function you don’t own, this is still the move. 💡

Posted in javascript, Web Development | Comments Off on Extending an Existing Javascript Function

Map Network Drive using DOS command

Create a batch file named mapdrive.bat and put the following inside it:

1
2
3
4
REM Map network drives
net use j: \\server1\john_music
net use p: \\server2\pdfs
pause

Explanation:

  • Line 1 is a comment.
  • Line 2 (and similarly line 3) maps the network share \\server1\john_music to drive J:.
  • Line 4 pauses the DOS window so it doesn’t close automatically.

A few useful additions.

If you want the mapping to survive a reboot, add the /persistent:yes flag. Without it, the mapping is gone the next time you log in:

1
net use j: \\server1\john_music /persistent:yes

If the share requires credentials, pass them with /user: — the password is prompted for if you omit it (which is the safer habit; embedding it in a batch file leaves it readable to anyone with access):

1
net use j: \\server1\john_music /user:DOMAIN\jdoe

To remove a mapping later:

1
2
net use j: /delete
net use *  /delete /yes        REM remove all mappings without prompting

And to see what’s currently mapped:

1
net use

The PowerShell equivalent. If you’re on a modern Windows and want a more scriptable approach, New-PSDrive covers the same ground:

1
2
New-PSDrive -Name J -PSProvider FileSystem `
  -Root \\server1\john_music -Persist

The -Persist flag is the equivalent of net use … /persistent:yes. Note that New-PSDrive without -Persist creates a mapping that’s only visible to the current PowerShell session, which is sometimes exactly what you want for a script that shouldn’t leave drive letters lying around afterward.

Posted in DOS | Comments Off on Map Network Drive using DOS command

Super Powerful Debugging Function in Javascript

Super powerful debugging function in JavaScript. This function will output your variable in question to the Firebug console or the HTML page. With it you can debug anything from an Object, array, string, integer, etc. It is also recursive-safe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
 * @author: Ronald Pringadi, 2011
 * @desc: to debug a JavaScript Object
 * @usage: debug(yourObject);  
 * or debug(yourString);
 * or debug(yourArray);  
 * @param object vTraceObject
 * @param string vPadding
 * @param object vPadding
 *
 */


function debug(vTraceObject, vPadding, vPreviousTraceObject) {
    'use strict';

    function getObjectClass(vObj) {

        if (vObj && vObj.constructor && vObj.constructor.toString) {
            var arr = vObj.constructor.toString().match(/function\s*(\w+)/);
            if (arr && arr.length === 2) {
                return arr[1];
            }
        }
        return undefined;
    }
    function typeOf(vObj) {

        var vResult = typeof vObj;
        if (vResult === 'object') {
            try {
                if (vObj.length !== undefined) {
                    vResult = 'array';
                }
            } catch (e) {
            }
        }

        return vResult;
    }  
    function print(vPadding, vType, vValue) {

        if (vPadding === undefined) {
            vPadding = '';
        }
        if (vType === undefined) {
            vType = '';
        }
        if (vValue === undefined) {
            vValue = '';
        }
        try{
            console.log(vPadding + ' (' + vType + '): ' + vValue + "\n");
        }catch(e){
            document.write(vPadding + ' (' + vType + '): ' + vValue + "\n");
        }
       
    }  

    if (vPadding === '' || vPadding === undefined) {
        vPadding = '';
    }
    if (typeOf(vTraceObject) === 'object'
            && typeOf(vPreviousTraceObject) === 'object') {
        if (getObjectClass(vPreviousTraceObject) === getObjectClass(vTraceObject)) {
            print(vPadding, 'ERROR', 'RECURSION EXISTING ...');
            return null;
        }
    }

    var vType = typeOf(vTraceObject);
    if (vType === 'object' || vType === 'array') {
        if (vPadding === '') {
            print(vPadding, vType, '...');
            vPadding = '----';

        }
        try {
            var vTempType = '';
            var variable = '';
            for (variable in vTraceObject) {
                vTempType = typeOf(vTraceObject[variable]);
                if (vTempType === 'object' || vTempType === 'array') {
                    print(vPadding, vTempType, variable);
                    debug(vTraceObject[variable], vPadding + '----',
                            vTraceObject);
                } else {
                    print(vPadding, vTempType, variable + ' : '
                            + vTraceObject[variable]);
                }
            }
        } catch (e) {
            print(vPadding, 'ERR', 'EXCEPTION');
        }
    } else {
        print(vPadding, vType, vTraceObject);

    }


}

function objA() {
    this.varA1 = 'a1';
    this.varA2 = 'a2';
    this.myCars = new Array();
    this.myCars[0] = "Saab";
    this.myCars[1] = "Volvo";
    this.myCars[2] = "BMW";
}

function objB() {
    this.varB1 = 'b1';
    this.varB2 = 'b2';
    this.varB3 = new objA;

}
instanceB = new objB;

Output:

1
2
3
4
5
6
7
8
9
10
(object): ...
---- (string): varB1 : b1
---- (string): varB2 : b2
---- (object): varB3
-------- (string): varA1 : a1
-------- (string): varA2 : a2
-------- (array): myCars
------------ (string): 0 : Saab
------------ (string): 1 : Volvo
------------ (string): 2 : BMW

A note from later — what the modern browser console gives you for free.

When this post was written, Firebug (the Firefox extension) was where serious JavaScript debugging happened, and a recursion-safe pretty-printer like the one above filled a real gap. Firebug was discontinued in 2017 once Firefox’s built-in DevTools caught up, and the modern browser console can do most of what this function does without any helper code. 🦊

A few one-liners that cover the common cases:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const car = { make: 'Saab', model: '900', year: 1995 };

// 1. Object shorthand — Chrome/Firefox/Safari all label the value with its name.
console.log({ car });
//   { car: { make: 'Saab', model: '900', year: 1995 } }

// 2. console.dir — walks the object as an interactive, expandable tree.
console.dir(car, { depth: null });

// 3. console.table — best when you have an array of similar objects.
console.table([
  { make: 'Saab', year: 1995 },
  { make: 'Volvo', year: 2001 },
  { make: 'BMW',   year: 2010 },
]);

// 4. JSON.stringify with indentation — copy-pasteable, great for logs and bug reports.
console.log(JSON.stringify(car, null, 2));

Each of those has its niche: { var } shorthand is what you reach for inside a function ten times a day, console.dir is for poking at DOM nodes and large objects interactively, console.table turns arrays into actual tables (a small everyday joy 🎉), and JSON.stringify(obj, null, 2) is what you want when the output needs to land in a file or a chat message.

One thing the modern console doesn’t automatically protect you from is circular references — JSON.stringify throws on them. If you need a copy-pasteable dump that survives cycles, you still need a helper. A modern, smaller version of the same idea as the original function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function safeStringify(obj, indent = 2) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) return '[Circular]';
      seen.add(value);
    }
    return value;
  }, indent);
}

const a = { name: 'a' };
const b = { name: 'b', ref: a };
a.ref = b; // circular!

console.log(safeStringify(a));
// {
//   "name": "a",
//   "ref": {
//     "name": "b",
//     "ref": "[Circular]"
//   }
// }

So the original function is no longer the tool of choice in 2024+, but the idea behind it — “give me a recursion-safe, copy-pasteable dump of anything” — is just as useful today. The modern toolbox just hands you most of it in two lines instead of fifty. 🛠️

Posted in javascript, Web Development | Comments Off on Super Powerful Debugging Function in Javascript