1- const parser = require ( 'ua-parser-js' ) ;
1+ import { InfluxDB , Point } from '@influxdata/influxdb-client' ;
22
3- // settings
4- const MAX_REQUESTS_PER_BATCH = process . env . MAX_REQUESTS_PER_BATCH || 150 ;
5- const MAX_TIME_AWAIT_PER_BATCH = process . env . MAX_TIME_AWAIT_PER_BATCH || 10 * 1000 ;
3+ const INFLUX_URL = process . env . INFLUX_URL || 'http://localhost:8086' ;
4+ const INFLUX_TOKEN = process . env . INFLUX_TOKEN || '' ;
5+ const INFLUX_ORG = process . env . INFLUX_ORG || '' ;
6+ const INFLUX_BUCKET = process . env . INFLUX_BUCKET || '' ;
67
7- const INFLUXDB_HOST = process . env . INFLUXDB_HOST ;
8- const INFLUXDB_DATABASE = process . env . INFLUXDB_DATABASE ;
9- const INFLUXDB_METRIC = process . env . INFLUXDB_METRIC ;
10- const INFLUXDB_USERNAME = process . env . INFLUXDB_USERNAME ;
11- const INFLUXDB_PASSWORD = process . env . INFLUXDB_PASSWORD ;
12- const INFLUXDB_URL = `${ INFLUXDB_HOST } /write?db=${ INFLUXDB_DATABASE } &precision=s&u=${ INFLUXDB_USERNAME } &p=${ INFLUXDB_PASSWORD } ` ;
8+ const client = new InfluxDB ( { url : INFLUX_URL , token : INFLUX_TOKEN } ) ;
139
1410// global vars
15- let requests = [ ] ;
16- let batchIsRunning = false ;
11+ async function handleRequest ( request , env , ctx ) {
12+ // TODO: Replace
13+ return await fetch ( 'http://httpbin.org/status/200' ) ;
14+ }
1715
18- addEventListener ( 'fetch' , event => {
19- event . passThroughOnException ( ) ;
20- event . respondWith ( logRequests ( event ) ) ;
21- } )
16+ async function formatMetricPoint ( request ) {
17+ const url = new URL ( request . url ) ;
18+ const today = new Date ( ) ;
2219
23- async function logRequests ( event ) {
24- let requestStartTime , requestEndTime ;
25- if ( ! batchIsRunning ) {
26- event . waitUntil ( handleBatch ( event ) ) ;
27- }
28- if ( requests . length >= MAX_REQUESTS_PER_BATCH ) {
29- event . waitUntil ( sendMetricsToInfuxDB ( ) )
30- }
31- requestStartTime = Date . now ( ) ;
32- const response = await fetch ( event . request ) ;
33- requestEndTime = Date . now ( ) ;
20+ const origin = request . headers . get ( "origin" ) ?? "" ;
21+ const ip = request . headers . get ( "cf-connecting-ip" ) ?? "" ;
22+ const userAgent = request . headers . get ( "user-agent" ) ?? "" ;
23+ const country = request . headers . get ( "cf-ipcountry" ) ?? "unknown" ;
24+ const cache = request . headers . get ( "cf-cache-status" ) ?? "unknown" ;
25+ const service = new URL ( origin ) . hostname . replaceAll ( "." , "-" ) ;
3426
35- if ( event . request . headers . get ( 'DNT' ) === '1' ) {
36- return response ;
37- }
27+ /**
28+ * For every origin that reports a page_view, visitors get a unique ID every
29+ * day. We don't log their IPs / UserAgents, but we do use them to calculate
30+ * their IDs. Visitor IDs let us determine uniqueness.
31+ *
32+ * This is also the strategy Plausible uses, and is a great balance between
33+ * usefulness and privacy.
34+ */
35+ const visitorDigest = await crypto . subtle . digest (
36+ "SHA-256" ,
37+ encoder . encode ( today . toDateString ( ) + ip + userAgent ) ,
38+ ) ;
39+ const visitor = Array . from ( new Uint8Array ( visitorDigest ) )
40+ . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , "0" ) )
41+ . join ( "" ) ;
3842
39- requests . push ( getRequestData ( event . request , response , requestStartTime , requestEndTime ) ) ;
43+ const point = new Point ( 'request' )
44+ . tag ( 'url' , url . toString ( ) )
45+ . tag ( 'hostname' , url . hostname )
46+ . tag ( 'pathname' , url . pathname )
47+ . tag ( 'method' , request . method )
48+ . tag ( 'cf_cache' , cache )
49+ . tag ( 'country' , country )
50+ . tag ( 'service' , service )
51+ . tag ( 'visitor' , visitor )
52+ . timestamp ( today ) ;
4053
41- return response ;
54+ return point ;
4255}
4356
44- async function handleBatch ( event ) {
45- batchIsRunning = true ;
46- await sleep ( MAX_TIME_AWAIT_PER_BATCH ) ;
47- try {
48- if ( requests . length ) event . waitUntil ( sendMetricsToInfuxDB ( ) )
49- } catch ( e ) {
50- console . error ( e ) ;
51- }
52- requests = [ ] ;
53- batchIsRunning = false ;
54- }
57+ async function handleMetrics ( events , env , ctx ) {
58+ const writeApi = client . getWriteApi ( INFLUX_ORG , INFLUX_BUCKET ) ;
59+ for ( const event of events ) {
60+ const point = formatMetricPoint ( event . request ) ;
5561
56- function sleep ( ms ) {
57- return new Promise ( resolve => {
58- setTimeout ( resolve , ms )
59- } )
60- }
62+ console . log ( point )
63+ writeApi . writePoint ( point ) ;
6164
62- function getRequestData ( request , response , startTime , endTime ) {
63- const cfData = request . cf || { } ;
64- const timestamp = Math . floor ( Date . now ( ) / 1000 ) ;
65- const originResponse = response || { } ;
66- return {
67- 'timestamp' : timestamp ,
68- 'userAgent' : request . headers . get ( 'user-agent' ) ,
69- 'referer' : request . headers . get ( 'Referer' ) ,
70- 'ip' : request . headers . get ( 'CF-Connecting-IP' ) ,
71- 'countryCode' : cfData . country ,
72- 'url' : request . url ,
73- 'method' : request . method ,
74- 'status' : originResponse . status ,
75- 'originTime' : ( endTime - startTime ) ,
76- 'cfCache' : ( originResponse ) ? ( response . headers . get ( 'CF-Cache-Status' ) || 'miss' ) : 'miss' ,
77- } ;
78-
79- }
80-
81- function formMetricLine ( data ) {
82- let referer ;
83- const url = new URL ( data . url ) ;
84- const utmSource = url . searchParams . get ( 'utm_source' ) || 'empty' ;
85- const ua = parser ( data . userAgent ) ;
86- try {
87- referer = new URL ( data . referer ) ;
88- } catch {
89- referer = {
90- hostname : 'empty'
91- } ;
9265 }
93- 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 } `
94- }
9566
96- async function sendMetricsToInfuxDB ( ) {
97- const metrics = requests . map ( formMetricLine ) . join ( '\n' ) ;
98- try {
99- return fetch ( INFLUXDB_URL , {
100- method : 'POST' ,
101- body : metrics ,
102- } ) . then ( function ( r ) {
103- return r ;
104- } ) ;
105- } catch ( err ) {
106- console . log ( err . stack || err ) ;
107- }
67+ ctx . waitUntil (
68+ writeApi . close ( )
69+ )
10870}
71+
72+ export default {
73+ fetch : handleRequest ,
74+ tail : handleMetrics ,
75+ }
0 commit comments