forked from vanadium23/cf-metrics
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Configure tail worker and vite usage
- Loading branch information
Showing
7 changed files
with
4,315 additions
and
806 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
INFLUXDB_HOST=http://localhost | ||
INFLUXDB_DATABASE=example | ||
INFLUXDB_METRIC=views | ||
INFLUXDB_USERNAME=admin | ||
INFLUXDB_PASSWORD=pass | ||
INFLUX_URL=http://localhost:8086 | ||
INFLUX_TOKEN=example | ||
INFLUX_ORG=example | ||
INFLUX_BUCKET=example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,108 +1,75 @@ | ||
const parser = require('ua-parser-js'); | ||
import { InfluxDB, Point } from '@influxdata/influxdb-client'; | ||
|
||
// settings | ||
const MAX_REQUESTS_PER_BATCH = process.env.MAX_REQUESTS_PER_BATCH || 150; | ||
const MAX_TIME_AWAIT_PER_BATCH = process.env.MAX_TIME_AWAIT_PER_BATCH || 10 * 1000; | ||
const INFLUX_URL = process.env.INFLUX_URL || 'http://localhost:8086'; | ||
const INFLUX_TOKEN = process.env.INFLUX_TOKEN || ''; | ||
const INFLUX_ORG = process.env.INFLUX_ORG || ''; | ||
const INFLUX_BUCKET = process.env.INFLUX_BUCKET || ''; | ||
|
||
const INFLUXDB_HOST = process.env.INFLUXDB_HOST; | ||
const INFLUXDB_DATABASE = process.env.INFLUXDB_DATABASE; | ||
const INFLUXDB_METRIC = process.env.INFLUXDB_METRIC; | ||
const INFLUXDB_USERNAME = process.env.INFLUXDB_USERNAME; | ||
const INFLUXDB_PASSWORD = process.env.INFLUXDB_PASSWORD; | ||
const INFLUXDB_URL = `${INFLUXDB_HOST}/write?db=${INFLUXDB_DATABASE}&precision=s&u=${INFLUXDB_USERNAME}&p=${INFLUXDB_PASSWORD}`; | ||
const client = new InfluxDB({ url: INFLUX_URL, token: INFLUX_TOKEN }); | ||
|
||
// global vars | ||
let requests = []; | ||
let batchIsRunning = false; | ||
async function handleRequest(request, env, ctx) { | ||
// TODO: Replace | ||
return await fetch('http://httpbin.org/status/200'); | ||
} | ||
|
||
addEventListener('fetch', event => { | ||
event.passThroughOnException(); | ||
event.respondWith(logRequests(event)); | ||
}) | ||
async function formatMetricPoint(request) { | ||
const url = new URL(request.url); | ||
const today = new Date(); | ||
|
||
async function logRequests(event) { | ||
let requestStartTime, requestEndTime; | ||
if (!batchIsRunning) { | ||
event.waitUntil(handleBatch(event)); | ||
} | ||
if (requests.length >= MAX_REQUESTS_PER_BATCH) { | ||
event.waitUntil(sendMetricsToInfuxDB()) | ||
} | ||
requestStartTime = Date.now(); | ||
const response = await fetch(event.request); | ||
requestEndTime = Date.now(); | ||
const origin = request.headers.get("origin") ?? ""; | ||
const ip = request.headers.get("cf-connecting-ip") ?? ""; | ||
const userAgent = request.headers.get("user-agent") ?? ""; | ||
const country = request.headers.get("cf-ipcountry") ?? "unknown"; | ||
const cache = request.headers.get("cf-cache-status") ?? "unknown"; | ||
const service = new URL(origin).hostname.replaceAll(".", "-"); | ||
|
||
if (event.request.headers.get('DNT') === '1') { | ||
return response; | ||
} | ||
/** | ||
* For every origin that reports a page_view, visitors get a unique ID every | ||
* day. We don't log their IPs / UserAgents, but we do use them to calculate | ||
* their IDs. Visitor IDs let us determine uniqueness. | ||
* | ||
* This is also the strategy Plausible uses, and is a great balance between | ||
* usefulness and privacy. | ||
*/ | ||
const visitorDigest = await crypto.subtle.digest( | ||
"SHA-256", | ||
encoder.encode(today.toDateString() + ip + userAgent), | ||
); | ||
const visitor = Array.from(new Uint8Array(visitorDigest)) | ||
.map((b) => b.toString(16).padStart(2, "0")) | ||
.join(""); | ||
|
||
requests.push(getRequestData(event.request, response, requestStartTime, requestEndTime)); | ||
const point = new Point('request') | ||
.tag('url', url.toString()) | ||
.tag('hostname', url.hostname) | ||
.tag('pathname', url.pathname) | ||
.tag('method', request.method) | ||
.tag('cf_cache', cache) | ||
.tag('country', country) | ||
.tag('service', service) | ||
.tag('visitor', visitor) | ||
.timestamp(today); | ||
|
||
return response; | ||
return point; | ||
} | ||
|
||
async function handleBatch(event) { | ||
batchIsRunning = true; | ||
await sleep(MAX_TIME_AWAIT_PER_BATCH); | ||
try { | ||
if (requests.length) event.waitUntil(sendMetricsToInfuxDB()) | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
requests = []; | ||
batchIsRunning = false; | ||
} | ||
async function handleMetrics(events, env, ctx) { | ||
const writeApi = client.getWriteApi(INFLUX_ORG, INFLUX_BUCKET); | ||
for (const event of events) { | ||
const point = formatMetricPoint(event.request); | ||
|
||
function sleep(ms) { | ||
return new Promise(resolve => { | ||
setTimeout(resolve, ms) | ||
}) | ||
} | ||
console.log(point) | ||
writeApi.writePoint(point); | ||
|
||
function getRequestData(request, response, startTime, endTime) { | ||
const cfData = request.cf || {}; | ||
const timestamp = Math.floor(Date.now() / 1000); | ||
const originResponse = response || {}; | ||
return { | ||
'timestamp': timestamp, | ||
'userAgent': request.headers.get('user-agent'), | ||
'referer': request.headers.get('Referer'), | ||
'ip': request.headers.get('CF-Connecting-IP'), | ||
'countryCode': cfData.country, | ||
'url': request.url, | ||
'method': request.method, | ||
'status': originResponse.status, | ||
'originTime': (endTime - startTime), | ||
'cfCache': (originResponse) ? (response.headers.get('CF-Cache-Status') || 'miss') : 'miss', | ||
}; | ||
|
||
} | ||
|
||
function formMetricLine(data) { | ||
let referer; | ||
const url = new URL(data.url); | ||
const utmSource = url.searchParams.get('utm_source') || 'empty'; | ||
const ua = parser(data.userAgent); | ||
try { | ||
referer = new URL(data.referer); | ||
} catch { | ||
referer = { | ||
hostname: 'empty' | ||
}; | ||
} | ||
return `${INFLUXDB_METRIC},status_code=${data.status},url=${data.url},hostname=${url.hostname},pathname=${url.pathname},method=${data.method},cf_cache=${data.cfCache},country=${data.countryCode},referer=${referer.hostname},utm_source=${utmSource},browser=${ua.browser.name},os=${ua.os.name},device=${ua.device.type} duration=${data.originTime} ${data.timestamp}` | ||
} | ||
|
||
async function sendMetricsToInfuxDB() { | ||
const metrics = requests.map(formMetricLine).join('\n'); | ||
try { | ||
return fetch(INFLUXDB_URL, { | ||
method: 'POST', | ||
body: metrics, | ||
}).then(function (r) { | ||
return r; | ||
}); | ||
} catch (err) { | ||
console.log(err.stack || err); | ||
} | ||
ctx.waitUntil( | ||
writeApi.close() | ||
) | ||
} | ||
|
||
export default { | ||
fetch: handleRequest, | ||
tail: handleMetrics, | ||
} |
Oops, something went wrong.