Skip to content

Commit

Permalink
feat: enhancements to OpenTelemetry support (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmyjames authored Sep 27, 2024
2 parents 09191e7 + be94b36 commit 66ebf75
Show file tree
Hide file tree
Showing 22 changed files with 823 additions and 191 deletions.
84 changes: 42 additions & 42 deletions api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
CallResult,
PromiseResult
} from "./common";
import { attributeNames } from "./telemetry";
import { Configuration } from "./configuration";
import { Credentials } from "./credentials";
import { assertParamExists } from "./validation";
Expand Down Expand Up @@ -110,6 +109,7 @@ import {
WriteRequestDeletes,
WriteRequestWrites,
} from "./apiModel";
import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes";


/**
Expand Down Expand Up @@ -759,10 +759,10 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async check(storeId: string, body: CheckRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<CheckResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.check(storeId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "check",
[attributeNames.requestStoreId]: storeId,
[attributeNames.requestModelId]: body.authorization_model_id,
[attributeNames.user]: body.tuple_key.user
[TelemetryAttribute.FgaClientRequestMethod]: "Check",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
[TelemetryAttribute.FgaClientUser]: body.tuple_key.user
});
},
/**
Expand All @@ -775,7 +775,7 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async createStore(body: CreateStoreRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<CreateStoreResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.createStore(body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "createStore",
[TelemetryAttribute.FgaClientRequestMethod]: "CreateStore",
});
},
/**
Expand All @@ -788,8 +788,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async deleteStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<void>> {
const localVarAxiosArgs = localVarAxiosParamCreator.deleteStore(storeId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "deleteStore",
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "DeleteStore",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
});
},
/**
Expand All @@ -803,9 +803,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async expand(storeId: string, body: ExpandRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ExpandResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.expand(storeId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "expand",
[attributeNames.requestModelId]: body.authorization_model_id,
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "Expand",
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
});
},
/**
Expand All @@ -818,8 +818,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async getStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<GetStoreResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.getStore(storeId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "getStore",
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "GetStore",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
});
},
/**
Expand All @@ -833,14 +833,14 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async listObjects(storeId: string, body: ListObjectsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ListObjectsResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.listObjects(storeId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "listObjects",
[attributeNames.requestStoreId]: storeId,
[attributeNames.requestModelId]: body.authorization_model_id,
[attributeNames.user]: body.user
[TelemetryAttribute.FgaClientRequestMethod]: "ListObjects",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
[TelemetryAttribute.FgaClientUser]: body.user
});
},
/**
* Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores.
* Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores.
* @summary List all stores
* @param {number} [pageSize]
* @param {string} [continuationToken]
Expand All @@ -850,7 +850,7 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async listStores(pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ListStoresResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.listStores(pageSize, continuationToken, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "listStores",
[TelemetryAttribute.FgaClientRequestMethod]: "ListStores",
});
},
/**
Expand All @@ -864,9 +864,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async listUsers(storeId: string, body: ListUsersRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ListUsersResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.listUsers(storeId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "listUsers",
[attributeNames.requestStoreId]: storeId,
[attributeNames.requestModelId]: body.authorization_model_id,
[TelemetryAttribute.FgaClientRequestMethod]: "ListUsers",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
});
},
/**
Expand All @@ -880,12 +880,12 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async read(storeId: string, body: ReadRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.read(storeId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "read",
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "Read",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
});
},
/**
* The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false.
* The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false.
* @summary Read assertions for an authorization model ID
* @param {string} storeId
* @param {string} authorizationModelId
Expand All @@ -895,9 +895,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async readAssertions(storeId: string, authorizationModelId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadAssertionsResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.readAssertions(storeId, authorizationModelId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "readAssertions",
[attributeNames.requestStoreId]: storeId,
[attributeNames.requestModelId]: authorizationModelId,
[TelemetryAttribute.FgaClientRequestMethod]: "ReadAssertions",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestModelId]: authorizationModelId,
});
},
/**
Expand All @@ -911,8 +911,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async readAuthorizationModel(storeId: string, id: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadAuthorizationModelResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.readAuthorizationModel(storeId, id, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "readAuthorizationModel",
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "ReadAuthorizationModel",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
});
},
/**
Expand All @@ -927,8 +927,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async readAuthorizationModels(storeId: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadAuthorizationModelsResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.readAuthorizationModels(storeId, pageSize, continuationToken, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "readAuthorizationModels",
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "ReadAuthorizationModels",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
});
},
/**
Expand All @@ -944,8 +944,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async readChanges(storeId: string, type?: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadChangesResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.readChanges(storeId, type, pageSize, continuationToken, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "readChanges",
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "ReadChanges",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
});
},
/**
Expand All @@ -959,9 +959,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async write(storeId: string, body: WriteRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<object>> {
const localVarAxiosArgs = localVarAxiosParamCreator.write(storeId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "write",
[attributeNames.requestStoreId]: storeId,
[attributeNames.requestModelId]: body.authorization_model_id,
[TelemetryAttribute.FgaClientRequestMethod]: "Write",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
});
},
/**
Expand All @@ -976,9 +976,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async writeAssertions(storeId: string, authorizationModelId: string, body: WriteAssertionsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<void>> {
const localVarAxiosArgs = localVarAxiosParamCreator.writeAssertions(storeId, authorizationModelId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "writeAssertions",
[attributeNames.requestStoreId]: storeId,
[attributeNames.requestModelId]: authorizationModelId,
[TelemetryAttribute.FgaClientRequestMethod]: "WriteAssertions",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestModelId]: authorizationModelId,
});
},
/**
Expand All @@ -992,8 +992,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
async writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<WriteAuthorizationModelResponse>> {
const localVarAxiosArgs = localVarAxiosParamCreator.writeAuthorizationModel(storeId, body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[attributeNames.requestMethod]: "writeAuthorizationModel",
[attributeNames.requestStoreId]: storeId,
[TelemetryAttribute.FgaClientRequestMethod]: "WriteAuthorizationModel",
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
});
},
};
Expand Down
89 changes: 62 additions & 27 deletions common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@


import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { metrics } from "@opentelemetry/api";

import { Configuration } from "./configuration";
import type { Credentials } from "./credentials";
Expand All @@ -26,17 +25,9 @@ import {
FgaError
} from "./errors";
import { setNotEnumerableProperty } from "./utils";
import { buildAttributes } from "./telemetry";

const meter = metrics.getMeter("@openfga/sdk", "0.6.3");
const durationHist = meter.createHistogram("fga-client.request.duration", {
description: "The duration of requests",
unit: "milliseconds",
});
const queryDurationHist = meter.createHistogram("fga-client.query.duration", {
description: "The duration of queries on the FGA server",
unit: "milliseconds",
});
import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes";
import { MetricRecorder } from "./telemetry/metrics";
import { TelemetryHistograms } from "./telemetry/histograms";

/**
*
Expand Down Expand Up @@ -127,6 +118,10 @@ export const toPathString = function (url: URL) {

type ObjectOrVoid = object | void;

interface StringIndexable {
[key: string]: any;
}

export type CallResult<T extends ObjectOrVoid> = T & {
$response: AxiosResponse<T>
};
Expand All @@ -147,19 +142,28 @@ function randomTime(loopCount: number, minWaitInMs: number): number {
return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive
}

interface WrappedAxiosResponse<R> {
response?: AxiosResponse<R>;
retries: number;
}

export async function attemptHttpRequest<B, R>(
request: AxiosRequestConfig<B>,
config: {
maxRetry: number;
minWaitInMs: number;
},
axiosInstance: AxiosInstance,
): Promise<AxiosResponse<R> | undefined> {
): Promise<WrappedAxiosResponse<R> | undefined> {
let iterationCount = 0;
do {
iterationCount++;
try {
return await axiosInstance(request);
const response = await axiosInstance(request);
return {
response: response,
retries: iterationCount - 1,
};
} catch (err: any) {
if (!isAxiosError(err)) {
throw new FgaError(err);
Expand Down Expand Up @@ -192,39 +196,70 @@ export async function attemptHttpRequest<B, R>(
/**
* creates an axios request function
*/
export const createRequestFunction = function (axiosArgs: RequestArgs, axiosInstance: AxiosInstance, configuration: Configuration, credentials: Credentials, methodAttributes: Record<string, unknown> = {}) {
export const createRequestFunction = function (axiosArgs: RequestArgs, axiosInstance: AxiosInstance, configuration: Configuration, credentials: Credentials, methodAttributes: Record<string, string | number> = {}) {
configuration.isValid();

const retryParams = axiosArgs.options?.retryParams ? axiosArgs.options?.retryParams : configuration.retryParams;
const maxRetry:number = retryParams ? retryParams.maxRetry : 0;
const minWaitInMs:number = retryParams ? retryParams.minWaitInMs : 0;

const start = Date.now();
const start = performance.now();

return async (axios: AxiosInstance = axiosInstance) : PromiseResult<any> => {
await setBearerAuthToObject(axiosArgs.options.headers, credentials!);

const axiosRequestArgs = {...axiosArgs.options, url: configuration.getBasePath() + axiosArgs.url};
const response = await attemptHttpRequest(axiosRequestArgs, {
const url = configuration.getBasePath() + axiosArgs.url;

const axiosRequestArgs = {...axiosArgs.options, url: url};
const wrappedResponse = await attemptHttpRequest(axiosRequestArgs, {
maxRetry,
minWaitInMs,
}, axios);
const executionTime = Date.now() - start;

const response = wrappedResponse?.response;
const data = typeof response?.data === "undefined" ? {} : response?.data;
const result: CallResult<any> = { ...data };
setNotEnumerableProperty(result, "$response", response);

const attributes = buildAttributes(response, configuration.credentials, methodAttributes);
let attributes: StringIndexable = {};

if (response?.headers) {
const duration = response.headers["fga-query-duration-ms"];
if (duration !== undefined) {
queryDurationHist.record(parseInt(duration, 10), attributes);
}
attributes = TelemetryAttributes.fromRequest({
userAgent: configuration.baseOptions?.headers["User-Agent"],
httpMethod: axiosArgs.options?.method,
url,
resendCount: wrappedResponse?.retries,
start: start,
credentials: credentials,
attributes: methodAttributes,
});

attributes = TelemetryAttributes.fromResponse({
response,
attributes,
});

// only if hisogramQueryDuration set AND if response header contains fga-query-duration-ms
const serverRequestDuration = attributes[TelemetryAttribute.HttpServerRequestDuration];
if (configuration.telemetry?.metrics?.histogramQueryDuration && typeof serverRequestDuration !== "undefined") {
configuration.telemetry.recorder.histogram(
TelemetryHistograms.queryDuration,
parseInt(attributes[TelemetryAttribute.HttpServerRequestDuration] as string, 10),
TelemetryAttributes.prepare(
attributes,
configuration.telemetry.metrics.histogramQueryDuration.attributes
)
);
}

durationHist.record(executionTime, attributes);
if (configuration.telemetry?.metrics?.histogramRequestDuration) {
configuration.telemetry.recorder.histogram(
TelemetryHistograms.requestDuration,
attributes[TelemetryAttribute.HttpClientRequestDuration],
TelemetryAttributes.prepare(
attributes,
configuration.telemetry.metrics.histogramRequestDuration.attributes
)
);
}

return result;
};
Expand Down
Loading

0 comments on commit 66ebf75

Please sign in to comment.