Skip to content

Commit

Permalink
feat: Add unhandledExceptionPlugin (#91)
Browse files Browse the repository at this point in the history
Co-authored-by: Igor Savin <[email protected]>
  • Loading branch information
yarlson and kibertoad authored Nov 21, 2023
1 parent 6aa5cd6 commit 30b7baf
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 3 deletions.
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Reusable plugins for Fastify.
* [BugSnag Plugin](#bugsnag-plugin)
* [Metrics Plugin](#metrics-plugin)
* [NewRelic Transaction Manager Plugin](#newrelic-transaction-manager-plugin)
* [UnhandledException Plugin](#unhandledexception-plugin)

## Dependency Management

Expand Down Expand Up @@ -161,3 +162,78 @@ The plugin decorates your Fastify instance with a `Amplitude`, which you can inj
> ```
> "@amplitude/analytics-types": "*"
> ```
### UnhandledException Plugin
This plugin provides a mechanism for handling uncaught exceptions within your Fastify application, ensuring that such exceptions are logged and reported. It's especially useful for capturing unforeseen exceptions and provides a controlled shutdown of the Fastify server, thereby ensuring no potential data corruption.
#### Setup & Configuration
To integrate this plugin into your Fastify instance, follow these steps:
1. First, import the necessary types and the plugin:
```typescript
import { FastifyInstance } from 'fastify';
import { unhandledExceptionPlugin, ErrorObjectResolver } from '@lokalise/fastify-extras';
```
2. Configure the plugin:

Define your own `ErrorObjectResolver` to dictate how the uncaught exceptions will be structured for logging. Here's an example:

```typescript
const myErrorResolver: ErrorObjectResolver = (err, correlationID) => {
return {
error: err,
id: correlationID
};
};
```

You'll also need to provide an `ErrorReporter` instance. This instance should have a `report` method to handle the error reporting logic. For example:

```typescript
import { ErrorReporter } from "@lokalise/node-core";

const myErrorReporter = new ErrorReporter(/* initialization params */);
```

3. Register the plugin with your Fastify instance:

```typescript
const fastify = Fastify();

fastify.register(unhandledExceptionPlugin, {
errorObjectResolver: myErrorResolver,
errorReporter: myErrorReporter
});
```

#### Options

The plugin accepts the following options:

- `errorObjectResolver` (required): This function determines the structure of the error object which will be logged in case of an uncaught exception.

- `errorReporter` (required): An instance of the ErrorReporter which will handle reporting of the uncaught exceptions.

#### Working Principle

When an uncaught exception occurs, the plugin:

- Logs the exception using the provided `errorObjectResolver`.

- Reports the exception using the `ErrorReporter`.

- Shuts down the Fastify server gracefully.

- Exits the process with exit code `1`.

#### Dependencies

- `@lokalise/node-core`: For error reporting.

- `fastify`: The framework this plugin is designed for.

> 🚨 It's critical to note that this plugin listens to the process's 'uncaughtException' event. Multiple listeners on this event can introduce unpredictable behavior in your application. Ensure that this is the sole listener for this event or handle interactions between multiple listeners carefully.
3 changes: 3 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ export {
} from './plugins/amplitudePlugin'

export type { FastifyReplyWithPayload } from './types'

export { unhandledExceptionPlugin } from './plugins/unhandledExceptionPlugin'
export type { UnhandledExceptionPluginOptions } from './plugins/unhandledExceptionPlugin'
29 changes: 29 additions & 0 deletions lib/plugins/unhandledExceptionPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { ErrorReporter } from '@lokalise/node-core'
import type { FastifyInstance } from 'fastify'
import fp from 'fastify-plugin'

export type ErrorObjectResolver = (err: unknown, correlationID?: string) => unknown

export interface UnhandledExceptionPluginOptions {
errorObjectResolver: ErrorObjectResolver
errorReporter: ErrorReporter
}

function plugin(app: FastifyInstance, opts: UnhandledExceptionPluginOptions) {
// Handle unhandled exceptions
process.on('uncaughtException', (err) => {
const logObject = opts.errorObjectResolver(err)
app.log.fatal(logObject, 'uncaught exception detected')
opts.errorReporter.report({ error: err })

// shutdown the server gracefully
app.close(() => {
process.exit(1) // then exit
})
})
}

export const unhandledExceptionPlugin = fp(plugin, {
fastify: '4.x',
name: 'unhandled-exception-plugin',
})
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
"@opentelemetry/semantic-conventions": "1.17.0",
"@prisma/instrumentation": "^5.4.1",
"@splitsoftware/splitio": "^10.23.1",
"@amplitude/analytics-node": "^1.3.3",
"fastify-metrics": "^10.3.2",
"@amplitude/analytics-node": "^1.3.4",
"fastify-metrics": "^10.3.3",
"fastify-plugin": "^4.5.1",
"tslib": "^2.6.2"
},
Expand Down Expand Up @@ -86,7 +86,8 @@
"shx": "^0.3.4",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
"vitest": "^0.34.6"
"vitest": "^0.34.6",
"vite": "4.5.0"
},
"engines": {
"node": ">=18"
Expand Down

0 comments on commit 30b7baf

Please sign in to comment.