Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: #146 async send support #181

Merged
merged 23 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 52 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,55 @@ Note that the Express middleware handler will pick up and transmit any `err` obj

## Documentation

### Callbacks
### Asynchronous `send` with `Promise` response

The `send()` function is asynchronous and returns a `Promise` of type `IncomingMessage`.

Note that `IncomingMessage` can be `null` if the request was stored because the application was offline.

`IncomingMessage` is the response from the Raygun API - there's nothing in the body, it's just a status code response.
If everything went ok, you'll get a 202 response code.
Otherwise, we throw 401 for incorrect API keys, 403 if you're over your plan limits, or anything in the 500+ range for internal errors.

We use the nodejs http/https library to make the POST to Raygun, you can see more documentation about that callback here: https://nodejs.org/api/http.html#http_http_request_options_callback

You can `await` the call to obtain the result, or use `then/catch`.

#### Using `await`

Use `await` to obtain the `IncomingMessage`, remember to `catch` any possible thrown errors from the `send()` method.

```js
try {
let message = await client.send(error);
} catch (e) {
// error sending message
}
```

#### Using `then/catch`

You can also use `then()` to obtain the `IncomingMessage`, as well, use `catch()` to catch any possible thrown errors from the `send()` method.

```js
client.send(error)
.then((message) => {
// message sent to Raygun
})
.catch((error) => {
// error sending message
});
```

### Legacy `sendWithCallback`

```javascript
client.sendWithCallback(new Error(), {}, function (response){ });
```

The client still provides a legacy `send()` method that supports callbacks instead of `Promises`.

**This method is deprecated and will be removed in the future.**

The callback should be a node-style callback: `function(err, response) { /*...*/ }`.
*Note*: If the callback only takes one parameter (`function(response){ /*...*/ }`)
Expand All @@ -98,7 +146,7 @@ backwards compatibility; the Node-style callback should be preferred.
You can pass custom data in on the Send() function, as the second parameter. For instance (based off the call in test/raygun_test.js):

```javascript
client.send(new Error(), { 'mykey': 'beta' }, function (response){ });
client.send(new Error(), { 'mykey': 'beta' });
```

#### Sending custom data with Expressjs
Expand All @@ -113,19 +161,11 @@ raygunClient.expressCustomData = function (err, req) {
};
```

### Callback

```javascript
client.send(new Error(), {}, function (response){ });
```

The argument to the 3rd argument callback is the response from the Raygun API - there's nothing in the body, it's just a status code response. If everything went ok, you'll get a 202 response code. Otherwise we throw 401 for incorrect API keys, 403 if you're over your plan limits, or anything in the 500+ range for internal errors. We use the nodejs http/https library to make the POST to Raygun, you can see more documentation about that callback here: https://nodejs.org/api/http.html#http_http_request_options_callback

### Sending request data

You can send the request data in the Send() function, as the fourth parameter. For example:
```javascript
client.send(new Error(), {}, function () {}, request);
client.send(new Error(), {}, request);
```

If you want to filter any of the request data then you can pass in an array of keys to filter when
Expand All @@ -139,7 +179,7 @@ const raygunClient = new raygun.Client().init({ apiKey: 'YOUR_API_KEY', filters:

You can add tags to your error in the Send() function, as the fifth parameter. For example:
```javascript
client.send(new Error(), {}, function () {}, {}, ['Custom Tag 1', 'Important Error']);
client.send(new Error(), {}, {}, ['Custom Tag 1', 'Important Error']);
```

Tags can also be set globally using setTags
Expand Down
21 changes: 11 additions & 10 deletions examples/using-domains/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@ 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) {
try {
console.log(`Domain error caught: ${err}`);
// Try send data to Raygun
raygunClient.send(err, {}, function () {
console.log(`Domain error caught: ${err}`);
// Try send data to Raygun
raygunClient
.send(err)
.then((message) => {
// Exit the process once the error has been sent
console.log("Error sent to Raygun, exiting process");
process.exit(1);
})
.catch((error) => {
// If there was an error sending to Raygun, log it out and end the process.
// Could possibly log out to a text file here
console.log(error);
process.exit(1);
});
} catch (e) {
// If there was an error sending to Raygun, log it out and end the process.
// Could possibly log out to a text file here
console.log(e);
process.exit(1);
}
});

// Run the domain
Expand Down
6 changes: 6 additions & 0 deletions lib/raygun.batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export class RaygunBatchTransport {
this.httpOptions = options.httpOptions;
}

/**
* Enqueues send request to batch processor.
* Callback in SendOptions is called when the message is eventually processed.
* @param options
*/
send(options: SendOptions) {
this.onIncomingMessage({
serializedMessage: options.message,
Expand Down Expand Up @@ -145,6 +150,7 @@ export class RaygunBatchTransport {
);
}

// TODO: Callbacks are processed in batch, see how can this be implemented with Promises
for (const callback of callbacks) {
if (callback) {
callVariadicCallback(callback, err, response);
Expand Down
1 change: 1 addition & 0 deletions lib/raygun.offline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class OfflineStorage implements IOfflineStorage {
path.join(this.cachePath, item),
"utf8",
(err, cacheContents) => {
// TODO: MessageTransport ignores any errors from the send callback, could this be improved?
this.transport.send(cacheContents);
fs.unlink(path.join(this.cachePath, item), () => {});
},
Expand Down
7 changes: 7 additions & 0 deletions lib/raygun.sync.transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ function syncRequest(httpOptions: SendOptionsWithoutCB) {
console.log(requestProcess.stdout.toString());
}

/**
* Spawns a synchronous send request.
* Errors are not returned and callback is ignored.
* Only used to report uncaught exceptions.
* @param options
*/
export function send(options: SendOptionsWithoutCB) {
try {
syncRequest(options);
} catch (e) {
// TODO: Is there a reason we ignore errors here?
miquelbeltran marked this conversation as resolved.
Show resolved Hide resolved
console.log(
`Raygun: error ${e} occurred while attempting to send error with message: ${options.message}`,
);
Expand Down
7 changes: 7 additions & 0 deletions lib/raygun.transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export function sendBatch(options: SendOptions) {
return send(options, BATCH_ENDPOINT);
}

// TODO: Convert this method callbacks to Promise.
miquelbeltran marked this conversation as resolved.
Show resolved Hide resolved
/**
* Transport implementation that sends error to Raygun.
* Errors are reported back via callback.
* @param options
*/
export function send(options: SendOptions, path = DEFAULT_ENDPOINT) {
try {
const data = Buffer.from(options.message);
Expand Down Expand Up @@ -66,6 +72,7 @@ export function send(options: SendOptions, path = DEFAULT_ENDPOINT) {
request.write(data);
request.end();
} catch (e) {
// TODO: Non-HTTP errors are being ignored, should be better pass them up?
miquelbeltran marked this conversation as resolved.
Show resolved Hide resolved
console.log(
`Raygun: error ${e} occurred while attempting to send error with message: ${options.message}`,
);
Expand Down
80 changes: 58 additions & 22 deletions lib/raygun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class Raygun {
}

this.expressHandler = this.expressHandler.bind(this);
this.sendWithCallback = this.sendWithCallback.bind(this);
this.send = this.send.bind(this);

this._offlineStorage =
Expand Down Expand Up @@ -185,10 +186,45 @@ class Raygun {
return raygunTransport;
}

send(
/**
* Sends exception to Raygun.
* @param exception to send.
* @param customData to attach to the error report.
* @param request custom RequestParams.
* @param tags to attach to the error report.
* @return IncomingMessage if message was delivered, null if stored, rejected with Error if failed.
*/
async send(
exception: Error | string,
customData?: CustomData,
callback?: (err: Error | null) => void,
request?: RequestParams,
tags?: Tag[],
): Promise<IncomingMessage | null> {
// Convert internal sendWithCallback implementation to a Promise.
return new Promise((resolve, reject) => {
this.sendWithCallback(
exception,
customData,
function (err, message) {
if (err != null) {
reject(err);
} else {
resolve(message);
}
},
request,
tags,
);
});
}

/**
* @deprecated use send instead, which supports async/await calls.
miquelbeltran marked this conversation as resolved.
Show resolved Hide resolved
*/
sendWithCallback(
exception: Error | string,
customData?: CustomData,
callback?: Callback<IncomingMessage>,
request?: RequestParams,
tags?: Tag[],
): Message {
Expand All @@ -212,11 +248,14 @@ class Raygun {
const sendOptions = sendOptionsResult.options;

if (this._isOffline) {
this.offlineStorage().save(
JSON.stringify(message),
callback || emptyCallback,
);
// make the save callback type compatible with Callback<IncomingMessage>
const saveCallback = callback
? (err: Error | null) => callVariadicCallback(callback, err, null)
: emptyCallback;
this.offlineStorage().save(JSON.stringify(message), saveCallback);
} else {
// Use current transport to send request.
// Transport can be batch or default.
this.transport().send(sendOptions);
}

Expand Down Expand Up @@ -245,20 +284,14 @@ class Raygun {
});
}

private sendSync(
exception: Error | string,
customData?: CustomData,
callback?: (err: Error | null) => void,
request?: RequestParams,
tags?: Tag[],
): void {
const result = this.buildSendOptions(
exception,
customData,
callback,
request,
tags,
Comment on lines -250 to -260
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all the unused parameters, although this would be out of scope for this PR

);
/**
* Send error using synchronous transport.
* Only used to report uncaught exceptions.
* @param exception error to report
* @private
*/
private sendSync(exception: Error | string): void {
const result = this.buildSendOptions(exception);

if (result.valid) {
raygunSyncTransport.send(result.options);
Expand All @@ -285,9 +318,11 @@ class Raygun {
body: req.body,
};

this.send(err, customData || {}, function () {}, requestParams, [
this.send(err, customData || {}, requestParams, [
"UnhandledException",
]);
]).catch((err) => {
console.log(`[Raygun] Failed to send Express error: ${err}`);
});
Comment on lines +321 to +325
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is internal to the express error handler.

next(err);
}

Expand Down Expand Up @@ -403,6 +438,7 @@ class Raygun {
};

return {
// TODO: MessageTransport ignores any errors from the send callback, could this be improved?
send(message: string) {
transport.send({
message,
Expand Down
1 change: 1 addition & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type Tag = string;

export type SendOptions = {
message: string;
// TODO: Remove Callback in SendOptions and use Promises internally
callback: Callback<IncomingMessage>;
http: HTTPOptions;
};
Expand Down
Loading