Skip to content

Commit 1112a2c

Browse files
committed
fix to send all telemetry attributes
1 parent 33c98b8 commit 1112a2c

File tree

6 files changed

+52
-42
lines changed

6 files changed

+52
-42
lines changed

common.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,19 +142,28 @@ function randomTime(loopCount: number, minWaitInMs: number): number {
142142
return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive
143143
}
144144

145+
export interface WrappedAxiosResponse<R> {
146+
response?: AxiosResponse<R>;
147+
retries: number;
148+
}
149+
145150
export async function attemptHttpRequest<B, R>(
146151
request: AxiosRequestConfig<B>,
147152
config: {
148153
maxRetry: number;
149154
minWaitInMs: number;
150155
},
151156
axiosInstance: AxiosInstance,
152-
): Promise<AxiosResponse<R> | undefined> {
157+
): Promise<WrappedAxiosResponse<R> | undefined> {
153158
let iterationCount = 0;
154159
do {
155160
iterationCount++;
156161
try {
157-
return await axiosInstance(request);
162+
const response = await axiosInstance(request);
163+
return {
164+
response: response,
165+
retries: iterationCount - 1,
166+
};
158167
} catch (err: any) {
159168
if (!isAxiosError(err)) {
160169
throw new FgaError(err);
@@ -194,36 +203,43 @@ export const createRequestFunction = function (axiosArgs: RequestArgs, axiosInst
194203
const maxRetry:number = retryParams ? retryParams.maxRetry : 0;
195204
const minWaitInMs:number = retryParams ? retryParams.minWaitInMs : 0;
196205

197-
const start = Date.now();
206+
const start = performance.now();
198207

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

202-
const axiosRequestArgs = {...axiosArgs.options, url: configuration.getBasePath() + axiosArgs.url};
203-
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, {
204215
maxRetry,
205216
minWaitInMs,
206217
}, axios);
207-
218+
const response = wrappedResponse?.response;
208219
const data = typeof response?.data === "undefined" ? {} : response?.data;
209220
const result: CallResult<any> = { ...data };
210221
setNotEnumerableProperty(result, "$response", response);
211222

212223
let attributes: StringIndexable = {};
213224

214225
attributes = TelemetryAttributes.fromRequest({
226+
userAgent: configuration.baseOptions?.headers["User-Agent"],
227+
httpMethod: axiosArgs.options?.method,
228+
url,
229+
resendCount: wrappedResponse?.retries,
215230
start: start,
216231
credentials: credentials,
217232
attributes: methodAttributes,
218233
});
219234

220235
attributes = TelemetryAttributes.fromResponse({
221236
response,
222-
credentials,
223237
attributes,
224238
});
225239

226-
if (configuration.telemetry?.metrics?.histogramQueryDuration) {
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") {
227243
configuration.telemetry.recorder.histogram(
228244
TelemetryHistograms.queryDuration,
229245
parseInt(attributes[TelemetryAttribute.HttpServerRequestDuration] as string, 10),
@@ -237,7 +253,7 @@ export const createRequestFunction = function (axiosArgs: RequestArgs, axiosInst
237253
if (configuration.telemetry?.metrics?.histogramRequestDuration) {
238254
configuration.telemetry.recorder.histogram(
239255
TelemetryHistograms.requestDuration,
240-
Date.now() - start,
256+
attributes[TelemetryAttribute.HttpClientRequestDuration],
241257
TelemetryAttributes.prepare(
242258
attributes,
243259
configuration.telemetry.metrics.histogramRequestDuration.attributes

credentials/credentials.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ export class Credentials {
2626
private accessToken?: string;
2727
private accessTokenExpiryDate?: Date;
2828

29-
public static init(configuration: { credentials: AuthCredentialsConfig, telemetry: TelemetryConfiguration }): Credentials {
30-
return new Credentials(configuration.credentials, globalAxios, configuration.telemetry);
29+
public static init(configuration: { credentials: AuthCredentialsConfig, telemetry: TelemetryConfiguration, baseOptions?: any }): Credentials {
30+
return new Credentials(configuration.credentials, globalAxios, configuration.telemetry, configuration.baseOptions);
3131
}
3232

33-
public constructor(private authConfig: AuthCredentialsConfig, private axios: AxiosInstance = globalAxios, private telemetryConfig: TelemetryConfiguration) {
33+
public constructor(private authConfig: AuthCredentialsConfig, private axios: AxiosInstance = globalAxios, private telemetryConfig: TelemetryConfiguration, private baseOptions?: any) {
3434
this.initConfig();
3535
this.isValid();
3636
}
@@ -129,9 +129,10 @@ export class Credentials {
129129
*/
130130
private async refreshAccessToken() {
131131
const clientCredentials = (this.authConfig as { method: CredentialsMethod.ClientCredentials; config: ClientCredentialsConfig })?.config;
132+
const url = `https://${clientCredentials.apiTokenIssuer}/oauth/token`;
132133

133134
try {
134-
const response = await attemptHttpRequest<{
135+
const wrappedResponse = await attemptHttpRequest<{
135136
client_id: string,
136137
client_secret: string,
137138
audience: string,
@@ -140,8 +141,8 @@ export class Credentials {
140141
access_token: string,
141142
expires_in: number,
142143
}>({
143-
url: `https://${clientCredentials.apiTokenIssuer}/oauth/token`,
144-
method: "post",
144+
url,
145+
method: "POST",
145146
data: {
146147
client_id: clientCredentials.clientId,
147148
client_secret: clientCredentials.clientSecret,
@@ -156,6 +157,7 @@ export class Credentials {
156157
minWaitInMs: 100,
157158
}, globalAxios);
158159

160+
const response = wrappedResponse?.response;
159161
if (response) {
160162
this.accessToken = response.data.access_token;
161163
this.accessTokenExpiryDate = new Date(Date.now() + response.data.expires_in * 1000);
@@ -166,18 +168,22 @@ export class Credentials {
166168
let attributes = {};
167169

168170
attributes = TelemetryAttributes.fromRequest({
171+
userAgent: this.baseOptions?.headers["User-Agent"],
172+
fgaMethod: "TokenExchange",
173+
url,
174+
resendCount: wrappedResponse?.retries,
175+
httpMethod: "POST",
169176
credentials: clientCredentials,
170-
// resendCount: 0, // TODO: implement resend count tracking, not available in the current context
177+
start: performance.now(),
171178
attributes,
172179
});
173180

174181
attributes = TelemetryAttributes.fromResponse({
175182
response,
176-
credentials: clientCredentials,
177-
attributes,
183+
attributes,
178184
});
179185

180-
attributes = TelemetryAttributes.prepare(attributes);
186+
attributes = TelemetryAttributes.prepare(attributes, this.telemetryConfig.metrics?.counterCredentialsRequest?.attributes);
181187
this.telemetryConfig.recorder.counter(TelemetryCounters.credentialsRequest, 1, attributes);
182188
}
183189

docs/opentelemetry.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ In cases when metrics events are sent, they will not be viewable outside of infr
3333
| `url.full` | `string` | No | The full URL of the request |
3434
| `url.scheme` | `string` | No | HTTP Scheme of the request (http/https) |
3535
| `http.request.resend_count` | `int` | Yes | The number of retries attempted |
36-
| `http.client.request.duration` | `int` | No | Time taken by the FGA server to process and evaluate the request, in milliseconds |
36+
| `http.client.request.duration` | `int` | No | Time taken by the FGA server to process and evaluate the request, rounded to the nearest milliseconds |
3737
| `http.server.request.duration` | `int` | No | The number of retries attempted |
3838

3939
## Default attributes

telemetry/attributes.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class TelemetryAttributes {
6767
attributes[TelemetryAttribute.UrlFull] = url;
6868
}
6969

70-
if (start) attributes[TelemetryAttribute.HttpClientRequestDuration] = Date.now() - start;
70+
if (start) attributes[TelemetryAttribute.HttpClientRequestDuration] = Math.round(performance.now() - start);
7171
if (resendCount) attributes[TelemetryAttribute.HttpRequestResendCount] = resendCount;
7272
if (credentials && credentials.method === "client_credentials") {
7373
attributes[TelemetryAttribute.FgaClientRequestClientId] = credentials.configuration.clientId;
@@ -78,24 +78,22 @@ export class TelemetryAttributes {
7878

7979
static fromResponse({
8080
response,
81-
credentials,
8281
attributes = {},
8382
}: {
8483
response: any;
85-
credentials?: any;
8684
attributes?: Record<string, string | number>;
8785
}): Record<string, string | number> {
8886
if (response?.status) attributes[TelemetryAttribute.HttpResponseStatusCode] = response.status;
8987

9088
const responseHeaders = response?.headers || {};
9189
const responseModelId = responseHeaders["openfga-authorization-model-id"];
92-
const responseQueryDuration = responseHeaders["fga-query-duration-ms"];
93-
94-
if (responseModelId) attributes[TelemetryAttribute.FgaClientResponseModelId] = responseModelId;
95-
if (responseQueryDuration) attributes[TelemetryAttribute.HttpServerRequestDuration] = responseQueryDuration;
90+
const responseQueryDuration = responseHeaders["fga-query-duration-ms"] ? parseInt(responseHeaders["fga-query-duration-ms"], 10) : undefined;
9691

97-
if (credentials && credentials.method === "client_credentials") {
98-
attributes[TelemetryAttribute.FgaClientRequestClientId] = credentials.configuration.clientId;
92+
if (responseModelId) {
93+
attributes[TelemetryAttribute.FgaClientResponseModelId] = responseModelId;
94+
}
95+
if (typeof responseQueryDuration !== "undefined" && Number.isFinite(responseQueryDuration)) {
96+
attributes[TelemetryAttribute.HttpServerRequestDuration] = responseQueryDuration as number;
9997
}
10098

10199
return attributes;

telemetry/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class TelemetryConfiguration implements TelemetryConfig {
5555
// TelemetryAttribute.HttpRequestMethod,
5656
// TelemetryAttribute.UrlFull,
5757
// TelemetryAttribute.HttpClientRequestDuration,
58-
// TelemetryAttribute.HttpServerRequestDuration
58+
// TelemetryAttribute.HttpServerRequestDuration,
5959

6060
// This not included by default as it has a very high cardinality which could increase costs for users
6161
// TelemetryAttribute.FgaClientUser

tests/telemetry/attributes.test.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe("TelemetryAttributes", () => {
6565
// Verify line 90 is covered - status is correctly set
6666
expect(result["http.response.status_code"]).toEqual(200);
6767
expect(result["fga-client.response.model_id"]).toEqual("model-id");
68-
expect(result["http.server.request.duration"]).toEqual("10");
68+
expect(result["http.server.request.duration"]).toEqual(10);
6969
});
7070

7171
test("should handle response without status correctly", () => {
@@ -75,16 +75,6 @@ describe("TelemetryAttributes", () => {
7575
// Verify that no status code is set when response does not have a status
7676
expect(result["http.response.status_code"]).toBeUndefined();
7777
expect(result["fga-client.response.model_id"]).toEqual("model-id");
78-
expect(result["http.server.request.duration"]).toEqual("10");
79-
});
80-
81-
test("should create attributes from response with client credentials", () => {
82-
const response = { status: 200, headers: {} };
83-
const credentials = { method: "client_credentials", configuration: { clientId: "client-id" } };
84-
const result = TelemetryAttributes.fromResponse({ response, credentials });
85-
86-
// Check that the client ID is set correctly from the credentials
87-
expect(result["http.response.status_code"]).toEqual(200);
88-
expect(result["fga-client.request.client_id"]).toEqual("client-id");
78+
expect(result["http.server.request.duration"]).toEqual(10);
8979
});
9080
});

0 commit comments

Comments
 (0)