From 3d70ff4a878521437365a1e5e79c3e3b3d5a9a13 Mon Sep 17 00:00:00 2001 From: Andreas Thomas Date: Tue, 22 Nov 2022 14:27:40 +0100 Subject: [PATCH 1/5] feat: telemetry --- README.md | 16 +++++++++ cmd/build.ts | 13 ++++++- .../src/index.ts | 2 +- mod.ts | 14 +++++++- pkg/http.ts | 36 +++++++++++++++++-- pkg/index.ts | 1 - pkg/test-utils.ts | 10 ++++-- platforms/cloudflare.ts | 21 +++++++++-- platforms/fastly.ts | 1 + platforms/node_with_fetch.ts | 14 +++++++- platforms/nodejs.ts | 16 +++++++-- version.ts | 1 + 12 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 version.ts diff --git a/README.md b/README.md index e43c7bee..36ba6521 100644 --- a/README.md +++ b/README.md @@ -100,3 +100,19 @@ the url and token ```sh UPSTASH_REDIS_REST_URL=".." UPSTASH_REDIS_REST_TOKEN=".." deno test -A ``` + +### Telemetry + +This library sends anonymous telemetry data to help us improve your experience. +We collect the following: + +- SDK verrsion +- Platform (Deno, Cloudflare, Vercel) +- Runtime version (node@18.x) + +You can opt out by setting the `UPSTASH_DISABLE_TELEMETRY` environment variable +to any truthy value. + +```sh +UPSTASH_DISABLE_TELEMETRY=1 +``` diff --git a/cmd/build.ts b/cmd/build.ts index 9fb48cce..8fe4f11d 100644 --- a/cmd/build.ts +++ b/cmd/build.ts @@ -5,6 +5,17 @@ const outDir = "./dist"; await dnt.emptyDir(outDir); +const version = Deno.args.length > 0 ? Deno.args[0] : "development"; +Deno.writeFileSync( + "version.ts", + new TextEncoder().encode(`export const VERSION = "${version}"`), +); + +await Deno.run({ + cmd: [packageManager, "uninstall", "@types/node"], + stdout: "piped", +}).output(); + await dnt.build({ packageManager, entryPoints: [ @@ -39,7 +50,7 @@ await dnt.build({ package: { // package.json properties name: "@upstash/redis", - version: Deno.args[0], + version, description: "An HTTP/REST based Redis client built on top of Upstash REST API.", repository: { diff --git a/examples/cloudflare-workers-with-typescript/src/index.ts b/examples/cloudflare-workers-with-typescript/src/index.ts index 2226ca40..167e2319 100644 --- a/examples/cloudflare-workers-with-typescript/src/index.ts +++ b/examples/cloudflare-workers-with-typescript/src/index.ts @@ -15,6 +15,6 @@ export default { const count = await redis.incr("cloudflare-workers-with-typescript-count"); - return new Response(JSON.stringify({ count })); + return new Response(JSON.stringify({ count, env })); }, }; diff --git a/mod.ts b/mod.ts index 2bb393d0..3ef22685 100644 --- a/mod.ts +++ b/mod.ts @@ -1,6 +1,12 @@ -import { HttpClient, RequesterConfig, RetryConfig } from "./pkg/http.ts"; +import { + HttpClient, + HttpClientConfig, + RequesterConfig, + RetryConfig, +} from "./pkg/http.ts"; import * as core from "./pkg/redis.ts"; export type { Requester, UpstashRequest, UpstashResponse } from "./pkg/http.ts"; +import { VERSION } from "./version.ts"; /** * Connection credentials for upstash redis. @@ -62,11 +68,17 @@ export class Redis extends core.Redis { ); } + const telemetry: HttpClientConfig["telemetry"] = {}; + if (!Deno.env.get("UPSTASH_DISABLE_TELEMETRY")) { + telemetry.runtime = `deno@${Deno.version.deno}`; + telemetry.sdk = `@upstash/redis@${VERSION}`; + } const client = new HttpClient({ retry: config.retry, baseUrl: config.url, headers: { authorization: `Bearer ${config.token}` }, responseEncoding: config.responseEncoding, + telemetry, }); super(client, { diff --git a/pkg/http.ts b/pkg/http.ts index dea915a9..43dbaecf 100644 --- a/pkg/http.ts +++ b/pkg/http.ts @@ -80,11 +80,30 @@ export type HttpClientConfig = { options?: Options; retry?: RetryConfig; agent?: any; + telemetry?: { + /** + * Upstash-Telemetry-Sdk + * @example @upstash/redis@v1.1.1 + */ + sdk?: string; + + /** + * Upstash-Telemetry-Platform + * @example cloudflare + */ + platform?: string; + + /** + * Upstash-Telemetry-Runtime + * @example node@v18 + */ + runtime?: string; + }; } & RequesterConfig; export class HttpClient implements Requester { public baseUrl: string; - public headers: Record; + public headers: Record; public readonly options: { backend?: string; agent: any; @@ -107,8 +126,12 @@ export class HttpClient implements Requester { this.headers = { "Content-Type": "application/json", + "Upstash-Telemetry-Runtime": config.telemetry?.runtime, + "Upstash-Telemetry-Platform": config.telemetry?.platform, + "Upstash-Telemetry-Sdk": config.telemetry?.sdk, ...config.headers, }; + if (this.options.responseEncoding === "base64") { this.headers["Upstash-Encoding"] = "base64"; } @@ -130,9 +153,18 @@ export class HttpClient implements Requester { public async request( req: UpstashRequest, ): Promise> { + const headers = Object.entries(this.headers).reduce((acc, [key, value]) => { + if (value) { + acc[key] = value; + } + return acc; + }, {} as Record); + + console.log({ headers }); + const requestOptions: RequestInit & { backend?: string; agent?: any } = { method: "POST", - headers: this.headers, + headers, body: JSON.stringify(req.body), keepalive: true, agent: this.options?.agent, diff --git a/pkg/index.ts b/pkg/index.ts index 78e38ea8..2b32a71a 100644 --- a/pkg/index.ts +++ b/pkg/index.ts @@ -1,2 +1 @@ -export * from "./nodejs.ts"; export * from "./error.ts"; diff --git a/pkg/test-utils.ts b/pkg/test-utils.ts index b6bc5b05..18744447 100644 --- a/pkg/test-utils.ts +++ b/pkg/test-utils.ts @@ -1,5 +1,6 @@ import { DelCommand } from "./commands/del.ts"; -import { HttpClient } from "./http.ts"; +import { HttpClient, HttpClientConfig } from "./http.ts"; +import { VERSION } from "../version.ts"; /** * crypto.randomUUID() is not available in dnt crypto shim @@ -23,10 +24,15 @@ export const newHttpClient = () => { if (!token) { throw new Error("Could not find token"); } - + const telemetry: HttpClientConfig["telemetry"] = {}; + if (!Deno.env.get("UPSTASH_DISABLE_TELEMETRY")) { + telemetry.runtime = `deno@${Deno.version.deno}`; + telemetry.sdk = `@upstash/redis@${VERSION}`; + } return new HttpClient({ baseUrl: url, headers: { authorization: `Bearer ${token}` }, + telemetry, }); }; diff --git a/platforms/cloudflare.ts b/platforms/cloudflare.ts index eb276839..788c3df7 100644 --- a/platforms/cloudflare.ts +++ b/platforms/cloudflare.ts @@ -1,11 +1,16 @@ import * as core from "../pkg/redis.ts"; import type { + HttpClientConfig, Requester, UpstashRequest, UpstashResponse, } from "../pkg/http.ts"; import { HttpClient, RequesterConfig } from "../pkg/http.ts"; +import { VERSION } from "../version.ts"; +type Env = { + UPSTASH_DISABLE_TELEMETRY?: string; +}; export type { Requester, UpstashRequest, UpstashResponse }; /** * Connection credentials for upstash redis. @@ -23,7 +28,8 @@ export type RedisConfigCloudflare = token: string; } & core.RedisOptions - & RequesterConfig; + & RequesterConfig + & Env; /** * Serverless redis client for upstash. @@ -40,7 +46,7 @@ export class Redis extends core.Redis { * }); * ``` */ - constructor(config: RedisConfigCloudflare) { + constructor(config: RedisConfigCloudflare, env?: Env) { if ( config.url.startsWith(" ") || config.url.endsWith(" ") || @@ -59,11 +65,19 @@ export class Redis extends core.Redis { "The redis token contains whitespace or newline, which can cause errors!", ); } + + const telemetry: HttpClientConfig["telemetry"] = {}; + if (!env?.UPSTASH_DISABLE_TELEMETRY) { + telemetry.platform = "cloudflare"; + telemetry.sdk = `@upstash/redis@${VERSION}`; + } + const client = new HttpClient({ retry: config.retry, baseUrl: config.url, headers: { authorization: `Bearer ${config.token}` }, responseEncoding: config.responseEncoding, + telemetry, }); super(client, { @@ -86,6 +100,7 @@ export class Redis extends core.Redis { env?: { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; + UPSTASH_DISABLE_TELEMETRY?: string; }, opts?: Omit, ): Redis { @@ -105,6 +120,6 @@ export class Redis extends core.Redis { "Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_TOKEN`", ); } - return new Redis({ ...opts, url, token }); + return new Redis({ ...opts, url, token }, env); } } diff --git a/platforms/fastly.ts b/platforms/fastly.ts index 66ab3538..4844efd9 100644 --- a/platforms/fastly.ts +++ b/platforms/fastly.ts @@ -68,6 +68,7 @@ export class Redis extends core.Redis { "The redis token contains whitespace or newline, which can cause errors!", ); } + const client = new HttpClient({ baseUrl: config.url, retry: config.retry, diff --git a/platforms/node_with_fetch.ts b/platforms/node_with_fetch.ts index 83ecad9c..939925dd 100644 --- a/platforms/node_with_fetch.ts +++ b/platforms/node_with_fetch.ts @@ -3,12 +3,13 @@ import * as core from "../pkg/redis.ts"; import { HttpClient, + HttpClientConfig, Requester, RequesterConfig, - RetryConfig, UpstashRequest, UpstashResponse, } from "../pkg/http.ts"; +import { VERSION } from "../version.ts"; import "isomorphic-fetch"; // @ts-ignore Deno can't compile @@ -112,12 +113,23 @@ export class Redis extends core.Redis { ); } + const telemetry: HttpClientConfig["telemetry"] = {}; + if (!process.env.UPSTASH_DISABLE_TELEMETRY) { + telemetry.runtime = `node@${process.version}`; + telemetry.platform = process.env.VERCEL + ? "vercel" + : process.env.AWS_REGION + ? "aws" + : "unknown"; + telemetry.sdk = `@upstash/redis@${VERSION}`; + } const client = new HttpClient({ baseUrl: configOrRequester.url, retry: configOrRequester.retry, headers: { authorization: `Bearer ${configOrRequester.token}` }, // agent: configOrRequester.agent, responseEncoding: configOrRequester.responseEncoding, + telemetry, }); super(client, { diff --git a/platforms/nodejs.ts b/platforms/nodejs.ts index ba436ef4..7edb130c 100644 --- a/platforms/nodejs.ts +++ b/platforms/nodejs.ts @@ -3,13 +3,13 @@ import * as core from "../pkg/redis.ts"; import { HttpClient, + HttpClientConfig, Requester, RequesterConfig, - RetryConfig, UpstashRequest, UpstashResponse, } from "../pkg/http.ts"; -// import "isomorphic-fetch"; +import { VERSION } from "../version.ts"; export type { Requester, UpstashRequest, UpstashResponse }; @@ -107,12 +107,24 @@ export class Redis extends core.Redis { ); } + const telemetry: HttpClientConfig["telemetry"] = {}; + if (!process.env.UPSTASH_DISABLE_TELEMETRY) { + telemetry.runtime = `node@${process.version}`; + telemetry.platform = process.env.VERCEL + ? "vercel" + : process.env.AWS_REGION + ? "aws" + : "unknown"; + telemetry.sdk = `@upstash/redis@${VERSION}`; + } + const client = new HttpClient({ baseUrl: configOrRequester.url, retry: configOrRequester.retry, headers: { authorization: `Bearer ${configOrRequester.token}` }, agent: configOrRequester.agent, responseEncoding: configOrRequester.responseEncoding, + telemetry, }); super(client, { diff --git a/version.ts b/version.ts new file mode 100644 index 00000000..68d3d46f --- /dev/null +++ b/version.ts @@ -0,0 +1 @@ +export const VERSION = "development"; From 58e06d8fe16dec360221ce8d1c93635063cbf8fe Mon Sep 17 00:00:00 2001 From: Andreas Thomas Date: Tue, 22 Nov 2022 16:06:48 +0100 Subject: [PATCH 2/5] fix: typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36ba6521..50cb38bb 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ UPSTASH_REDIS_REST_URL=".." UPSTASH_REDIS_REST_TOKEN=".." deno test -A This library sends anonymous telemetry data to help us improve your experience. We collect the following: -- SDK verrsion +- SDK version - Platform (Deno, Cloudflare, Vercel) - Runtime version (node@18.x) From bb4221c19caa67ab38f5e36b79c835e0bafc1d43 Mon Sep 17 00:00:00 2001 From: Andreas Thomas Date: Wed, 23 Nov 2022 15:29:21 +0100 Subject: [PATCH 3/5] fix: remove console.logs --- pkg/http.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/http.ts b/pkg/http.ts index 43dbaecf..09d0f9a9 100644 --- a/pkg/http.ts +++ b/pkg/http.ts @@ -160,7 +160,6 @@ export class HttpClient implements Requester { return acc; }, {} as Record); - console.log({ headers }); const requestOptions: RequestInit & { backend?: string; agent?: any } = { method: "POST", From c1cb10738747bbe35f7b4b14e8b08fb18a799927 Mon Sep 17 00:00:00 2001 From: Andreas Thomas Date: Wed, 23 Nov 2022 15:30:17 +0100 Subject: [PATCH 4/5] style: fmt --- pkg/http.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/http.ts b/pkg/http.ts index 09d0f9a9..b5d2c34a 100644 --- a/pkg/http.ts +++ b/pkg/http.ts @@ -160,7 +160,6 @@ export class HttpClient implements Requester { return acc; }, {} as Record); - const requestOptions: RequestInit & { backend?: string; agent?: any } = { method: "POST", headers, From 181d50544f3e3e6994a7da0b17cb1f74cb6d55e2 Mon Sep 17 00:00:00 2001 From: Andreas Thomas Date: Wed, 23 Nov 2022 15:55:03 +0100 Subject: [PATCH 5/5] chore: clean --- cmd/build.ts | 5 ---- .../src/index.ts | 2 +- pkg/http.ts | 24 +++++++++---------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/cmd/build.ts b/cmd/build.ts index 8fe4f11d..3fbc00d0 100644 --- a/cmd/build.ts +++ b/cmd/build.ts @@ -11,11 +11,6 @@ Deno.writeFileSync( new TextEncoder().encode(`export const VERSION = "${version}"`), ); -await Deno.run({ - cmd: [packageManager, "uninstall", "@types/node"], - stdout: "piped", -}).output(); - await dnt.build({ packageManager, entryPoints: [ diff --git a/examples/cloudflare-workers-with-typescript/src/index.ts b/examples/cloudflare-workers-with-typescript/src/index.ts index 167e2319..2226ca40 100644 --- a/examples/cloudflare-workers-with-typescript/src/index.ts +++ b/examples/cloudflare-workers-with-typescript/src/index.ts @@ -15,6 +15,6 @@ export default { const count = await redis.incr("cloudflare-workers-with-typescript-count"); - return new Response(JSON.stringify({ count, env })); + return new Response(JSON.stringify({ count })); }, }; diff --git a/pkg/http.ts b/pkg/http.ts index b5d2c34a..ca8897ad 100644 --- a/pkg/http.ts +++ b/pkg/http.ts @@ -103,7 +103,7 @@ export type HttpClientConfig = { export class HttpClient implements Requester { public baseUrl: string; - public headers: Record; + public headers: Record; public readonly options: { backend?: string; agent: any; @@ -126,11 +126,18 @@ export class HttpClient implements Requester { this.headers = { "Content-Type": "application/json", - "Upstash-Telemetry-Runtime": config.telemetry?.runtime, - "Upstash-Telemetry-Platform": config.telemetry?.platform, - "Upstash-Telemetry-Sdk": config.telemetry?.sdk, + ...config.headers, }; + if (config.telemetry?.runtime) { + this.headers["Upstash-Telemetry-Runtime"] = config.telemetry.runtime; + } + if (config.telemetry?.platform) { + this.headers["Upstash-Telemetry-Platform"] = config.telemetry.platform; + } + if (config.telemetry?.sdk) { + this.headers["Upstash-Telemetry-Sdk"] = config.telemetry.sdk; + } if (this.options.responseEncoding === "base64") { this.headers["Upstash-Encoding"] = "base64"; @@ -153,16 +160,9 @@ export class HttpClient implements Requester { public async request( req: UpstashRequest, ): Promise> { - const headers = Object.entries(this.headers).reduce((acc, [key, value]) => { - if (value) { - acc[key] = value; - } - return acc; - }, {} as Record); - const requestOptions: RequestInit & { backend?: string; agent?: any } = { method: "POST", - headers, + headers: this.headers, body: JSON.stringify(req.body), keepalive: true, agent: this.options?.agent,