diff --git a/.changeset/few-wolves-divide.md b/.changeset/few-wolves-divide.md new file mode 100644 index 00000000..543d3b50 --- /dev/null +++ b/.changeset/few-wolves-divide.md @@ -0,0 +1,5 @@ +--- +"@saleor/app-sdk": patch +--- + +Added OTEL for VercelKV APL. This APL is still experimental, so the change is marked as patch diff --git a/src/APL/saleor-cloud/saleor-cloud-apl.ts b/src/APL/saleor-cloud/saleor-cloud-apl.ts index 0c749713..7405f1f3 100644 --- a/src/APL/saleor-cloud/saleor-cloud-apl.ts +++ b/src/APL/saleor-cloud/saleor-cloud-apl.ts @@ -146,7 +146,7 @@ export class SaleorCloudAPL implements APL { debug("Will fetch data from SaleorCloudAPL for saleorApiUrl %s", saleorApiUrl); return this.tracer.startActiveSpan( - "Call SaleorCloudAPL GET", + "SaleorCloudAPL.get", { attributes: { saleorApiUrl, diff --git a/src/APL/vercel-kv/vercel-kv-apl.ts b/src/APL/vercel-kv/vercel-kv-apl.ts index 8844d4ec..65fcbaed 100644 --- a/src/APL/vercel-kv/vercel-kv-apl.ts +++ b/src/APL/vercel-kv/vercel-kv-apl.ts @@ -1,5 +1,8 @@ +import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; +import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; import { kv } from "@vercel/kv"; +import { getOtelTracer, OTEL_APL_SERVICE_NAME } from "../../open-telemetry"; import { APL, AplConfiguredResult, AplReadyResult, AuthData } from "../apl"; import { createAPLDebug } from "../apl-debug"; @@ -10,6 +13,8 @@ type Params = { export class VercelKvApl implements APL { private debug = createAPLDebug("VercelKvApl"); + private tracer = getOtelTracer(); + /** * Store all items inside hash collection, to enable read ALL items when needed. * Otherwise, multiple redis calls will be needed to iterate over every key. @@ -29,44 +34,133 @@ export class VercelKvApl implements APL { async get(saleorApiUrl: string): Promise { this.debug("Will call Vercel KV to get auth data for %s", saleorApiUrl); - try { - const authData = await kv.hget(this.hashCollectionKey, saleorApiUrl); - - return authData ?? undefined; - } catch (e) { - this.debug("Failed to get auth data from Vercel KV"); - this.debug(e); - - throw e; - } + return this.tracer.startActiveSpan( + "VercelKvApl.get", + { + attributes: { + saleorApiUrl, + [SemanticAttributes.PEER_SERVICE]: OTEL_APL_SERVICE_NAME, + [SemanticAttributes.HTTP_METHOD]: "GET", + }, + kind: SpanKind.CLIENT, + }, + async (span) => { + try { + const authData = await kv.hget(this.hashCollectionKey, saleorApiUrl); + + this.debug("Received response from VercelKV"); + + if (!authData) { + this.debug("AuthData is empty for %s", saleorApiUrl); + } + + span + .setStatus({ + code: 200, + message: "Received response from VercelKV", + }) + .end(); + + return authData ?? undefined; + } catch (e) { + this.debug("Failed to get auth data from Vercel KV"); + this.debug(e); + + span.recordException("Failed to get auth data from Vercel KV"); + + span + .setStatus({ + code: SpanStatusCode.ERROR, + message: "Failed to get auth data from Vercel KV", + }) + .end(); + + throw e; + } + } + ); } async set(authData: AuthData): Promise { this.debug("Will call Vercel KV to set auth data for %s", authData.saleorApiUrl); - try { - await kv.hset(this.hashCollectionKey, { - [authData.saleorApiUrl]: authData, - }); - } catch (e) { - this.debug("Failed to set auth data in Vercel KV"); - this.debug(e); - - throw e; - } + return this.tracer.startActiveSpan( + "VercelKvApl.set", + { + attributes: { + saleorApiUrl: authData.saleorApiUrl, + [SemanticAttributes.PEER_SERVICE]: OTEL_APL_SERVICE_NAME, + [SemanticAttributes.HTTP_METHOD]: "POST", + }, + kind: SpanKind.CLIENT, + }, + async (span) => { + try { + await kv.hset(this.hashCollectionKey, { + [authData.saleorApiUrl]: authData, + }); + + span + .setStatus({ + code: 200, + message: "Successfully written auth data to VercelKV", + }) + .end(); + } catch (e) { + this.debug("Failed to set auth data in Vercel KV"); + this.debug(e); + + span.recordException("Failed to set auth data in Vercel KV"); + span + .setStatus({ + code: SpanStatusCode.ERROR, + }) + .end(); + + throw e; + } + } + ); } async delete(saleorApiUrl: string) { this.debug("Will call Vercel KV to delete auth data for %s", saleorApiUrl); - try { - await kv.hdel(this.hashCollectionKey, saleorApiUrl); - } catch (e) { - this.debug("Failed to delete auth data from Vercel KV"); - this.debug(e); - - throw e; - } + return this.tracer.startActiveSpan( + "VercelKvApl.delete", + { + attributes: { + saleorApiUrl, + [SemanticAttributes.PEER_SERVICE]: OTEL_APL_SERVICE_NAME, + [SemanticAttributes.HTTP_METHOD]: "DELETE", + }, + kind: SpanKind.CLIENT, + }, + async (span) => { + try { + await kv.hdel(this.hashCollectionKey, saleorApiUrl); + + span + .setStatus({ + code: 200, + message: "Successfully deleted auth data to VercelKV", + }) + .end(); + } catch (e) { + this.debug("Failed to delete auth data from Vercel KV"); + this.debug(e); + + span.recordException("Failed to delete auth data from Vercel KV"); + span + .setStatus({ + code: SpanStatusCode.ERROR, + }) + .end(); + + throw e; + } + } + ); } async getAll() {