Skip to content

Commit

Permalink
feat: Breadcrumbs (#210)
Browse files Browse the repository at this point in the history
* setup eslint style

* rules for quotes

* add eslint plugin ts

* wip configs

* wip

* add rules

* define rules

* add comment

* add implementation from old breadcrumbs branch

* tests passing

* add todo

* WIP

* cleaning up and adding docs

* scoping breadcrumbs in error handler

* improving tests

* cleanup tests

* fix eslint

* prettier

* moved method

* add Breadcrumb object test

* add Breadcrumb object test

* fix test

* expanded the express-example

* prettier

* prettier

* add breadcrumbs to domains app

* cleanup and test for clear breadcrumbs

* prettier

* rename

* refactor debug statements

* rename logger.js to raygun.client.js

* prettier

* rename

* document Breadcrumbs and rename Breadcrumb type

* improve docs

* add word

* removed commented out code
  • Loading branch information
miquelbeltran authored May 15, 2024
1 parent a95d14c commit 57ceec3
Show file tree
Hide file tree
Showing 17 changed files with 663 additions and 29 deletions.
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,60 @@ const myBeforeSend = function (payload, exception, customData, request, tags) {
Raygun.onBeforeSend(myBeforeSend);
```

### Breadcrumbs

Breadcrumbs can be sent to Raygun to provide additional information to look into and debug issues stemming from crash reports.

Breadcrumbs can be created in two ways.

#### Simple string:

Call `client.addBreadcrumb(message)`, where message is just a string:

```js
client.addBreadcrumb('test breadcrumb');
```

#### Using `BreadcrumbMessage`:

Create your own `BreadcrumbMessage` object and send more than just a message with `client.addBreadcrumb(BreadcrumbMessage)`.

The structure of the type `BreadcrumbMessage` is as shown here:

```js
BreadcrumbMessage: {
level: "debug" | "info" | "warning" | "error";
category: string;
message: string;
customData?: CustomData;
}
```

Breadcrumbs can be cleared with `client.clearBreadcrumbs()`.

#### Breadcrumbs and ExpressJS

Raygun4Node provides a custom ExpressJS middleware that helps to scope Breadcrumbs to a specific request.
As well, this middleware will add a Breadcrumb with information about the performed request.

To set up, add the Raygun Breadcrumbs ExpressJS handler before configuring any endpoints.

```js
// Add the Raygun Breadcrumb ExpressJS handler
app.use(raygunClient.expressHandlerBreadcrumbs);

// Setup the rest of the app, e.g.
app.use("/", routes);
```

This middleware can be user together with the provided ExpressJS error handler `expressHandler`.
The order in which the middlewares are configured is important. `expressHandlerBreadcrumbs` should go first to scope breadcrumbs correctly.

```js
app.use(raygunClient.expressHandlerBreadcrumbs);
app.use(raygunClient.expressHandler);
```

### Batched error transport

You can enable a batched transport mode for the Raygun client by passing `{batch: true}` when initializing.
Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default tseslint.config(
// Add node globals to ignore undefined
globals: {
"__dirname": false,
"__filename": false,
"console": false,
"module": false,
"process": false,
Expand Down
11 changes: 7 additions & 4 deletions examples/express-sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ in the subdirectory where you found this README.md file.

## Interesting files to look

- `raygun.client.js`
- Setup of Raygun (lines 9-14)
- `app.js`
- Setup of Raygun (lines 9-12)
- Sets the user (lines 27-29)
- Attaches Raygun to Express (line 60)
- Sets the user (lines 17-19)
- Attaches Raygun Breadcrumb middleware to Express (line 26)
- Attaches Raygun to Express (line 53)
- `routes/index.js`
- Tries to use a fake object, which bounces up to the Express handler (lines 11-15)
- `/send` endpoint: Sends a custom error to Raygun (lines 11-34)
- `/error` endpoint: Tries to use a fake object, which bounces up to the Express handler (lines 36-49)
23 changes: 7 additions & 16 deletions examples/express-sample/app.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
var config = require("config");

if (config.Raygun.Key === "YOUR_API_KEY") {
console.error(
"[Raygun4Node-Express-Sample] You need to set your Raygun API key in the config file",
);
process.exit(1);
}

// Setup Raygun
var raygun = require("raygun");
var raygunClient = new raygun.Client().init({
apiKey: config.Raygun.Key,
});

var express = require("express");
var path = require("path");
var logger = require("morgan");
var cookieParser = require("cookie-parser");
var bodyParser = require("body-parser");
var sassMiddleware = require("node-sass-middleware");
var raygunClient = require("./raygun.client");

var routes = require("./routes/index");
var users = require("./routes/users");
Expand All @@ -34,6 +20,9 @@ raygunClient.user = function (req) {
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");

// Add the Raygun breadcrumb Express handler
app.use(raygunClient.expressHandlerBreadcrumbs);

// uncomment after placing your favicon in /public
// app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger("dev"));
Expand All @@ -58,7 +47,9 @@ app.use(express.static(path.join(__dirname, "public")));
app.use("/", routes);
app.use("/users", users);

// Add the Raygun Express handler
// Add the Raygun error Express handler
app.use(raygunClient.expressHandler);

raygunClient.addBreadcrumb("Express Server started!");

module.exports = app;
2 changes: 2 additions & 0 deletions examples/express-sample/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions examples/express-sample/raygun.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
var config = require("config");

if (config.Raygun.Key === "YOUR_API_KEY") {
console.error(
"[Raygun4Node-Express-Sample] You need to set your Raygun API key in the config file",
);
process.exit(1);
}

// Setup Raygun
var raygun = require("raygun");
var raygunClient = new raygun.Client().init({
apiKey: config.Raygun.Key,
});

module.exports = raygunClient;
42 changes: 39 additions & 3 deletions examples/express-sample/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
var express = require("express");
var router = express.Router();
const express = require("express");
const router = express.Router();
const raygunClient = require("../raygun.client");

/* GET home page. */
router.get("/", function (req, res, next) {
res.render("index", {
title: "Express",
title: "Raygun Express Example",
});
});

router.get("/send", function (req, res, next) {
raygunClient.addBreadcrumb({
level: "debug",
category: "Example",
message: "Breadcrumb in /send endpoint",
customData: {
"custom-data": "data",
},
});

raygunClient
.send("Custom Raygun Error in /send endpoint")
.then((message) => {
res.render("send", {
title: "Sent custom error to Raygun",
body: `Raygun status code: ${message.statusCode}`,
});
})
.catch((error) => {
res.render("send", {
title: "Failed to send custom error to Raygun",
body: error.toString(),
});
});
});

router.get("/error", function (req, res, next) {
raygunClient.addBreadcrumb({
level: "debug",
category: "Example",
message: "Breadcrumb in /error endpoint",
customData: {
"custom-data": "data",
},
});

// Call an object that doesn't exist to send an error to Raygun
fakeObject.FakeMethod();
res.send(500);
Expand Down
3 changes: 3 additions & 0 deletions examples/express-sample/views/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>

<div>
<a href="/send">Send a custom error</a>
</div>
<div>
<a href="/error">Throw an error</a>
</div>
Expand Down
18 changes: 18 additions & 0 deletions examples/express-sample/views/send.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p><%= title %></p>
<div>
<%= body %>
</div>

<div>
<a href="/">Back to index</a>
</div>
</body>
</html>
2 changes: 2 additions & 0 deletions examples/using-domains/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var appDomain = require("domain").create();
// Add the error handler so we can pass errors to Raygun when the domain
// crashes
appDomain.on("error", function (err) {
raygunClient.addBreadcrumb("Domain error caught!");
console.log(`[Raygun4Node-Domains-Sample] Domain error caught: ${err}`);
// Try send data to Raygun
raygunClient
Expand All @@ -40,6 +41,7 @@ appDomain.on("error", function (err) {
appDomain.run(function () {
var fs = require("fs");

raygunClient.addBreadcrumb("Running example app");
console.log("[Raygun4Node-Domains-Sample] Running example app");

// Try and read a file that doesn't exist
Expand Down
2 changes: 2 additions & 0 deletions examples/using-domains/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions lib/raygun.breadcrumbs.express.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Request } from "express";
import type { Breadcrumb } from "./types";
import { getBreadcrumbs } from "./raygun.breadcrumbs";

const debug = require("debug")("raygun");

/**
* Parses an ExpressJS Request and adds it to the breadcrumbs store
* @param request
*/
export function addRequestBreadcrumb(request: Request) {
const crumbs = getBreadcrumbs();

if (!crumbs) {
debug(
"[raygun.breadcrumbs.express.ts] Add request breadcrumb skip, no store!",
);
return;
}

const internalCrumb: Breadcrumb = {
category: "http",
message: `${request.method} ${request.url}`,
level: "info",
timestamp: Number(new Date()),
type: "request",
};

debug(
`[raygun.breadcrumbs.express.ts] recorded request breadcrumb: ${internalCrumb}`,
);

crumbs.push(internalCrumb);
}
Loading

0 comments on commit 57ceec3

Please sign in to comment.