misc: add total api requests metric

This commit is contained in:
Sheen Capadngan
2025-10-29 01:57:09 +08:00
parent 2830d465aa
commit f6460245f3
6 changed files with 124 additions and 17 deletions
+12
View File
@@ -136,8 +136,20 @@ declare module "@fastify/request-context" {
interface RequestContextData { interface RequestContextData {
reqId: string; reqId: string;
orgId?: string; orgId?: string;
orgName?: string;
userAuthInfo?: {
userId: string;
email: string;
};
projectDetails?: {
id: string;
name: string;
slug: string;
};
identityAuthInfo?: { identityAuthInfo?: {
identityId: string; identityId: string;
identityName: string;
authMethod: string;
oidc?: { oidc?: {
claims: Record<string, string>; claims: Record<string, string>;
}; };
@@ -337,6 +337,12 @@ export const permissionServiceFactory = ({
throw new NotFoundError({ message: `Project with ${projectId} not found` }); throw new NotFoundError({ message: `Project with ${projectId} not found` });
} }
requestContext.set("projectDetails", {
id: projectDetails.id,
name: projectDetails.name,
slug: projectDetails.slug
});
if (projectDetails.orgId !== actorOrgId) { if (projectDetails.orgId !== actorOrgId) {
throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
} }
+78
View File
@@ -1,21 +1,99 @@
import { requestContext } from "@fastify/request-context";
import opentelemetry from "@opentelemetry/api"; import opentelemetry from "@opentelemetry/api";
import fp from "fastify-plugin"; import fp from "fastify-plugin";
export const apiMetrics = fp(async (fastify) => { export const apiMetrics = fp(async (fastify) => {
const apiMeter = opentelemetry.metrics.getMeter("API"); const apiMeter = opentelemetry.metrics.getMeter("API");
const latencyHistogram = apiMeter.createHistogram("API_latency", { const latencyHistogram = apiMeter.createHistogram("API_latency", {
unit: "ms" unit: "ms"
}); });
const infisicalMeter = opentelemetry.metrics.getMeter("Infisical");
const requestCounter = infisicalMeter.createCounter("infisical.http.server.request.count", {
description: "Total number of API requests to Infisical (covers both human users and machine identities)",
unit: "{request}"
});
fastify.addHook("onResponse", async (request, reply) => { fastify.addHook("onResponse", async (request, reply) => {
const { method } = request; const { method } = request;
const route = request.routerPath; const route = request.routerPath;
const { statusCode } = reply; const { statusCode } = reply;
// Record latency
latencyHistogram.record(reply.elapsedTime, { latencyHistogram.record(reply.elapsedTime, {
route, route,
method, method,
statusCode statusCode
}); });
// Get context data
const orgId = requestContext.get("orgId");
const orgName = requestContext.get("orgName");
const userAuthInfo = requestContext.get("userAuthInfo");
const identityAuthInfo = requestContext.get("identityAuthInfo");
const projectDetails = requestContext.get("projectDetails");
// Build attributes object
const attributes: Record<string, string | number> = {
"http.request.method": method,
"http.route": route,
"http.response.status_code": statusCode
};
// Add organization info
if (orgId) {
attributes["infisical.organization.id"] = orgId;
}
if (orgName) {
attributes["infisical.organization.name"] = orgName;
}
// Add user info (for human users)
if (userAuthInfo) {
if (userAuthInfo.userId) {
attributes["infisical.user.id"] = userAuthInfo.userId;
}
if (userAuthInfo.email) {
attributes["infisical.user.email"] = userAuthInfo.email;
}
}
// Add identity info (for machine identities)
if (identityAuthInfo) {
if (identityAuthInfo.identityId) {
attributes["infisical.identity.id"] = identityAuthInfo.identityId;
}
if (identityAuthInfo.identityName) {
attributes["infisical.identity.name"] = identityAuthInfo.identityName;
}
if (identityAuthInfo.authMethod) {
attributes["infisical.auth.method"] = identityAuthInfo.authMethod;
}
}
// Add project info
if (projectDetails) {
if (projectDetails.id) {
attributes["infisical.project.id"] = projectDetails.id;
}
if (projectDetails.name) {
attributes["infisical.project.name"] = projectDetails.name;
}
}
// Add user agent
const userAgent = request.headers["user-agent"];
if (userAgent) {
attributes["user_agent.original"] = userAgent;
}
// Add client IP address
if (request.realIp) {
attributes["client.address"] = request.realIp;
}
requestCounter.add(1, attributes);
}); });
}); });
@@ -1,4 +1,4 @@
import { requestContext } from "@fastify/request-context"; import { requestContext, RequestContextData } from "@fastify/request-context";
import { FastifyRequest } from "fastify"; import { FastifyRequest } from "fastify";
import fp from "fastify-plugin"; import fp from "fastify-plugin";
import type { JwtPayload } from "jsonwebtoken"; import type { JwtPayload } from "jsonwebtoken";
@@ -159,10 +159,11 @@ export const injectIdentity = fp(
switch (authMode) { switch (authMode) {
case AuthMode.JWT: { case AuthMode.JWT: {
const { user, tokenVersionId, orgId, rootOrgId, parentOrgId } = const { user, tokenVersionId, orgId, orgName, rootOrgId, parentOrgId } =
await server.services.authToken.fnValidateJwtIdentity(token, subOrganizationSelector); await server.services.authToken.fnValidateJwtIdentity(token, subOrganizationSelector);
requestContext.set("orgId", orgId); requestContext.set("orgId", orgId);
requestContext.set("orgName", orgName);
requestContext.set("userAuthInfo", { userId: user.id, email: user.email || "" });
req.auth = { req.auth = {
authMode: AuthMode.JWT, authMode: AuthMode.JWT,
user, user,
@@ -186,6 +187,7 @@ export const injectIdentity = fp(
); );
const serverCfg = await getServerCfg(); const serverCfg = await getServerCfg();
requestContext.set("orgId", identity.orgId); requestContext.set("orgId", identity.orgId);
requestContext.set("orgName", identity.orgName);
req.auth = { req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor, actor,
@@ -198,24 +200,23 @@ export const injectIdentity = fp(
isInstanceAdmin: serverCfg?.adminIdentityIds?.includes(identity.identityId), isInstanceAdmin: serverCfg?.adminIdentityIds?.includes(identity.identityId),
token token
}; };
const identityAuthInfo: RequestContextData["identityAuthInfo"] = {
identityId: identity.identityId,
identityName: identity.name,
authMethod: identity.authMethod
};
if (token?.identityAuth?.oidc) { if (token?.identityAuth?.oidc) {
requestContext.set("identityAuthInfo", { identityAuthInfo.oidc = token?.identityAuth?.oidc;
identityId: identity.identityId,
oidc: token?.identityAuth?.oidc
});
} }
if (token?.identityAuth?.kubernetes) { if (token?.identityAuth?.kubernetes) {
requestContext.set("identityAuthInfo", { identityAuthInfo.kubernetes = token?.identityAuth?.kubernetes;
identityId: identity.identityId,
kubernetes: token?.identityAuth?.kubernetes
});
} }
if (token?.identityAuth?.aws) { if (token?.identityAuth?.aws) {
requestContext.set("identityAuthInfo", { identityAuthInfo.aws = token?.identityAuth?.aws;
identityId: identity.identityId,
aws: token?.identityAuth?.aws
});
} }
requestContext.set("identityAuthInfo", identityAuthInfo);
break; break;
} }
case AuthMode.SERVICE_TOKEN: { case AuthMode.SERVICE_TOKEN: {
@@ -210,6 +210,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` }); if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
let orgId = ""; let orgId = "";
let orgName = "";
let rootOrgId = ""; let rootOrgId = "";
let parentOrgId = ""; let parentOrgId = "";
if (token.organizationId) { if (token.organizationId) {
@@ -235,9 +236,11 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
throw new ForbiddenRequestError({ message: "User organization membership is inactive" }); throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
} }
orgId = subOrganization.id; orgId = subOrganization.id;
orgName = subOrganization.name;
rootOrgId = token.organizationId; rootOrgId = token.organizationId;
parentOrgId = subOrganization.parentOrgId as string; parentOrgId = subOrganization.parentOrgId as string;
} else { } else {
const organization = await orgDAL.findOne({ id: token.organizationId });
const orgMembership = await membershipUserDAL.findOne({ const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id, actorUserId: user.id,
scopeOrgId: token.organizationId, scopeOrgId: token.organizationId,
@@ -253,12 +256,13 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
} }
orgId = token.organizationId; orgId = token.organizationId;
orgName = organization.name;
rootOrgId = token.organizationId; rootOrgId = token.organizationId;
parentOrgId = token.organizationId; parentOrgId = token.organizationId;
} }
} }
return { user, tokenVersionId: token.tokenVersionId, orgId, rootOrgId, parentOrgId }; return { user, tokenVersionId: token.tokenVersionId, orgId, orgName, rootOrgId, parentOrgId };
}; };
return { return {
@@ -210,6 +210,7 @@ export const identityAccessTokenServiceFactory = ({
}); });
} }
let orgId = ""; let orgId = "";
let orgName = "";
let parentOrgId = ""; let parentOrgId = "";
const identityOrgDetails = await orgDAL.findOne({ id: identityAccessToken.identityScopeOrgId }); const identityOrgDetails = await orgDAL.findOne({ id: identityAccessToken.identityScopeOrgId });
const rootOrgId = identityOrgDetails.rootOrgId || identityOrgDetails.id; const rootOrgId = identityOrgDetails.rootOrgId || identityOrgDetails.id;
@@ -229,8 +230,12 @@ export const identityAccessTokenServiceFactory = ({
throw new BadRequestError({ message: "Identity does not belong to any organization" }); throw new BadRequestError({ message: "Identity does not belong to any organization" });
} }
orgId = subOrganization.id; orgId = subOrganization.id;
orgName = subOrganization.name;
parentOrgId = subOrganization.parentOrgId as string; parentOrgId = subOrganization.parentOrgId as string;
} else { } else {
const organization = await orgDAL.findOne({ id: rootOrgId });
const identityOrgMembership = await membershipIdentityDAL.findOne({ const identityOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization, scope: AccessScope.Organization,
actorIdentityId: identityAccessToken.identityId, actorIdentityId: identityAccessToken.identityId,
@@ -242,6 +247,7 @@ export const identityAccessTokenServiceFactory = ({
} }
orgId = rootOrgId; orgId = rootOrgId;
orgName = organization.name;
parentOrgId = rootOrgId; parentOrgId = rootOrgId;
} }
@@ -253,7 +259,7 @@ export const identityAccessTokenServiceFactory = ({
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses }); await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses });
await accessTokenQueue.updateIdentityAccessTokenStatus(identityAccessToken.id, Number(accessTokenNumUses) + 1); await accessTokenQueue.updateIdentityAccessTokenStatus(identityAccessToken.id, Number(accessTokenNumUses) + 1);
return { ...identityAccessToken, orgId, rootOrgId, parentOrgId }; return { ...identityAccessToken, orgId, rootOrgId, parentOrgId, orgName };
}; };
return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken }; return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken };