metrics!!!!!!!!!!!
All checks were successful
Publish Docker Images / docker (push) Successful in 1m52s

This commit is contained in:
Lee 2023-11-16 17:52:44 +00:00
parent 6b08b8fc7a
commit 6003c2436a
12 changed files with 533 additions and 102 deletions

@ -18,6 +18,8 @@
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"utils": "workspace:*", "utils": "workspace:*",
"node-cache": "^5.1.2" "node-cache": "^5.1.2",
"@influxdata/influxdb-client": "^1.33.2",
"mongoose": "^7.6.3"
} }
} }

@ -0,0 +1,11 @@
import mongoose, { Model } from "mongoose";
const { Schema } = mongoose;
const metricsSchema = new Schema({
_id: String,
totalRequests: Number,
});
export const MetricsSchema =
(mongoose.models.Metrics as Model<typeof metricsSchema>) ||
mongoose.model("Metrics", metricsSchema);

@ -0,0 +1,18 @@
import mongoose from "mongoose";
/**
* Creates a connection to Mongo
*
* @param uri the URI of the mongo instance
* @returns the mongoose connection
*/
export async function connectMongo(uri: string) {
// Check if mongoose is already connected
if (mongoose.connection.readyState) {
return;
}
return mongoose.connect(uri, {
autoCreate: true,
family: 4,
});
}

@ -1,9 +1,18 @@
import { QueryApi, WriteApi } from "@influxdata/influxdb-client";
import dotenv from "dotenv"; import dotenv from "dotenv";
import { RouteManager, RouteMessages, createServer } from "server"; import { RouteManager, RouteMessages, createServer } from "server";
import { checkEnvironmentVariables } from "utils"; import { checkEnvironmentVariables, createInfluxClient } from "utils";
import { connectMongo } from "./db/mongo";
import NodeManager from "./node/nodeManager"; import NodeManager from "./node/nodeManager";
import ProxyRoute from "./routes/proxy"; import ProxyRoute from "./routes/proxy";
import { initSecrets } from "./secrets"; import {
INFLUXDB_BUCKET,
INFLUXDB_ORG,
INFLUXDB_TOKEN,
INFLUXDB_URL,
MONGO_URI,
initSecrets,
} from "./secrets";
dotenv.config(); // load .env file dotenv.config(); // load .env file
@ -13,27 +22,48 @@ if (!envVarsValid) {
process.exit(1); process.exit(1);
} }
export let InfluxWriteApi: WriteApi;
export let InfluxQueryApi: QueryApi;
/** /**
* The node manager for all of the loaded nodes * The node manager for all of the loaded nodes
*/ */
export const nodeManager = new NodeManager(); export const nodeManager = new NodeManager();
const server = createServer({ (async () => {
port: process.env.API_PORT || 3000, await initSecrets(process.env.INFISICAL_TOKEN!); // Load the infisical secrets
onLoaded: async () => {
await initSecrets(process.env.INFISICAL_TOKEN!); // Load the infisical secrets
const routeManager = new RouteManager(); // Init MongoDB
routeManager.addRoute(new ProxyRoute()); await connectMongo(MONGO_URI);
server.all("*", (req, res) => { const routeManager = new RouteManager();
// Handle all paths to the proxy route routeManager.addRoute(new ProxyRoute());
const routes = routeManager.getRoutes();
if (!routes || routes.length === 0 || !routes[0]) { // Init InfluxDB
res.status(500).json(RouteMessages.internalServerError("No routes")); const influxClient = createInfluxClient(
return; INFLUXDB_TOKEN,
} INFLUXDB_URL,
routes[0].handle(req, res); INFLUXDB_ORG,
}); INFLUXDB_BUCKET
}, );
}); if (!influxClient) {
console.log("Influx client failed to initialize, exiting...");
process.exit(1);
}
InfluxWriteApi = influxClient.influxWriteClient;
InfluxQueryApi = influxClient.influxQueryClient;
const server = createServer({
port: process.env.API_PORT || 3000,
});
server.all("*", (req, res) => {
// Handle all paths to the proxy route
const routes = routeManager.getRoutes();
if (!routes || routes.length === 0 || !routes[0]) {
res.status(500).json(RouteMessages.internalServerError("No routes"));
return;
}
routes[0].handle(req, res);
});
})();

@ -1,10 +1,18 @@
import { Point } from "@influxdata/influxdb-client";
import { Request, Response } from "express"; import { Request, Response } from "express";
import Cache from "node-cache"; import Cache from "node-cache";
import { Route, RouteMessages } from "server"; import { Route, RouteMessages } from "server";
import { nodeManager } from ".."; import { InfluxWriteApi, nodeManager } from "..";
import { MetricsSchema } from "../db/metrics";
const IGNORED_PATHS = ["/favicon.ico"]; const IGNORED_PATHS = ["/favicon.ico"];
/**
* The total requests that have been made
* TODO: move this to a metrics file
*/
let totalRequests: number | undefined;
const cache = new Cache({ const cache = new Cache({
stdTTL: 300, // 5 minutes stdTTL: 300, // 5 minutes
}); });
@ -36,6 +44,56 @@ function log(
); );
} }
type InfluxLog = {
nodeId: string;
url: string;
status: number;
time?: number;
cached?: boolean;
};
/**
* Creates an InfluxDB point and writes it to the database
*
* @param nodeId the node ID that handled the request
* @param url the URL of the request
* @param status the status code of the request
* @param time the time it took to handle the request (or true if it was cached)
*/
async function logRequestToDatabase({
nodeId,
url,
status,
time,
cached,
}: InfluxLog) {
totalRequests = totalRequests ? totalRequests + 1 : 1;
MetricsSchema.updateOne(
{ _id: "proxy" },
{ $set: { totalRequests: totalRequests } },
{ upsert: true }
).exec();
const point = new Point("proxy");
point.tag("type", "request");
point.tag("node", nodeId);
point.stringField("url", url);
point.intField("status", status);
if (cached) {
point.tag("cached", "true");
} else {
point.intField("time", time as number);
}
try {
InfluxWriteApi.writePoint(point);
InfluxWriteApi.writePoint(
new Point("proxy").intField("totalRequests", totalRequests)
);
} catch (ex) {
console.log("Failed to write to influx");
console.log(ex);
}
}
export default class ProxyRoute extends Route { export default class ProxyRoute extends Route {
constructor() { constructor() {
super({ path: "/" }); super({ path: "/" });
@ -63,11 +121,17 @@ export default class ProxyRoute extends Route {
try { try {
const cachedRequest = cache.get<CachedRequest>(url); const cachedRequest = cache.get<CachedRequest>(url);
if (cachedRequest) { if (cachedRequest) {
log(cachedRequest.nodeId, url, cachedRequest.status, true);
res res
.status(cachedRequest.status) .status(cachedRequest.status)
.set(cachedRequest.headers) .set(cachedRequest.headers)
.json(cachedRequest.data); .json(cachedRequest.data);
log(cachedRequest.nodeId, url, cachedRequest.status, true);
logRequestToDatabase({
nodeId: cachedRequest.nodeId,
url,
status: cachedRequest.status,
cached: true,
});
return; return;
} }
@ -91,6 +155,12 @@ export default class ProxyRoute extends Route {
headers: response.headers, headers: response.headers,
data: data, data: data,
} as CachedRequest); } as CachedRequest);
logRequestToDatabase({
nodeId,
url,
status: response.status,
time: Date.now() - before,
});
} catch (ex: any) { } catch (ex: any) {
res.status(500).json(RouteMessages.internalServerError(ex.message || ex)); res.status(500).json(RouteMessages.internalServerError(ex.message || ex));
} }

@ -2,6 +2,15 @@ import { createInfisicalClient } from "utils";
export let PROXY_SECRET: string; export let PROXY_SECRET: string;
// InfluxDB
export let INFLUXDB_URL: string;
export let INFLUXDB_ORG: string;
export let INFLUXDB_BUCKET: string;
export let INFLUXDB_TOKEN: string;
// MongoDB
export let MONGO_URI: string;
/** /**
* Initialize the secrets from Infisical * Initialize the secrets from Infisical
*/ */
@ -9,12 +18,54 @@ export async function initSecrets(token: string) {
console.log("Initializing secrets..."); console.log("Initializing secrets...");
const infisicalClient = createInfisicalClient(token); const infisicalClient = createInfisicalClient(token);
const proxySecret = (await infisicalClient.getSecret("PROXY_SECRET")) const proxySecret = (await infisicalClient.getSecret("PROXY_SECRET"))
.secretValue; .secretValue;
// InfluxDB
const influxDBUrl = (await infisicalClient.getSecret("INFLUXDB_URL"))
.secretValue;
const influxDBOrg = (await infisicalClient.getSecret("INFLUXDB_ORG"))
.secretValue;
const influxDBBucket = (await infisicalClient.getSecret("INFLUXDB_BUCKET"))
.secretValue;
const influxDBToken = (await infisicalClient.getSecret("INFLUXDB_TOKEN"))
.secretValue;
// Mongo
const mongoUri = (await infisicalClient.getSecret("MONGO_URI")).secretValue;
if (!proxySecret) { if (!proxySecret) {
throw new Error("PROXY_SECRET not set in Infisical"); throw new Error("PROXY_SECRET not set in Infisical");
} }
// InfluxDB
if (!influxDBUrl) {
throw new Error("INFLUXDB_URL not set in Infisical");
}
if (!influxDBOrg) {
throw new Error("INFLUXDB_ORG not set in Infisical");
}
if (!influxDBBucket) {
throw new Error("INFLUXDB_BUCKET not set in Infisical");
}
if (!influxDBToken) {
throw new Error("INFLUXDB_TOKEN not set in Infisical");
}
// Mongo
if (!mongoUri) {
throw new Error("MONGO_URI not set in Infisical");
}
PROXY_SECRET = proxySecret; PROXY_SECRET = proxySecret;
// InfluxDB
INFLUXDB_URL = influxDBUrl;
INFLUXDB_ORG = influxDBOrg;
INFLUXDB_BUCKET = influxDBBucket;
INFLUXDB_TOKEN = influxDBToken;
// Mongo
MONGO_URI = mongoUri;
} }

@ -1,3 +1,6 @@
{ {
"extends": "tsconfig/server.json" "extends": "tsconfig/server.json",
"compilerOptions": {
"esModuleInterop": true
}
} }

@ -9,17 +9,19 @@
"format": "prettier --write \"**/*.{ts,tsx,md}\"" "format": "prettier --write \"**/*.{ts,tsx,md}\""
}, },
"devDependencies": { "devDependencies": {
"@influxdata/influxdb-client": "^1.33.2",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.48.0", "eslint": "^8.48.0",
"infisical-node": "^1.5.0",
"node-cache": "^5.1.2",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"tsconfig": "workspace:*", "tsconfig": "workspace:*",
"tsup": "^7.2.0",
"turbo": "latest" "turbo": "latest"
}, },
"dependencies": { "dependencies": {
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"infisical-node": "^1.5.0", "mongoose": "7.6.3"
"node-cache": "^5.1.2",
"tsup": "^7.2.0"
} }
} }

@ -1,2 +1,3 @@
export * from "./envVariables"; export * from "./envVariables";
export * from "./secrets"; export * from "./influx/influx";
export * from "./secrets/secrets";

@ -0,0 +1,29 @@
import { InfluxDB } from "@influxdata/influxdb-client";
/**
* Creates an InfluxDB client
*
* @param token the influx token
* @param url the url of the influx instance
* @param org the org of the influx instance
* @param bucket the bucket of the influx instance
* @returns
*/
function createInfluxClient(
token: string,
url: string,
org: string,
bucket: string
) {
const client = new InfluxDB({
url: url,
token: token,
});
return {
influxWriteClient: client.getWriteApi(org, bucket, "ms"),
influxQueryClient: client.getQueryApi(org),
};
}
export { createInfluxClient };

File diff suppressed because it is too large Load Diff