Skip to content

Instantly share code, notes, and snippets.

@diegofcornejo
Last active April 15, 2025 09:31
Show Gist options
  • Save diegofcornejo/7b94a0f384f916e58a44e7618e6e57e2 to your computer and use it in GitHub Desktop.
Save diegofcornejo/7b94a0f384f916e58a44e7618e6e57e2 to your computer and use it in GitHub Desktop.
Grafana Dashboard for Pino HTTP Logs - Express JS example

Pino HTTP Logs Dashboard
This Grafana dashboard utilizes the Loki data source to visualize and analyze logs generated by the Pino logging library. Logs from HTTP requests are captured using the Pino-HTTP plugin and are sent to a Loki instance via the Pino-Loki plugin, enabling real-time monitoring and historical analysis. The dashboard includes graph and table panels to display key metrics such as request status codes, response times, and error rates, making it easy to track application behavior and identify performance issues.

Flow Overview:

  1. Pino: Captures structured logs in a JSON format.
  2. Pino-HTTP: Specifically designed to handle HTTP request/response logging, capturing details such as request method, URL, and status code.
  3. Pino-Loki: Transmits the structured logs to a Loki instance, which acts as the centralized log storage and query engine.

To explore and use the published dashboard, visit:
https://grafana.com/grafana/dashboards/21900-pino-http-logs/

config:
target: "http://localhost:4000"
phases:
- duration: 180
arrivalRate: 10
rampTo: 20
name: Slow start
- duration: 120
arrivalRate: 20
rampTo: 40
name: Traffic increase
- duration: 120
arrivalRate: 40
rampTo: 60
name: Traffic peak
- duration: 180
arrivalRate: 50
rampTo: 20
name: Traffic reduction
- duration: 180
arrivalRate: 20
rampTo: 10
name: Traffic drop
- duration: 120
arrivalRate: 10
rampTo: 30
name: Final recovery
scenarios:
- flow:
- post:
url: "/log"
json:
message: "Test log"
level: "info"
- post:
url: "/create"
json:
username: "testuser"
password: "password123"
- get:
url: "/unauthorized"
- get:
url: "/not-found"
- get:
url: "/error"
APP_NAME=my-app
NODE_ENV=production
LOKI_HOST=http://<ip-or-domain>:<port>
LOKI_USERNAME=<username>
LOKI_PASSWORD=<password>
DATADOG_API_KEY=<api-key>
DATADOG_SITE=datadoghq.com
LOG_LEVEL=trace
LOG_LEVEL_CONSOLE=debug
LOG_LEVEL_HTTP=info
process.loadEnvFile();
import express from 'express';
import pinoHttp from 'pino-http';
import logger from './logger.js';
const app = express();
const port = process.env.APP_PORT || 3000;
app.use(express.json());
app.use(pinoHttp({
logger,
serializers: {
req: (req) => {
req.body = req.raw.body;
return req;
},
}
}));
app.post('/log', (req, res) => {
logger.trace('LOG TRACE');
logger.debug('LOG DEBUG');
logger.info('LOG INFO');
logger.warn('LOG WARN');
logger.error('LOG ERROR');
logger.fatal('LOG FATAL');
logger.info(req.body);
res.status(200).json(req.body);
});
app.post('/create', (req, res) => {
logger.info('Create user');
logger.info(req.body);
res.status(201).json(req.body);
});
app.get('/unauthorized', (req, res) => {
logger.warn('Unauthorized access!');
res.status(401).send('Unauthorized!');
});
app.get('/not-found', (req, res) => {
logger.warn('Not found!');
res.status(404).send('Not found!');
});
app.get('/error', (req, res) => {
logger.error('Something went wrong!');
res.status(500).send('Something went wrong!');
});
app.listen(port, () => {
logger.info(`Server started on port ${port}`);
});
process.loadEnvFile();
import pino from 'pino';
const transport = pino.transport({
targets: [
{
target: "pino-loki",
level: process.env.LOG_LEVEL_HTTP || 'info',
options: {
batching: false,
labels: {
app: process.env.APP_NAME,
namespace: process.env.NODE_ENV || 'development',
source: process.env.LOG_SOURCE || "pino",
runtime: `nodejs/${process.version}`
},
host: process.env.LOKI_HOST || "http://localhost:3100",
basicAuth: {
username: process.env.LOKI_USERNAME,
password: process.env.LOKI_PASSWORD
},
}
},
// {
// target: "pino-datadog-transport",
// level: process.env.LOG_LEVEL || 'info',
// options: {
// ddClientConf: {
// authMethods: {
// apiKeyAuth: process.env.DATADOG_API_KEY
// }
// },
// ddServerConf: {
// site: process.env.DATADOG_SITE || "datadoghq.com"
// },
// ddsource: process.env.LOG_SOURCE || "pino",
// ddtags: `env:${process.env.NODE_ENV || "development"}, runtime: nodejs/${process.version}`,
// service: `${process.env.APP_NAME}`,
// }
// },
// {
// target: "pino-pretty",
// level: process.env.LOG_LEVEL_CONSOLE || 'debug',
// options: {
// colorize: true
// }
// }
]
});
const logger = pino(transport);
logger.level = process.env.LOG_LEVEL || 'trace';
export default logger;
{
"name": "express-loki",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"engines": {
"node": ">=22.10.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node --watch index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.21.1",
"pino": "9.5.0",
"pino-http": "10.3.0",
"pino-loki": "2.3.1",
"pino-pretty": "11.3.0"
}
}
@diegofcornejo
Copy link
Author

demo1 demo2

@foamrider
Copy link

Great post, is it possible to get the Grafana Dashboard json as well? Those dashboard views looks amazing!

@diegofcornejo
Copy link
Author

Great post, is it possible to get the Grafana Dashboard json as well? Those dashboard views looks amazing!

Sure, here is the json

{
  "__inputs": [
    {
      "name": "DS_LOKI",
      "label": "loki",
      "description": "",
      "type": "datasource",
      "pluginId": "loki",
      "pluginName": "Loki"
    }
  ],
  "__elements": {},
  "__requires": [
    {
      "type": "grafana",
      "id": "grafana",
      "name": "Grafana",
      "version": "11.5.1"
    },
    {
      "type": "panel",
      "id": "logs",
      "name": "Logs",
      "version": ""
    },
    {
      "type": "datasource",
      "id": "loki",
      "name": "Loki",
      "version": "1.0.0"
    },
    {
      "type": "datasource",
      "id": "prometheus",
      "name": "Prometheus",
      "version": "1.0.0"
    },
    {
      "type": "panel",
      "id": "stat",
      "name": "Stat",
      "version": ""
    },
    {
      "type": "panel",
      "id": "timeseries",
      "name": "Time series",
      "version": ""
    }
  ],
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "grafana",
          "uid": "-- Grafana --"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 0,
  "id": null,
  "links": [],
  "panels": [
    {
      "datasource": {
        "type": "loki",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "fieldMinMax": false,
          "mappings": [],
          "noValue": "0",
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "blue",
                "value": null
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 6,
        "x": 0,
        "y": 0
      },
      "id": 3,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "center",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": true,
        "textMode": "value",
        "wideLayout": true
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          },
          "editorMode": "builder",
          "expr": "sum(\n    count_over_time(\n        {app=~\"$app\", namespace=~\"$namespace\"} \n        |= \"statusCode\"\n        | json\n        [${__range}]\n    )\n)",
          "legendFormat": "{{res_statusCode}}",
          "queryType": "range",
          "refId": "A"
        }
      ],
      "title": "Total Requests",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "fieldMinMax": false,
          "mappings": [],
          "noValue": "0",
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 6,
        "x": 6,
        "y": 0
      },
      "id": 2,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "center",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": true,
        "textMode": "value",
        "wideLayout": true
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          },
          "editorMode": "code",
          "expr": "sum(\n    count_over_time(\n        {app=~\"$app\", namespace=~\"$namespace\"} \n        |= \"statusCode\"\n        | json\n        | res_statusCode < 400\n        [${__range}]\n    )\n)",
          "legendFormat": "{{res_statusCode}}",
          "queryType": "range",
          "refId": "A"
        }
      ],
      "title": "Success Requests 2xx",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "fieldMinMax": false,
          "mappings": [],
          "noValue": "0",
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "orange",
                "value": null
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 6,
        "x": 12,
        "y": 0
      },
      "id": 4,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "center",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": true,
        "textMode": "value",
        "wideLayout": true
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          },
          "editorMode": "code",
          "expr": "sum(\n    count_over_time(\n        {app=~\"$app\", namespace=~\"$namespace\"} \n        |= \"statusCode\"\n        | json\n        | res_statusCode >= 400 and res_statusCode < 500\n        [${__range}]\n    )\n)",
          "legendFormat": "{{res_statusCode}}",
          "queryType": "range",
          "refId": "A"
        }
      ],
      "title": "Bad Requests 4xx",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "fieldMinMax": false,
          "mappings": [],
          "noValue": "0",
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": null
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 6,
        "x": 18,
        "y": 0
      },
      "id": 5,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "center",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": true,
        "textMode": "value",
        "wideLayout": true
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          },
          "editorMode": "code",
          "expr": "sum(\n    count_over_time(\n        {app=~\"$app\", namespace=~\"$namespace\"} \n        |= \"statusCode\"\n        | json\n        | res_statusCode >= 500\n        [${__range}]\n    )\n)",
          "legendFormat": "{{res_statusCode}}",
          "queryType": "range",
          "refId": "A"
        }
      ],
      "title": "Errored Requests 5xx",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "fixedColor": "green",
            "mode": "shades"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 1,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 50,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineStyle": {
              "fill": "solid"
            },
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "fieldMinMax": false,
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "blue",
                "value": null
              }
            ]
          },
          "unit": "none"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "500"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "404"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "orange",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "401"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "orange",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 6,
        "w": 24,
        "x": 0,
        "y": 5
      },
      "id": 1,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          },
          "editorMode": "code",
          "expr": "count_over_time(\n    {app=~\"$app\", namespace=~\"$namespace\"} \n    |= \"statusCode\"\n    | json\n    | keep res_statusCode\n    [1m]\n)",
          "legendFormat": "{{res_statusCode}}",
          "queryType": "range",
          "refId": "A"
        }
      ],
      "title": "Requests by Status Code",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${datasource}"
      },
      "gridPos": {
        "h": 14,
        "w": 24,
        "x": 0,
        "y": 11
      },
      "id": 6,
      "options": {
        "dedupStrategy": "none",
        "enableInfiniteScrolling": false,
        "enableLogDetails": true,
        "prettifyLogMessage": false,
        "showCommonLabels": false,
        "showLabels": false,
        "showTime": true,
        "sortOrder": "Descending",
        "wrapLogMessage": true
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          },
          "editorMode": "code",
          "expr": "{app=~\"$app\", namespace=~\"$namespace\", level=~\"$level\"}\n| json\n| res_statusCode=~\"$status\"",
          "queryType": "range",
          "refId": "A"
        }
      ],
      "title": "App Logs",
      "type": "logs"
    }
  ],
  "refresh": "1m",
  "schemaVersion": 40,
  "tags": [],
  "templating": {
    "list": [
      {
        "current": {},
        "includeAll": false,
        "label": "Datasource",
        "name": "datasource",
        "options": [],
        "query": "loki",
        "refresh": 1,
        "regex": "",
        "type": "datasource"
      },
      {
        "current": {},
        "datasource": {
          "type": "loki",
          "uid": "${datasource}"
        },
        "definition": "",
        "includeAll": true,
        "label": "Aplication",
        "multi": true,
        "name": "app",
        "options": [],
        "query": {
          "label": "app",
          "refId": "LokiVariableQueryEditor-VariableQuery",
          "stream": "",
          "type": 1
        },
        "refresh": 2,
        "regex": "",
        "sort": 1,
        "type": "query"
      },
      {
        "current": {},
        "datasource": {
          "type": "loki",
          "uid": "${DS_LOKI}"
        },
        "definition": "",
        "includeAll": true,
        "label": "Namespace",
        "multi": true,
        "name": "namespace",
        "options": [],
        "query": {
          "label": "namespace",
          "refId": "LokiVariableQueryEditor-VariableQuery",
          "stream": "",
          "type": 1
        },
        "refresh": 2,
        "regex": "",
        "type": "query"
      },
      {
        "current": {
          "text": [
            "$__all"
          ],
          "value": [
            "$__all"
          ]
        },
        "includeAll": true,
        "label": "Status Code",
        "multi": true,
        "name": "status",
        "options": [
          {
            "selected": false,
            "text": "200",
            "value": "200"
          },
          {
            "selected": false,
            "text": "201",
            "value": "201"
          },
          {
            "selected": false,
            "text": "401",
            "value": "401"
          },
          {
            "selected": false,
            "text": "404",
            "value": "404"
          },
          {
            "selected": false,
            "text": "500",
            "value": "500"
          },
          {
            "selected": false,
            "text": "",
            "value": ""
          }
        ],
        "query": "200,201,401,404,500, ",
        "type": "custom"
      },
      {
        "current": {},
        "datasource": {
          "type": "loki",
          "uid": "${datasource}"
        },
        "definition": "",
        "includeAll": true,
        "label": "Log Level",
        "multi": true,
        "name": "level",
        "options": [],
        "query": {
          "label": "level",
          "refId": "LokiVariableQueryEditor-VariableQuery",
          "stream": "",
          "type": 1
        },
        "refresh": 1,
        "regex": "",
        "type": "query"
      }
    ]
  },
  "time": {
    "from": "now-6h",
    "to": "now"
  },
  "timepicker": {},
  "timezone": "",
  "title": "Pino HTTP Logs",
  "uid": "dfx5h5b4k5e5sd",
  "version": 53,
  "weekStart": ""
}

@foamrider
Copy link

Awesome, thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment