Skip to content

Commit

Permalink
Merge pull request #8 from Moesif/cleanup-cloudflare-worker
Browse files Browse the repository at this point in the history
Fix: missing waitUntil causing events to not get logged.
  • Loading branch information
dgilling authored Dec 15, 2020
2 parents b2abf1f + f18764b commit fd898c4
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 87 deletions.
112 changes: 60 additions & 52 deletions MoesifWorker.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
// moesif-cloudflare
// https://github.com/Moesif/moesif-cloudflare
//
// Please update the `applicationId` as well as any hooks
// you'd like to use (eg: identifyUser, getSessionToken, etc)
// For manual install, areas of interest are tagged with MOESIF_INSTALL

// this value is defined by the Cloudflare App Worker framework
/**************************
* MOESIF_INSTALL
* Register logRequest handler.
* This line should be called before any other event listeners in your application
**************************/
addEventListener('fetch', event => {
logRequest(event);
});

/***********************
* MOESIF_INSTALL
* Main MoesifWorker.js
************************/

// this value is defined automatically by the Cloudflare App framework
var INSTALL_OPTIONS;

// Not installed via Cloudflare App framework, so set your options manually
if (typeof INSTALL_OPTIONS === 'undefined') {
// only for manual installation (not installed via Cloudflare Apps)

INSTALL_OPTIONS = {
// your moesif App Id
/*********************************
* MOESIF_INSTALL
* Set Your Moesif Application Id
*********************************/
"appId": "",

// only used by default identifyUser() implementation
// Only used by CloudFlare App Worker Framework. Modify identifyUser() function instead.
"userIdHeader": "",

// only used by default identifyUser() implementation
// Only used by CloudFlare App Worker Framework. Modify identifyCompany() function instead.
"companyIdHeader": "",

// only used by default getSessionToken() implementation
// Only used by CloudFlare App Worker Framework. Modify getSessionToken() function instead.
"sessionTokenHeader": "",

// true or false
Expand All @@ -41,30 +56,10 @@ let {
urlPatterns = []
} = INSTALL_OPTIONS;

urlPatterns = urlPatterns.map(({ appId, regex }) => {
try {
return {
regex: new RegExp(regex),
appId
};
} catch (e) {
console.error(e);
}
}).filter(x => x && x.regex); // filter invalid regular expressions / blank entries

if (!appId && urlPatterns.length === 0) {
console.error('Cannot track events. No App ID or valid URL Pattern specified.');
}

const overrideApplicationId = moesifEvent => {
// you may want to use a different app ID based on the request being made
const pattern = urlPatterns.find(({ regex }) => regex.test(moesifEvent.request.uri));

return pattern
? pattern.appId // may be an empty string, which means don't track this
: appId;
};

/*********************
* MOESIF_INSTALL
* Configuration hooks
**********************/
const identifyUser = (req, res) => {
return req.headers.get(userIdHeader) || res.headers.get(userIdHeader);
};
Expand Down Expand Up @@ -93,14 +88,34 @@ const maskContent = moesifEvent => {
return moesifEvent;
};

//
// moesif worker code
//

const MAX_REQUESTS_PER_BATCH = 15;
const BATCH_DURATION = 5000; // ms
const MAX_REQUESTS_PER_BATCH = 10;
const BATCH_DURATION = 1000; // ms
const TRANSACTION_ID_HEADER = 'X-Moesif-Transaction-Id';

urlPatterns = urlPatterns.map(({ appId, regex }) => {
try {
return {
regex: new RegExp(regex),
appId
};
} catch (e) {
console.error(e);
}
}).filter(x => x && x.regex); // filter invalid regular expressions / blank entries

if (!appId && urlPatterns.length === 0) {
console.error('Cannot track events. No App ID or valid URL Pattern specified.');
}

const overrideApplicationId = moesifEvent => {
// you may want to use a different app ID based on the request being made
const pattern = urlPatterns.find(({ regex }) => regex.test(moesifEvent.request.uri));

return pattern
? pattern.appId // may be an empty string, which means don't track this
: appId;
};

const BATCH_URL = 'https://api.moesif.net/v1/events/batch';
let batchRunning = false;

Expand Down Expand Up @@ -345,6 +360,7 @@ async function tryTrackRequest(event, request, response, before, after, txId) {
if (!isMoesif(request) && !runHook(() => skip(request, response), skip.name, false)) {
const moesifEvent = await makeMoesifEvent(request, response, before, after, txId);
const applicationId = runHook(() => overrideApplicationId(moesifEvent), overrideApplicationId.name, appId);
event.waitUntil(moesifEvent);

if (applicationId) {
// only track this if there's an associated applicationId
Expand All @@ -370,12 +386,14 @@ async function tryTrackRequest(event, request, response, before, after, txId) {
}
}

async function handleRequest(event) {
async function logRequest(event) {
const request = event.request;
const before = new Date();
// use a cloned request so the read buffer isn't locked
// when we inspect the request body later
const response = await fetch(request.clone());
const fetchResp = fetch(request.clone());
event.waitUntil(fetchResp);
const response = await fetchResp;
const after = new Date();
const txId = request.headers.get(TRANSACTION_ID_HEADER) || uuid4();

Expand All @@ -392,19 +410,9 @@ async function handleRequest(event) {

if (!disableTransactionId) {
const responseClone = new Response(response.body, response);

responseClone.headers.set(TRANSACTION_ID_HEADER, txId);

return responseClone;
} else {
return response;
}
}

addEventListener('fetch', event => {
// if this worker breaks, don't break the site
// https://developers.cloudflare.com/workers/writing-workers/handling-errors/
event.passThroughOnException();

event.respondWith(handleRequest(event));
});
}
97 changes: 62 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,61 @@
[![Source Code][ico-source]][link-source]

[Moesif Cloudflare app](https://www.cloudflare.com/apps/moesif) automatically logs API calls to [Moesif](https://www.moesif.com) for API analytics and monitoring.

[Source Code on GitHub](https://github.com/moesif/moesif-cloudflare)

Your Moesif Application Id can be found in the [_Moesif Portal_](https://www.moesif.com/).
After signing up for a Moesif account, your Moesif Application Id will be displayed during the onboarding steps.

You can always find your Moesif Application Id at any time by logging
into the [_Moesif Portal_](https://www.moesif.com/), click on the top right menu,
and then clicking _Installation_.
You can install Moesif using the Cloudflare Marketplace (simple install) or you can add the `MoesifWorker.js` script directly (custom install).
The custom install provides the most flexibility, allowing you to write custom logic to identify users, session tokens, etc.

## Install via Cloudflare App (Simple install)

Go to the [Moesif app](https://www.cloudflare.com/apps/moesif) on Cloudflare's App Marketplace and click _Preview_
* Go to the [Moesif app on Cloudflare](https://www.cloudflare.com/apps/moesif) and click _Preview_
* Add your Application Id which will be displayed during the onboarding steps when signing up for [Moesif](https://www.moesif.com/).
* Click _Finish Installing onto your site_ button.

## Install via Cloudflare Workers (Custom install)

Installing via the Cloudflare Workers Dashboard provides the most flexibility, allowing you to write custom logic to identify users, session tokens, etc.

- Visit the [Cloudflare Workers Dashboard](https://dash.cloudflare.com/workers). *(make sure you're looking at the Workers tab)*
- Click the `Launch Editor` button
- Click the `Routes` tab and create a route under `Script enabled at:`. We suggest a pattern that matches all requests for your domain. Eg: If your domain is `acmeinc.com`, **your pattern should be** `*acmeinc.com/*`. This will match all requests to `acmeinc.com` and any subdomains of `acmeinc.com`.
- Click the `Script` tab, and replace the editor content with the latest version of the [Moesif Cloudflare worker](https://raw.githubusercontent.com/Moesif/moesif-cloudflare/master/MoesifWorker.js).
- replace any instances of the `INSTALL_OPTIONS` variable with desired values.
- update the `INSTALL_OPTIONS` declaration with the desired values.
### 1. Add script to your worker

For example:
Go to your worker in the [Cloudflare Workers Dashboard](https://dash.cloudflare.com/workers).
Add the contents of the file [MoesifWorker.js](https://raw.githubusercontent.com/Moesif/moesif-cloudflare/master/MoesifWorker.js) to your worker.
The first three lines of `MoesifWorker.js` must be above any of your other `addEventListener` functions as shown below

```javascript
INSTALL_OPTIONS = {
// your moesif App Id
"appId": "",
// Add Moesif handler from top of `MoesifWorker.js` file
addEventListener('fetch', event => {
logRequest(event);
});

// A sample hello world app
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
return new Response('hello world', {status: 200})
}

// only used by default identifyUser() implementation
"userIdHeader": "",
// Rest of code from MoesifWorker.js file
```

// only used by default identifyUser() implementation
"companyIdHeader": "",
### 2. Set up worker route

// only used by default getSessionToken() implementation
"sessionTokenHeader": "",
If using a custom domain, you'll need to create add a route.
We recommend matching all requests for your domain. For example if your domain is `acmeinc.com`, your pattern should be `*acmeinc.com/*`.

// true or false
"hideCreditCards": true
};
```
### 3. Set Moesif options

becomes
Set the `INSTALL_OPTIONS.appId` field to your Moesif Application Id.
Your Moesif Application Id will be displayed during the onboarding steps when signing up for [Moesif](https://www.moesif.com/).

```javascript
INSTALL_OPTIONS = {
// your moesif App Id
"appId": "<< YOUR MOESIF APPLICATION ID >>",
"appId": "Your Moesif Application Id",

// only used by default identifyUser() implementation
"userIdHeader": "User-Id",
"userIdHeader": "X-Custom-User-Id",

// only used by default identifyUser() implementation
"companyIdHeader": "",
Expand All @@ -71,14 +71,41 @@ INSTALL_OPTIONS = {
};
```

*Please note `HIDE_CREDIT_CARDS`, `sessionTokenHeader`, and `userIdHeader` may be `null`.*
Besides the static fields in `INSTALL_OPTIONS`, you can also override the functions hooks like `identifyUser` and `skip`.
Just search for `Configuration hooks` in the `MoesifWorker.js` file.

### 4. Deploy changes

- click `Update Preview` to see changes in the preview window, and click `Deploy` to deploy the worker to production
Click `Save and Deploy` to see changes in the preview window. In the Cloudflare editor, make a few GET calls to your service.
The API calls should show up in Moesif event stream.

Congratulations! If everything was correct, Moesif should now be tracking all network requests that match the route you specified earlier. If you have any issues with set up, please reach out to support@moesif.com with the subject `Cloudflare Workers`.
If you have any issues with set up, please reach out to support@moesif.com with the subject `Cloudflare Workers`.

## Troubleshooting

### Timeout errors
Your worker code should register a function that calls `event.respondWith` to ensure a response is returned.
Moesif will not return the response directly.

```javascript
// Add Moesif handler from top of `MoesifWorker.js` file
addEventListener('fetch', event => {
logRequest(event);
});

// Sample hello world app
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
return new Response('hello world', {status: 200})
}

// Rest of `MoesifWorker.js` file
```


### Requests not being logged
If you installed via the custom install with Cloudflare Workers, then you need to set the route pattern to ensure the worker is active for the correct routes. Cloudflare has [very specific rules](https://developers.cloudflare.com/workers/about/routes/) for the route pattens.

Expand Down

0 comments on commit fd898c4

Please sign in to comment.