Skip to content
Grant Carthew edited this page Oct 25, 2024 · 33 revisions

perj JSON Format

JavaScript Object Notation or JSON is a lightweight data-interchange format. It serializes JavaScript Objects into strings for network transmission or long term storage.

The perj logger converts log arguments into a standard JSON log format and sends the JSON string out to standard output by default. The JSON format is compatible with ndjson.

This document explains the JSON structure used for the perj logger and why the format was chosen.

Default Log Format

The perj package is not designed to be used straight out of the box. In fact it is missing some important standard log values in the output if you use it without customization.

Here is an example of using the perj logger with no options supplied:

import Perj from 'perj'
const log = new Perj()

log.info('string message', { simple: 'object' })
/*
The following string is sent to standard out:

{"level":"info","lvl":30,"time":1526383932101,"msg":"string message","data":{"simple":"object"}}
*/

Here is the above JSON string in an easy format for analysis:

{
   "level":"info",
   "lvl":30,
   "time":1526383932101,
   "msg":"string message",
   "data":{
      "simple":"object"
   }
}

As you can see above there are five default top level keys or properties in the JSON object. Both the level and lvl keys have the ability to be disabled. The other three keys; time, msg, and data are required.

You don't have to keep the default string labels for the keys. You can change every key in perj by using the Options object during logger instantiation.

level Key

The level key is used to indicate the level of the message. The default levels are:

Level Number Usage
fatal 60 The application can no longer execute.
error 50 Something critical went wrong.
warn 40 Something went wrong.
info 30 A normal log event. Useful for following the application process.
debug 20 Noisy loop messages or debugging information.
trace 10 Every step the application takes.

Using the level option you can control how noisy the logger is by suppressing lower numbered log messages.

Each one of these levels has a corresponding level function such as log.warn() that you can call to produce a specific level JSON output.

During creation of the logger object you can change the log levels to any names and numbers you like. See the levels document for more detail.

See the levelKey option if you would like to change the name of the key.

See the levelKeyEnabled options if you would like to disable this key.

lvl Key

The lvl key holds the log level corresponding number as you can see from the table above. This number is used internally by perj to determine which level log messages should be written out.

Some logging packages only provide a string name of the log level in the log messages. Some logging packages only provide the level number. For the sake of 9 bytes of output, perj includes both the level name and number in the JSON output By default. You can disable either of the keys.

See the levelNumberKey option if you would like to change the name of the key.

See the levelNumberKeyEnabled option if you would like to disable this key.

time Key

The time key holds a value representing the date and time the log message was written. By default perj uses epoch time or the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.

This can easily be changed by using the dateTimeFunction option.

See the dateTimeKey option if you would like to change the name of the key.

This key cannot be disabled.

msg Key

The msg key holds the first string argument passed into the log function. The first string logic is based on the following:

  1. First string argument found such as log.info('first').
  2. First Error message found such as log.error(new Error('first')).
  3. Whichever of the above is first eg: log.error('first', new Error('second')).

This means the msg key will only ever be a string value. Any extra string arguments will be added to the data key.

If you log two messages log.info('one', 'two'), the msg key will hold the first 'one' while the data key will hold the second one 'two'.

If you log three or more messages log.info('one', 'two', 'three'), the msg key will hold 'one' while the data key will hold an array ['two', 'three'].

See the messageKey option if you would like to change the name of the key.

This key cannot be disabled.

data Key

One of the primary reasons perj was created was to nest extra messages and data under a top level key. By default this key is called data.

If you supply more than one string as arguments to the log function the extra strings will be nested under the data key.

Any object you supply as arguments to the log function will be nested under the data key.

If there is more than one item, being either string or object, that needs to be nested under the data key, it is nested as an Array.

See the dataKey option if you would like to change the name of the key.

This key cannot be disabled.

error Key

This key was added in v3.0.0 of the perj logger to make it easy to query for error log entries.

This is a special top level key that will only be present in the log output if an Error object is logged. The error key will only ever be set to true. If there are no error objects logged the key will not exist. It does not matter if you use log.error(), log.info(), or any other level to log your message. If an error object exists, the top level error key will be set to true.

Here is an example of an error being logged showing the error key:

import Perj from 'perj'
const log = new Perj()
const err = new Error('sample error')

log.info(err)
/*
The following string is sent to standard out:

{"level":"info","lvl":30,"time":1535076243098,"msg":"sample error","data":{"stack":"Error: sample error\n    at repl:1:9\n    at Script.runInThisContext (vm.js:90:20)\n    at REPLServer.defaultEval (repl.js:323:29)\n    at bound (domain.js:396:14)\n    at REPLServer.runBound [as eval] (domain.js:409:12)\n    at REPLServer.onLine (repl.js:621:10)\n    at REPLServer.emit (events.js:187:15)\n    at REPLServer.EventEmitter.emit (domain.js:442:20)\n    at REPLServer.Interface._onLine (readline.js:290:10)\n    at REPLServer.Interface._line (readline.js:638:8)","message":"sample error","name":"Error"},"error":true}
*/

Here is the above JSON string in an easy format for analysis:

{
  "level": "info",
  "lvl": 30,
  "time": 1535076243098,
  "msg": "sample error",
  "data": {
    "stack": "Error: sample error\n    at repl:1:9\n    at Script.runInThisContext (vm.js:90:20)\n    at REPLServer.defaultEval (repl.js:323:29)\n    at bound (domain.js:396:14)\n    at REPLServer.runBound [as eval] (domain.js:409:12)\n    at REPLServer.onLine (repl.js:621:10)\n    at REPLServer.emit (events.js:187:15)\n    at REPLServer.EventEmitter.emit (domain.js:442:20)\n    at REPLServer.Interface._onLine (readline.js:290:10)\n    at REPLServer.Interface._line (readline.js:638:8)",
    "message": "sample error",
    "name": "Error"
  },
  "error": true
}

This key can not be disabled.

Key Suggestions

As stated in the primary goals of the perj package, it is designed to be integrated rather than used out of the box. With that in mind the required top level properties were kept to a minimum.

By default every log message will have a level, lvl, time, msg, and data top level property. You can disable either the level key or the lvl key. You could disable both. See the Options for more details.

You can add top level properties or keys easily. See the Top Level Properties document for more detail.

Depending on your platform, Node.js or browser, you may want to add any of the following to your logging module:

version Key

You could name the version key something other than the full name such as ver or simply v.

Set the version key to a number which represents the JSON format version. This is for your own use so you can change the format of your JSON in the future and know which version the messages are using.

const log = new Perj({ ver: 1 })

name Key

Add a name, module or label key to your JSON messages so you can identify the micro-service or module that is writing a log message.

If using Node.js there is a global module object that you can use to extract the file name the code is being executed from. Here is an example of using perj within a module to add a module top level property:

import { basename } from 'path'
const moduleName = basename(import.meta.url, 'js')
const log = new Perj({ module: moduleName })

It would be recommended to expand on this to create your own logger module to easily use within your other modules. See the Examples document for ideas on this.

process Key

A process or pid can be handy when debugging if you are using Node.js.

const log = new Perj({ pid: process.pid })

hostname Key

If you have a distributed application or micro-service a hostname or host top level property can be useful.

// Use one of the following
// Node.js
import { hostname } from 'os'
// Browser
const host = location.hostname
const log = new Perj({ host })

session Key

Adding a session key to your JSON log messages will allow you to debug based around a specific session. In a micro-services environment this is a requirement.

If you can't use the browser session or other related session values across your modules, consider adding a custom unique session identifier and adding it to your data store.

Some examples of unique id generators include ulid, cuid, the classic uuid, or you could even use ObjectId. There are many more.

location Related Keys

Without listing any specific examples here, if you are logging from the browser the Location interface has some values that could be of use.

Hit F12 and type location in the console and hit enter to inspect it.

Clone this wiki locally