Skip to content

Commit 66ebf75

Browse files
authored
feat: enhancements to OpenTelemetry support (#149)
2 parents 09191e7 + be94b36 commit 66ebf75

22 files changed

+823
-191
lines changed

api.ts

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
CallResult,
2525
PromiseResult
2626
} from "./common";
27-
import { attributeNames } from "./telemetry";
2827
import { Configuration } from "./configuration";
2928
import { Credentials } from "./credentials";
3029
import { assertParamExists } from "./validation";
@@ -110,6 +109,7 @@ import {
110109
WriteRequestDeletes,
111110
WriteRequestWrites,
112111
} from "./apiModel";
112+
import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes";
113113

114114

115115
/**
@@ -759,10 +759,10 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
759759
async check(storeId: string, body: CheckRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<CheckResponse>> {
760760
const localVarAxiosArgs = localVarAxiosParamCreator.check(storeId, body, options);
761761
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
762-
[attributeNames.requestMethod]: "check",
763-
[attributeNames.requestStoreId]: storeId,
764-
[attributeNames.requestModelId]: body.authorization_model_id,
765-
[attributeNames.user]: body.tuple_key.user
762+
[TelemetryAttribute.FgaClientRequestMethod]: "Check",
763+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
764+
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
765+
[TelemetryAttribute.FgaClientUser]: body.tuple_key.user
766766
});
767767
},
768768
/**
@@ -775,7 +775,7 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
775775
async createStore(body: CreateStoreRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<CreateStoreResponse>> {
776776
const localVarAxiosArgs = localVarAxiosParamCreator.createStore(body, options);
777777
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
778-
[attributeNames.requestMethod]: "createStore",
778+
[TelemetryAttribute.FgaClientRequestMethod]: "CreateStore",
779779
});
780780
},
781781
/**
@@ -788,8 +788,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
788788
async deleteStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<void>> {
789789
const localVarAxiosArgs = localVarAxiosParamCreator.deleteStore(storeId, options);
790790
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
791-
[attributeNames.requestMethod]: "deleteStore",
792-
[attributeNames.requestStoreId]: storeId,
791+
[TelemetryAttribute.FgaClientRequestMethod]: "DeleteStore",
792+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
793793
});
794794
},
795795
/**
@@ -803,9 +803,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
803803
async expand(storeId: string, body: ExpandRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ExpandResponse>> {
804804
const localVarAxiosArgs = localVarAxiosParamCreator.expand(storeId, body, options);
805805
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
806-
[attributeNames.requestMethod]: "expand",
807-
[attributeNames.requestModelId]: body.authorization_model_id,
808-
[attributeNames.requestStoreId]: storeId,
806+
[TelemetryAttribute.FgaClientRequestMethod]: "Expand",
807+
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
808+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
809809
});
810810
},
811811
/**
@@ -818,8 +818,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
818818
async getStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<GetStoreResponse>> {
819819
const localVarAxiosArgs = localVarAxiosParamCreator.getStore(storeId, options);
820820
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
821-
[attributeNames.requestMethod]: "getStore",
822-
[attributeNames.requestStoreId]: storeId,
821+
[TelemetryAttribute.FgaClientRequestMethod]: "GetStore",
822+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
823823
});
824824
},
825825
/**
@@ -833,14 +833,14 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
833833
async listObjects(storeId: string, body: ListObjectsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ListObjectsResponse>> {
834834
const localVarAxiosArgs = localVarAxiosParamCreator.listObjects(storeId, body, options);
835835
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
836-
[attributeNames.requestMethod]: "listObjects",
837-
[attributeNames.requestStoreId]: storeId,
838-
[attributeNames.requestModelId]: body.authorization_model_id,
839-
[attributeNames.user]: body.user
836+
[TelemetryAttribute.FgaClientRequestMethod]: "ListObjects",
837+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
838+
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
839+
[TelemetryAttribute.FgaClientUser]: body.user
840840
});
841841
},
842842
/**
843-
* 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.
843+
* 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.
844844
* @summary List all stores
845845
* @param {number} [pageSize]
846846
* @param {string} [continuationToken]
@@ -850,7 +850,7 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
850850
async listStores(pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ListStoresResponse>> {
851851
const localVarAxiosArgs = localVarAxiosParamCreator.listStores(pageSize, continuationToken, options);
852852
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
853-
[attributeNames.requestMethod]: "listStores",
853+
[TelemetryAttribute.FgaClientRequestMethod]: "ListStores",
854854
});
855855
},
856856
/**
@@ -864,9 +864,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
864864
async listUsers(storeId: string, body: ListUsersRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ListUsersResponse>> {
865865
const localVarAxiosArgs = localVarAxiosParamCreator.listUsers(storeId, body, options);
866866
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
867-
[attributeNames.requestMethod]: "listUsers",
868-
[attributeNames.requestStoreId]: storeId,
869-
[attributeNames.requestModelId]: body.authorization_model_id,
867+
[TelemetryAttribute.FgaClientRequestMethod]: "ListUsers",
868+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
869+
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
870870
});
871871
},
872872
/**
@@ -880,12 +880,12 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
880880
async read(storeId: string, body: ReadRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadResponse>> {
881881
const localVarAxiosArgs = localVarAxiosParamCreator.read(storeId, body, options);
882882
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
883-
[attributeNames.requestMethod]: "read",
884-
[attributeNames.requestStoreId]: storeId,
883+
[TelemetryAttribute.FgaClientRequestMethod]: "Read",
884+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
885885
});
886886
},
887887
/**
888-
* 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.
888+
* 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.
889889
* @summary Read assertions for an authorization model ID
890890
* @param {string} storeId
891891
* @param {string} authorizationModelId
@@ -895,9 +895,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
895895
async readAssertions(storeId: string, authorizationModelId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadAssertionsResponse>> {
896896
const localVarAxiosArgs = localVarAxiosParamCreator.readAssertions(storeId, authorizationModelId, options);
897897
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
898-
[attributeNames.requestMethod]: "readAssertions",
899-
[attributeNames.requestStoreId]: storeId,
900-
[attributeNames.requestModelId]: authorizationModelId,
898+
[TelemetryAttribute.FgaClientRequestMethod]: "ReadAssertions",
899+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
900+
[TelemetryAttribute.FgaClientRequestModelId]: authorizationModelId,
901901
});
902902
},
903903
/**
@@ -911,8 +911,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
911911
async readAuthorizationModel(storeId: string, id: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadAuthorizationModelResponse>> {
912912
const localVarAxiosArgs = localVarAxiosParamCreator.readAuthorizationModel(storeId, id, options);
913913
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
914-
[attributeNames.requestMethod]: "readAuthorizationModel",
915-
[attributeNames.requestStoreId]: storeId,
914+
[TelemetryAttribute.FgaClientRequestMethod]: "ReadAuthorizationModel",
915+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
916916
});
917917
},
918918
/**
@@ -927,8 +927,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
927927
async readAuthorizationModels(storeId: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadAuthorizationModelsResponse>> {
928928
const localVarAxiosArgs = localVarAxiosParamCreator.readAuthorizationModels(storeId, pageSize, continuationToken, options);
929929
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
930-
[attributeNames.requestMethod]: "readAuthorizationModels",
931-
[attributeNames.requestStoreId]: storeId,
930+
[TelemetryAttribute.FgaClientRequestMethod]: "ReadAuthorizationModels",
931+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
932932
});
933933
},
934934
/**
@@ -944,8 +944,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
944944
async readChanges(storeId: string, type?: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<ReadChangesResponse>> {
945945
const localVarAxiosArgs = localVarAxiosParamCreator.readChanges(storeId, type, pageSize, continuationToken, options);
946946
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
947-
[attributeNames.requestMethod]: "readChanges",
948-
[attributeNames.requestStoreId]: storeId,
947+
[TelemetryAttribute.FgaClientRequestMethod]: "ReadChanges",
948+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
949949
});
950950
},
951951
/**
@@ -959,9 +959,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
959959
async write(storeId: string, body: WriteRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<object>> {
960960
const localVarAxiosArgs = localVarAxiosParamCreator.write(storeId, body, options);
961961
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
962-
[attributeNames.requestMethod]: "write",
963-
[attributeNames.requestStoreId]: storeId,
964-
[attributeNames.requestModelId]: body.authorization_model_id,
962+
[TelemetryAttribute.FgaClientRequestMethod]: "Write",
963+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
964+
[TelemetryAttribute.FgaClientRequestModelId]: body.authorization_model_id ?? "",
965965
});
966966
},
967967
/**
@@ -976,9 +976,9 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
976976
async writeAssertions(storeId: string, authorizationModelId: string, body: WriteAssertionsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<void>> {
977977
const localVarAxiosArgs = localVarAxiosParamCreator.writeAssertions(storeId, authorizationModelId, body, options);
978978
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
979-
[attributeNames.requestMethod]: "writeAssertions",
980-
[attributeNames.requestStoreId]: storeId,
981-
[attributeNames.requestModelId]: authorizationModelId,
979+
[TelemetryAttribute.FgaClientRequestMethod]: "WriteAssertions",
980+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
981+
[TelemetryAttribute.FgaClientRequestModelId]: authorizationModelId,
982982
});
983983
},
984984
/**
@@ -992,8 +992,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
992992
async writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<WriteAuthorizationModelResponse>> {
993993
const localVarAxiosArgs = localVarAxiosParamCreator.writeAuthorizationModel(storeId, body, options);
994994
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
995-
[attributeNames.requestMethod]: "writeAuthorizationModel",
996-
[attributeNames.requestStoreId]: storeId,
995+
[TelemetryAttribute.FgaClientRequestMethod]: "WriteAuthorizationModel",
996+
[TelemetryAttribute.FgaClientRequestStoreId]: storeId,
997997
});
998998
},
999999
};

common.ts

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313

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

1716
import { Configuration } from "./configuration";
1817
import type { Credentials } from "./credentials";
@@ -26,17 +25,9 @@ import {
2625
FgaError
2726
} from "./errors";
2827
import { setNotEnumerableProperty } from "./utils";
29-
import { buildAttributes } from "./telemetry";
30-
31-
const meter = metrics.getMeter("@openfga/sdk", "0.6.3");
32-
const durationHist = meter.createHistogram("fga-client.request.duration", {
33-
description: "The duration of requests",
34-
unit: "milliseconds",
35-
});
36-
const queryDurationHist = meter.createHistogram("fga-client.query.duration", {
37-
description: "The duration of queries on the FGA server",
38-
unit: "milliseconds",
39-
});
28+
import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes";
29+
import { MetricRecorder } from "./telemetry/metrics";
30+
import { TelemetryHistograms } from "./telemetry/histograms";
4031

4132
/**
4233
*
@@ -127,6 +118,10 @@ export const toPathString = function (url: URL) {
127118

128119
type ObjectOrVoid = object | void;
129120

121+
interface StringIndexable {
122+
[key: string]: any;
123+
}
124+
130125
export type CallResult<T extends ObjectOrVoid> = T & {
131126
$response: AxiosResponse<T>
132127
};
@@ -147,19 +142,28 @@ function randomTime(loopCount: number, minWaitInMs: number): number {
147142
return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive
148143
}
149144

145+
interface WrappedAxiosResponse<R> {
146+
response?: AxiosResponse<R>;
147+
retries: number;
148+
}
149+
150150
export async function attemptHttpRequest<B, R>(
151151
request: AxiosRequestConfig<B>,
152152
config: {
153153
maxRetry: number;
154154
minWaitInMs: number;
155155
},
156156
axiosInstance: AxiosInstance,
157-
): Promise<AxiosResponse<R> | undefined> {
157+
): Promise<WrappedAxiosResponse<R> | undefined> {
158158
let iterationCount = 0;
159159
do {
160160
iterationCount++;
161161
try {
162-
return await axiosInstance(request);
162+
const response = await axiosInstance(request);
163+
return {
164+
response: response,
165+
retries: iterationCount - 1,
166+
};
163167
} catch (err: any) {
164168
if (!isAxiosError(err)) {
165169
throw new FgaError(err);
@@ -192,39 +196,70 @@ export async function attemptHttpRequest<B, R>(
192196
/**
193197
* creates an axios request function
194198
*/
195-
export const createRequestFunction = function (axiosArgs: RequestArgs, axiosInstance: AxiosInstance, configuration: Configuration, credentials: Credentials, methodAttributes: Record<string, unknown> = {}) {
199+
export const createRequestFunction = function (axiosArgs: RequestArgs, axiosInstance: AxiosInstance, configuration: Configuration, credentials: Credentials, methodAttributes: Record<string, string | number> = {}) {
196200
configuration.isValid();
197201

198202
const retryParams = axiosArgs.options?.retryParams ? axiosArgs.options?.retryParams : configuration.retryParams;
199203
const maxRetry:number = retryParams ? retryParams.maxRetry : 0;
200204
const minWaitInMs:number = retryParams ? retryParams.minWaitInMs : 0;
201205

202-
const start = Date.now();
206+
const start = performance.now();
203207

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

207-
const axiosRequestArgs = {...axiosArgs.options, url: configuration.getBasePath() + axiosArgs.url};
208-
const response = await attemptHttpRequest(axiosRequestArgs, {
211+
const url = configuration.getBasePath() + axiosArgs.url;
212+
213+
const axiosRequestArgs = {...axiosArgs.options, url: url};
214+
const wrappedResponse = await attemptHttpRequest(axiosRequestArgs, {
209215
maxRetry,
210216
minWaitInMs,
211217
}, axios);
212-
const executionTime = Date.now() - start;
213-
218+
const response = wrappedResponse?.response;
214219
const data = typeof response?.data === "undefined" ? {} : response?.data;
215220
const result: CallResult<any> = { ...data };
216221
setNotEnumerableProperty(result, "$response", response);
217222

218-
const attributes = buildAttributes(response, configuration.credentials, methodAttributes);
223+
let attributes: StringIndexable = {};
219224

220-
if (response?.headers) {
221-
const duration = response.headers["fga-query-duration-ms"];
222-
if (duration !== undefined) {
223-
queryDurationHist.record(parseInt(duration, 10), attributes);
224-
}
225+
attributes = TelemetryAttributes.fromRequest({
226+
userAgent: configuration.baseOptions?.headers["User-Agent"],
227+
httpMethod: axiosArgs.options?.method,
228+
url,
229+
resendCount: wrappedResponse?.retries,
230+
start: start,
231+
credentials: credentials,
232+
attributes: methodAttributes,
233+
});
234+
235+
attributes = TelemetryAttributes.fromResponse({
236+
response,
237+
attributes,
238+
});
239+
240+
// only if hisogramQueryDuration set AND if response header contains fga-query-duration-ms
241+
const serverRequestDuration = attributes[TelemetryAttribute.HttpServerRequestDuration];
242+
if (configuration.telemetry?.metrics?.histogramQueryDuration && typeof serverRequestDuration !== "undefined") {
243+
configuration.telemetry.recorder.histogram(
244+
TelemetryHistograms.queryDuration,
245+
parseInt(attributes[TelemetryAttribute.HttpServerRequestDuration] as string, 10),
246+
TelemetryAttributes.prepare(
247+
attributes,
248+
configuration.telemetry.metrics.histogramQueryDuration.attributes
249+
)
250+
);
225251
}
226252

227-
durationHist.record(executionTime, attributes);
253+
if (configuration.telemetry?.metrics?.histogramRequestDuration) {
254+
configuration.telemetry.recorder.histogram(
255+
TelemetryHistograms.requestDuration,
256+
attributes[TelemetryAttribute.HttpClientRequestDuration],
257+
TelemetryAttributes.prepare(
258+
attributes,
259+
configuration.telemetry.metrics.histogramRequestDuration.attributes
260+
)
261+
);
262+
}
228263

229264
return result;
230265
};

0 commit comments

Comments
 (0)