Skip to content

Commit

Permalink
Update to TS SDK 1.4.0, add additional tracing spans (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-chambers authored Feb 23, 2024
1 parent d884c56 commit e6090cd
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 51 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ This changelog documents the changes between release versions.
## Unreleased
Changes to be included in the next upcoming v0 release

## v0.16.0
- Updated to [NDC TypeScript SDK v1.4.0](https://github.com/hasura/ndc-sdk-typescript/releases/tag/v1.4.0) to include OpenTelemetry improvements. Traced spans should now appear in the Hasura Console
- Additional OpenTelemetry trace spans covering work done around function invocations

## v0.15.0
- OpenTelemetry support added via support for NDC TypeScript SDK v1.3.0 ([#12](https://github.com/hasura/ndc-nodejs-lambda/pull/12))

Expand Down
46 changes: 40 additions & 6 deletions ndc-lambda-sdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ndc-lambda-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hasura/ndc-lambda-sdk",
"version": "0.15.0",
"version": "0.16.0",
"description": "SDK that can automatically expose TypeScript functions as Hasura NDC functions/procedures",
"author": "Hasura",
"license": "Apache-2.0",
Expand Down Expand Up @@ -30,7 +30,7 @@
"url": "git+https://github.com/hasura/ndc-nodejs-lambda.git"
},
"dependencies": {
"@hasura/ndc-sdk-typescript": "^1.3.0",
"@hasura/ndc-sdk-typescript": "^1.4.0",
"@json-schema-tools/meta-schema": "^1.7.0",
"@tsconfig/node18": "^18.2.2",
"commander": "^11.1.0",
Expand Down
95 changes: 55 additions & 40 deletions ndc-lambda-sdk/src/execution.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EOL } from "os";
import * as sdk from "@hasura/ndc-sdk-typescript"
import { withActiveSpan } from "@hasura/ndc-sdk-typescript/instrumentation"
import opentelemetry, { SpanStatusCode } from '@opentelemetry/api';
import pLimit from "p-limit";
import * as schema from "./schema"
Expand All @@ -15,6 +16,9 @@ export type RuntimeFunctions = {
// parallelism going on within a single query
const DEFAULT_PARALLEL_DEGREE = 10;

const FUNCTION_NAME_SPAN_ATTR_NAME = "ndc-lambda-sdk.function_name";
const FUNCTION_INVOCATION_INDEX_SPAN_ATTR_NAME = "ndc-lambda-sdk.function_invocation_index";

export async function executeQuery(queryRequest: sdk.QueryRequest, functionsSchema: schema.FunctionsSchema, runtimeFunctions: RuntimeFunctions): Promise<sdk.QueryResponse> {
const functionName = queryRequest.collection;

Expand All @@ -29,23 +33,36 @@ export async function executeQuery(queryRequest: sdk.QueryRequest, functionsSche
if (runtimeFunction === undefined)
throw new sdk.InternalServerError(`Couldn't find '${functionName}' function exported from hosted functions module.`)

const functionInvocationPreparedArgs = (queryRequest.variables ?? [{}]).map(variables => {
const resolvedArgs = resolveArgumentValues(queryRequest.arguments, variables)
return prepareArguments(resolvedArgs, functionDefinition, functionsSchema.objectTypes);
});
const spanAttributes = { [FUNCTION_NAME_SPAN_ATTR_NAME]: functionName };

const functionInvocationPreparedArgs = withActiveSpan(tracer, "prepare arguments", () =>
(queryRequest.variables ?? [{}]).map(variables => {
const resolvedArgs = resolveArgumentValues(queryRequest.arguments, variables);
return prepareArguments(resolvedArgs, functionDefinition, functionsSchema.objectTypes);
})
, spanAttributes);

const parallelLimit = pLimit(functionDefinition.parallelDegree ?? DEFAULT_PARALLEL_DEGREE);
const functionInvocations: Promise<sdk.RowSet>[] = functionInvocationPreparedArgs.map(invocationPreparedArgs => parallelLimit(async () => {
const result = await invokeFunction(runtimeFunction, invocationPreparedArgs, functionName);
const prunedResult = reshapeResultToNdcResponseValue(result, functionDefinition.resultType, [], queryRequest.query.fields ?? {}, functionsSchema.objectTypes);
return {
aggregates: {},
rows: [
{
__value: prunedResult
}
]
};
const functionInvocations: Promise<sdk.RowSet>[] = functionInvocationPreparedArgs.map((invocationPreparedArgs, invocationIndex) => parallelLimit(async () => {
const invocationSpanAttrs = {...spanAttributes, [FUNCTION_INVOCATION_INDEX_SPAN_ATTR_NAME]: invocationIndex};

return withActiveSpan(tracer, "function invocation", async () => {
const result = await invokeFunction(runtimeFunction, invocationPreparedArgs, functionName);

const prunedResult = withActiveSpan(tracer, "reshape result", () =>
reshapeResultToNdcResponseValue(result, functionDefinition.resultType, [], queryRequest.query.fields ?? {}, functionsSchema.objectTypes)
, invocationSpanAttrs);

return {
aggregates: {},
rows: [
{
__value: prunedResult
}
]
};
}, invocationSpanAttrs);

}));

return await Promise.all(functionInvocations);
Expand Down Expand Up @@ -74,13 +91,21 @@ async function executeMutationOperation(mutationOperation: sdk.MutationOperation
throw new sdk.BadRequest(`'${functionName}' is a '${functionDefinition.ndcKind}' and cannot be queried as a ${schema.FunctionNdcKind.Procedure}.`)
}

const spanAttributes = { [FUNCTION_NAME_SPAN_ATTR_NAME]: functionName };

const runtimeFunction = runtimeFunctions[functionName];
if (runtimeFunction === undefined)
throw new sdk.InternalServerError(`Couldn't find ${functionName} function exported from hosted functions module.`)

const preparedArgs = prepareArguments(mutationOperation.arguments, functionDefinition, functionsSchema.objectTypes);
const preparedArgs = withActiveSpan(tracer, "prepare arguments", () =>
prepareArguments(mutationOperation.arguments, functionDefinition, functionsSchema.objectTypes)
, spanAttributes);

const result = await invokeFunction(runtimeFunction, preparedArgs, functionName);
const prunedResult = reshapeResultToNdcResponseValue(result, functionDefinition.resultType, [], mutationOperation.fields ?? {}, functionsSchema.objectTypes);

const prunedResult = withActiveSpan(tracer, "reshape result", () =>
reshapeResultToNdcResponseValue(result, functionDefinition.resultType, [], mutationOperation.fields ?? {}, functionsSchema.objectTypes)
, spanAttributes);

return {
affected_rows: 1,
Expand Down Expand Up @@ -153,36 +178,26 @@ function coerceArgumentValue(value: unknown, type: schema.TypeReference, valuePa
}

async function invokeFunction(func: Function, preparedArgs: unknown[], functionName: string): Promise<unknown> {
return tracer.startActiveSpan(`Function: ${functionName}`, async (span) => {
span.setAttribute("ndc-lambda-sdk.function_name", functionName);
try {
try {
return await withActiveSpan(tracer, `Function: ${functionName}`, async () => {
const result = func.apply(undefined, preparedArgs);
// Await the result if it is a promise
if (typeof result === "object" && 'then' in result && typeof result.then === "function") {
return await result;
}
return result;
} catch (e) {
if (e instanceof sdk.ConnectorError) {
span.recordException(e);
span.setStatus({ code: SpanStatusCode.ERROR });
throw e;
} else if (e instanceof Error) {
span.recordException(e);
span.setStatus({ code: SpanStatusCode.ERROR });
throw new sdk.InternalServerError(`Error encountered when invoking function '${functionName}'`, getErrorDetails(e));
} else if (typeof e === "string") {
span.recordException(e);
span.setStatus({ code: SpanStatusCode.ERROR });
throw new sdk.InternalServerError(`Error encountered when invoking function '${functionName}'`, { message: e });
} else {
throw new sdk.InternalServerError(`Error encountered when invoking function '${functionName}'`);
}
}
finally {
span.end();
}, { [FUNCTION_NAME_SPAN_ATTR_NAME]: functionName });
} catch (e) {
if (e instanceof sdk.ConnectorError) {
throw e;
} else if (e instanceof Error) {
throw new sdk.InternalServerError(`Error encountered when invoking function '${functionName}'`, getErrorDetails(e));
} else if (typeof e === "string") {
throw new sdk.InternalServerError(`Error encountered when invoking function '${functionName}'`, { message: e });
} else {
throw new sdk.InternalServerError(`Error encountered when invoking function '${functionName}'`);
}
})
}
}

export type ErrorDetails = {
Expand Down
4 changes: 2 additions & 2 deletions yeoman-generator/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion yeoman-generator/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "generator-hasura-ndc-nodejs-lambda",
"version": "0.15.0",
"version": "0.16.0",
"description": "Yeoman generator for Hasura DDN ndc-nodejs-lambda connectors",
"files": [
"generators"
Expand Down

0 comments on commit e6090cd

Please sign in to comment.