-
Notifications
You must be signed in to change notification settings - Fork 1
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.
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.
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.
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.
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.
The msg
key holds the first string argument passed into the log function. The first string logic is based on the following:
- First string argument found such as
log.info('first')
. - First
Error
message found such aslog.error(new Error('first'))
. - 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.
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.
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.
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:
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 })
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.
A process
or pid
can be handy when debugging if you are using Node.js.
const log = new Perj({ pid: process.pid })
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 })
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.
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.