To use Flowgger, you first need to define the flow of your application or an endpoint in .stflow files. A .stflow follows the syntax defined by text2obj node js library. Text2Chart VS code plugin can help you to visualize the flow using flow chart. A flow defined in .stflow, is used by Flowgger to replace static log statements with steps id. This helps to reduce the log size drastically.
Sample flow
FLOW: Binary Search
LOOP searching for target in array
read low (initial index of array)
read high (last index of array)
IF low <= high
THEN calculate mid ((low + high) / 2)
IF array[mid] = target
found target at mid
END
ELSE
FOLLOW update boundaries
ELSE
ERR Target not found
END
FLOW: update boundaries
IF array[mid] < target
update low to mid + 1
ELSE
update high to mid - 1
Branching (LOOP, IF, ELSE, ELSE_IF) and termination steps (END, STOP) should not be logged. Only actions taken within a step should be logged.
flog.info("found target at mid");While logging you have to exclude words "THEN", "ERR". These are used in algorithm for understanding purpose. Additionally, extra info in a logging step has to be avoided. E.g. "read low (initial index of array)" should be logged as below
flog.info("read low");Appenders help Flowgger to write logs to a desire streams. Flowgger provides some basic appenders. You can use log4js library for more appenders.
Path of the .stflow and appenders need to be configured. First let's understand how appenders can be created.
- Creating Log4js appenders
import log4js from 'log4js';
import Log4jsAdapter from '@solothought/flowgger/Log4jsAdapter';
log4js.configure({
appenders: {
error: { type: 'console' }
},
categories: {
error: { appenders: ['error'], level: 'error' },
default: { appenders: ['error'], level: 'error' }
}
});
const log4jsErrAppender = new Log4jsAdapter(log4js.getLogger("error"));- Using Flowgger provided basic appenders
import {ConsoleAppender} from '@solothought/flowgger/appenders';- Creating your own custom appenders
class CustomAppender{
construct(){
this.streamData = [];
this.layout;
}
append(logRecord, level){
if(typeof this.layout === "function"){
this.streamData.push([level, this.layout(logRecord, level)]);
}else{
this.streamData.push([level, this.layout[level](logRecord)]);
}
}
}
const flowsAppender = new CustomAppender();
const headAppender = new CustomAppender();
const dataAppender = new CustomAppender();There are four type of logs. Flowgger expects you to define the appenders for each type:
| Log Type | Purpose |
|---|---|
| Flows | Logs the sequence of steps in a flow once a flow is completed or failed. info log level is used for this |
| Head | Acknowledges when a flow starts. Since flows remain in memory until completion, there is a risk of losing flows if the application crashes unexpectedly. Logging the acknowledgment ensures traceability. trace log level used for this |
| Data | Debug and Warn level logs are categorized as data logs. These allow users to log extra information during a flow |
| Error | Error level logs are categorizes as error logs. You should use this when application takes unexpected path which is not defined in .stflow. |
| Note: If an appender is not defined for a specific type, logging for that type will be skipped. |
Filters: Logging is enabled by default for all the appenders. You can configure an appender to log for particular flow or particular type of logs using notFor and onlyFor properties. Name of the flow must match with the flow name defined in .stflow. If filters are not defined, all the flows for all the types would logged.
Configure Flowgger to use appenders for each flow type.
const config = {
"appenders": [
{
handler: flowsAppender,
layout: lr => { // can be a function or object
return `${lr.success},${lr.id},${lr.reportTime},${lr.flowName},${lr.steps}`;
},
onlyFor: {
types: ["flows"],
flows: ["second flow(2)", "first flow"]
}
},
{
handler: headAppender,
layout: new CsvLayout(),
onlyFor: {
types: ["head"],
flows: ["second flow(2)", "first flow"]
}
},
{
handler: dataAppender,
// layout: logRecord => logRecord, // default
onlyFor: {
types: ["data", "error"],
flows: ["second flow(2)", "first flow"]
}
},
{
handler: log4jsErrAppender,
// layout: logRecord => logRecord, // default
onlyFor: {
types: ["error"],
flows: ["second flow(1)"]
}
}
],
flow: {
source: path.resolve("./tests/flows"),
// maxIdleTime: 200, // time difference between two consecutive steps
}
};This is the sample of how you can integrate Flowgger to your code
import Flowgger from '@solothought/flowgger';
//flowgger should be created once and on application level
const flowgger = new Flowgger(config)
// Each endpoint must use Flowgger instance to get a logger instance using 'init' before stating logging
// Flow name passed in 'init' must match to one flow defined in 'stflow' files.
const binFlow = flowgger.init("Binary Search");
// All consecutive code should use non-branching, non-leaving steps from the flow
// using 'info' method
binFlow.info("read low");
//order of the steps in code is most important
binFlow.info("read high");
//You can log additional data using 'debug', 'error', 'warn' methods
binFlow.debug("values", {low: lowVal, high: highVal});
// It is recommended to call 'end' when the flow ends expectedly or divert to unexpected path
binFlow.error("NULL POINTER EXCEPTION", e.stackTrace())
binFlow.end()