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

View File

@@ -136,8 +136,20 @@ declare module "@fastify/request-context" {
interface RequestContextData {
reqId: string;
orgId?: string;
orgName?: string;
userAuthInfo?: {
userId: string;
email: string;
};
projectDetails?: {
id: string;
name: string;
slug: string;
};
identityAuthInfo?: {
identityId: string;
identityName: string;
authMethod: string;
oidc?: {
claims: Record<string, string>;
};

View File

@@ -337,6 +337,12 @@ export const permissionServiceFactory = ({
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) {
throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
}

View File

@@ -1,21 +1,99 @@
import { requestContext } from "@fastify/request-context";
import opentelemetry from "@opentelemetry/api";
import fp from "fastify-plugin";
export const apiMetrics = fp(async (fastify) => {
const apiMeter = opentelemetry.metrics.getMeter("API");
const latencyHistogram = apiMeter.createHistogram("API_latency", {
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) => {
const { method } = request;
const route = request.routerPath;
const { statusCode } = reply;
// Record latency
latencyHistogram.record(reply.elapsedTime, {
route,
method,
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);
});
});

View File

@@ -1,4 +1,4 @@
import { requestContext } from "@fastify/request-context";
import { requestContext, RequestContextData } from "@fastify/request-context";
import { FastifyRequest } from "fastify";
import fp from "fastify-plugin";
import type { JwtPayload } from "jsonwebtoken";
@@ -159,10 +159,11 @@ export const injectIdentity = fp(
switch (authMode) {
case AuthMode.JWT: {
const { user, tokenVersionId, orgId, rootOrgId, parentOrgId } =
const { user, tokenVersionId, orgId, orgName, rootOrgId, parentOrgId } =
await server.services.authToken.fnValidateJwtIdentity(token, subOrganizationSelector);
requestContext.set("orgId", orgId);
requestContext.set("orgName", orgName);
requestContext.set("userAuthInfo", { userId: user.id, email: user.email || "" });
req.auth = {
authMode: AuthMode.JWT,
user,
@@ -186,6 +187,7 @@ export const injectIdentity = fp(
);
const serverCfg = await getServerCfg();
requestContext.set("orgId", identity.orgId);
requestContext.set("orgName", identity.orgName);
req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor,
@@ -198,24 +200,23 @@ export const injectIdentity = fp(
isInstanceAdmin: serverCfg?.adminIdentityIds?.includes(identity.identityId),
token
};
const identityAuthInfo: RequestContextData["identityAuthInfo"] = {
identityId: identity.identityId,
identityName: identity.name,
authMethod: identity.authMethod
};
if (token?.identityAuth?.oidc) {
requestContext.set("identityAuthInfo", {
identityId: identity.identityId,
oidc: token?.identityAuth?.oidc
});
identityAuthInfo.oidc = token?.identityAuth?.oidc;
}
if (token?.identityAuth?.kubernetes) {
requestContext.set("identityAuthInfo", {
identityId: identity.identityId,
kubernetes: token?.identityAuth?.kubernetes
});
identityAuthInfo.kubernetes = token?.identityAuth?.kubernetes;
}
if (token?.identityAuth?.aws) {
requestContext.set("identityAuthInfo", {
identityId: identity.identityId,
aws: token?.identityAuth?.aws
});
identityAuthInfo.aws = token?.identityAuth?.aws;
}
requestContext.set("identityAuthInfo", identityAuthInfo);
break;
}
case AuthMode.SERVICE_TOKEN: {

View File

@@ -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` });
let orgId = "";
let orgName = "";
let rootOrgId = "";
let parentOrgId = "";
if (token.organizationId) {
@@ -235,9 +236,11 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
}
orgId = subOrganization.id;
orgName = subOrganization.name;
rootOrgId = token.organizationId;
parentOrgId = subOrganization.parentOrgId as string;
} else {
const organization = await orgDAL.findOne({ id: token.organizationId });
const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id,
scopeOrgId: token.organizationId,
@@ -253,12 +256,13 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
}
orgId = token.organizationId;
orgName = organization.name;
rootOrgId = token.organizationId;
parentOrgId = token.organizationId;
}
}
return { user, tokenVersionId: token.tokenVersionId, orgId, rootOrgId, parentOrgId };
return { user, tokenVersionId: token.tokenVersionId, orgId, orgName, rootOrgId, parentOrgId };
};
return {

View File

@@ -210,6 +210,7 @@ export const identityAccessTokenServiceFactory = ({
});
}
let orgId = "";
let orgName = "";
let parentOrgId = "";
const identityOrgDetails = await orgDAL.findOne({ id: identityAccessToken.identityScopeOrgId });
const rootOrgId = identityOrgDetails.rootOrgId || identityOrgDetails.id;
@@ -229,8 +230,12 @@ export const identityAccessTokenServiceFactory = ({
throw new BadRequestError({ message: "Identity does not belong to any organization" });
}
orgId = subOrganization.id;
orgName = subOrganization.name;
parentOrgId = subOrganization.parentOrgId as string;
} else {
const organization = await orgDAL.findOne({ id: rootOrgId });
const identityOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identityAccessToken.identityId,
@@ -242,6 +247,7 @@ export const identityAccessTokenServiceFactory = ({
}
orgId = rootOrgId;
orgName = organization.name;
parentOrgId = rootOrgId;
}
@@ -253,7 +259,7 @@ export const identityAccessTokenServiceFactory = ({
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses });
await accessTokenQueue.updateIdentityAccessTokenStatus(identityAccessToken.id, Number(accessTokenNumUses) + 1);
return { ...identityAccessToken, orgId, rootOrgId, parentOrgId };
return { ...identityAccessToken, orgId, rootOrgId, parentOrgId, orgName };
};
return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken };