Skip to content

Commit cc942b5

Browse files
authored
feat(appkit): send internal telemetry via AppkitLog schema (#332)
* feat(appkit): send internal telemetry via AppkitLog schema Introduce the AppkitLog event family (APP_STARTUP, HEARTBEAT, REQUEST_METRICS) and a TelemetryReporter singleton that owns the shared dispatch state, periodic heartbeat, and request metrics aggregation. The server plugin records each matched route via res.on('finish') middleware; the reporter flushes one event per endpoint on a periodic timer. The legacy observability_log APP_STARTUP is kept as a fallback until the AppkitLog schema is deployed end-to-end on the telemetry backend. Errors propagate from the inner senders so consumers can see exactly what was POSTed and how the endpoint responded; the only catches live at the SDK's outermost boundaries (fire-and-forget startup + interval timers). Adds an Internal Telemetry tab in dev-playground that lets you trigger each event on demand and renders the request, response, and an equivalent curl command. Disable with disableInternalTelemetry: true or APPKIT_TELEMETRY_DISABLED=true. Also unblocks the pre-commit knip hook by ignoring the @cyclonedx/cdxgen dependency, which is invoked dynamically via pnpm exec from the release:sbom script. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * fix(playground): bind DATABRICKS_JOB_ID in app.yaml The dev-playground registers the jobs() plugin, whose manifest requires DATABRICKS_JOB_ID, but app.yaml never declared a binding for it. As a result, deploying the playground to a fresh Databricks App fails AppKit's startup resource validation with "Missing required resources: job:Job [jobs]". Add the missing entry alongside the other resource bindings. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * fix(appkit): use /telemetry-ext and resolve redirect locations The bare /telemetry endpoint rejects SP bearer tokens and 302s to /login.html?next_url=..., which the previous code tried to follow verbatim — but a relative location is not a valid fetch URL and threw a "Failed to parse URL" error that the legacy try/catch silently swallowed. Switch the dispatch URL to the SP-friendly /telemetry-ext endpoint, and harden redirect handling by resolving the location against the original request URL. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * refactor(appkit): drop legacy observability_log startup telemetry Now that the AppkitLog dispatch path is verified end-to-end against /telemetry-ext, the observability_log fallback is no longer needed. Remove sendStartupTelemetry, the dead StartupTelemetryParams / buildEntityId / buildLegacyStartupPayload helpers, and the second fire-and-forget block in createApp's bootstrap. Migrate the sender test suite to cover sendAppkitLogs (which retains all the URL, auth, redirect, and error-propagation guarantees) and rewrite the core internal-telemetry tests against the reporter mock. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * fix(appkit): read app_id from DATABRICKS_CLIENT_ID Databricks Apps injects DATABRICKS_CLIENT_ID (the app's OAuth client UUID) into the runtime env, not DATABRICKS_APP_ID, so the old lookup always resolved to "" and the AppkitLog.app_id field went out empty. Switch the bootstrap to read DATABRICKS_CLIENT_ID so logs carry the actual per-app identifier. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * refactor(appkit): rename internal telemetry kill-switch env var Rename APPKIT_TELEMETRY_DISABLED to DISABLE_APPKIT_INTERNAL_TELEMETRY. The new name makes it explicit that this controls AppKit's internal/anonymized telemetry, not the user-facing OpenTelemetry config exposed via createApp({ telemetry }). Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * refactor(appkit): inline sender.ts into reporter sender.ts only wrapped postTelemetry with buildAppkitPayload — one caller, no shared logic worth a dedicated file. Have the reporter's #send call postTelemetry directly and rename the test file from sender.test.ts to client.test.ts so the wire-format coverage (URL, auth, redirects, error propagation) clearly targets postTelemetry. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * docs: add public internal-telemetry page Document exactly what AppKit collects (event_name, app_id, appkit_version, plus per-event bodies for APP_STARTUP, HEARTBEAT, and REQUEST_METRICS), how it's sent, and the two ways to disable — disableInternalTelemetry on createApp and the DISABLE_APPKIT_INTERNAL_TELEMETRY env var. Bump faq.md's sidebar position to 8 to make room. Add a one-paragraph header in the package's index.ts pointing at the public doc. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * fix(appkit): harden telemetry dispatch + revert knip cdxgen change - Refuse cross-origin redirects from /telemetry-ext so the live SP Authorization header cannot be replayed against a third party. - Fall back to serviceCtx.client.config.host when DATABRICKS_HOST is unset so the dispatch URL still resolves correctly when the SDK was given a pre-configured WorkspaceClient. - Redact Authorization / Cookie / Set-Cookie when surfacing the request in the dev-playground debug UI and in the printed curl, so the sensitive headers don't leak via the response or get copy-pasted into shared logs. - Revert the knip.json @cyclonedx/cdxgen exception. Earlier diagnosis was wrong — the warnings are notices, not errors, and the original pre-commit failures came from this branch's own unused exports (now trimmed). With the branch rebased on origin/ main, knip exits 0 against the unmodified config. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * refactor(appkit): hoist redirect body.cancel() out of the branch Both branches in fetchWithRedirect (cross-origin throw + same-origin follow) want to release the redirect's response body before moving on. Run the cancel once after we've parsed the target URL, before deciding what to do, instead of repeating it on each side. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore(appkit): drop redirect-follow logic to test endpoint behavior The /telemetry-ext endpoint may not actually issue redirects in practice; the follow logic was added defensively. Inline a single fetch with redirect: "manual" so any 3xx surfaces directly to the caller (and the dev-playground UI), making it easy to verify whether the redirect path is exercised on real traffic. If the deployed app never produces a 3xx response, the helper function and its tests stay deleted; if it does, restore them. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore(appkit): drop redirect handling and dev-playground doc section The /telemetry-ext endpoint does not actually redirect under normal SP-authenticated traffic, so the redirect-follow logic, the redirect: "manual" hint on fetch, and the corresponding tests were all dead weight. Inline a plain fetch and remove the related test. Also drop the "Inspecting events locally" section from the public internal-telemetry doc — the dev-playground harness is internal tooling and isn't relevant to library consumers. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore(appkit): drop public TelemetryReporter exports + dev-playground debug The dev-playground debug plugin was the only consumer of TelemetryReporter, TelemetrySendRequest, TelemetrySendResponse, and TelemetrySendResult outside the package. Since dev-playground is internal tooling for AppKit maintainers, exporting infrastructure types via the public surface only to feed it isn't justified. Drop: - TelemetryReporter and the TelemetrySend* types from the public @databricks/appkit exports - The TelemetrySend* re-exports from internal-telemetry/index.ts (no remaining consumer) - The internal-telemetry-debug plugin and its routes - The /internal-telemetry route, home-page card, and nav link in dev-playground The reporter and types remain available intra-package for the core/server wiring; nothing about the dispatch behavior changes. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * docs(appkit): rename internal-telemetry page to Privacy + DO_NOT_TRACK - Rename docs/docs/internal-telemetry.mdx to privacy.mdx so library consumers don't confuse it with the user-facing OpenTelemetry config exposed via createApp({ telemetry }). - Move the page to the bottom of the sidebar (sidebar_position: 99). - Honor the cross-tool DO_NOT_TRACK=1 convention (https://consoledonottrack.com) alongside the AppKit-specific DISABLE_APPKIT_INTERNAL_TELEMETRY env var. - Document the new opt-out path on the Privacy page and update the in-source comment that pointed at the old doc location. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore(appkit): drop .js suffixes from internal-telemetry imports The project uses moduleResolution: "bundler", which doesn't require explicit .js extensions on relative imports. The rest of appkit imports without them; only internal-telemetry/ added them, by mistake on my part. Strip them to match the surrounding codebase. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * docs(appkit): trim Privacy page Drop the "we do not send X, Y, Z" paragraph — committing to a non-collection list ties our hands if the schema later changes. Also drop the "How it's sent" section; the dispatch endpoint and fire-and-forget semantics are implementation details users don't need to track. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore(playground): drop appkit.plugins.json The dev-playground no longer needs the aggregated plugin manifest; the file is generated by `appkit plugin sync` and isn't read by any current dev-playground flow. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore(appkit): send empty bodies for APP_STARTUP and HEARTBEAT Drop the placeholder: true field from app_startup_event and heartbeat_event payloads; an empty object is enough to identify the oneof variant on the wire. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * refactor(appkit): send telemetry via client.apiClient.request The SDK's apiClient.request already does host resolution, authentication, query string handling, and User-Agent setting that client.ts was reimplementing by hand. Drop client.ts (and its tests), inline a single apiClient.request call inside reporter's #send, and remove the now-unused workspaceHost from the reporter constructor. Tests for the reporter switch to mocking apiClient.request on the WorkspaceClient mock instead of stubbing global fetch. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * fix(appkit): stop telemetry reporter on graceful shutdown + cover middleware - Call TelemetryReporter.getInstance()?.stop() in the server plugin's _gracefulShutdown so heartbeat/metric timers don't keep firing during the 15s shutdown grace window. - Export requestMetricsMiddleware as @internal and add unit tests for the integration point: matched routes, baseUrl + route template assembly, no-op when req.route is unset, no-op when the reporter is uninitialized, and 4xx/5xx status pass-through. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore(appkit): remove dead consoledonottrack.com link Drop the link from the config.ts comment and from the Privacy page; the URL doesn't resolve. The DO_NOT_TRACK env var stays — the convention itself is fine, just the canonical site reference isn't useful. Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> * chore: drop .superset/config.json from gitignore Co-authored-by: Isaac Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com> --------- Signed-off-by: Jorge Calvar <jorge.calvar@databricks.com>
1 parent 9d2920c commit cc942b5

14 files changed

Lines changed: 869 additions & 1 deletion

File tree

apps/dev-playground/app.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ env:
55
valueFrom: genie-space
66
- name: DATABRICKS_SERVING_ENDPOINT_NAME
77
valueFrom: serving-endpoint
8+
- name: DATABRICKS_JOB_ID
9+
valueFrom: job
810
# Files plugin manifest declares a static DATABRICKS_VOLUME_FILES
911
# requirement; keep it bound so appkit's runtime validation passes
1012
# even though the policy harness below uses its own keys.

docs/docs/api/appkit/Function.createApp.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
function createApp<T>(config: {
55
cache?: CacheConfig;
66
client?: WorkspaceClient;
7+
disableInternalTelemetry?: boolean;
78
onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;
89
plugins?: T;
910
telemetry?: TelemetryConfig;
@@ -30,9 +31,10 @@ with an `asUser(req)` method for user-scoped execution.
3031

3132
| Parameter | Type |
3233
| ------ | ------ |
33-
| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `client?`: `WorkspaceClient`; `onPluginsReady?`: (`appkit`: `PluginMap`\<`T`\>) => `void` \| `Promise`\<`void`\>; `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} |
34+
| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `client?`: `WorkspaceClient`; `disableInternalTelemetry?`: `boolean`; `onPluginsReady?`: (`appkit`: `PluginMap`\<`T`\>) => `void` \| `Promise`\<`void`\>; `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} |
3435
| `config.cache?` | [`CacheConfig`](Interface.CacheConfig.md) |
3536
| `config.client?` | `WorkspaceClient` |
37+
| `config.disableInternalTelemetry?` | `boolean` |
3638
| `config.onPluginsReady?` | (`appkit`: `PluginMap`\<`T`\>) => `void` \| `Promise`\<`void`\> |
3739
| `config.plugins?` | `T` |
3840
| `config.telemetry?` | [`TelemetryConfig`](Interface.TelemetryConfig.md) |

docs/docs/privacy.mdx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
sidebar_position: 99
3+
---
4+
5+
# Privacy
6+
7+
AppKit sends a small amount of anonymized usage telemetry to Databricks
8+
so the team can understand how the SDK is used and prioritize
9+
improvements. This page documents exactly what is sent, when, and how
10+
to turn it off.
11+
12+
## What we collect
13+
14+
Every event is a single record with three top-level fields:
15+
16+
| Field | Type | Source |
17+
| ---------------- | ------ | ----------------------------------- |
18+
| `event_name` | enum | One of `APP_STARTUP`, `HEARTBEAT`, `REQUEST_METRICS` |
19+
| `app_id` | string | The app's OAuth client UUID (`DATABRICKS_CLIENT_ID`) |
20+
| `appkit_version` | string | The AppKit SDK version |
21+
22+
Each event also carries one of three event-specific bodies:
23+
24+
- **`APP_STARTUP`** — emitted once when `createApp` finishes booting.
25+
Empty body.
26+
- **`HEARTBEAT`** — emitted every five minutes from a running app.
27+
Empty body.
28+
- **`REQUEST_METRICS`** — emitted once per minute, one record per HTTP
29+
endpoint that received traffic in the window. Each record contains:
30+
- `endpoint` — the route template (e.g. `GET /api/genie/:space_id/messages`),
31+
never the raw request URL or any user-provided values.
32+
- `request_count`
33+
- `request_latency_ms_avg`
34+
- `response_count_http4xx`
35+
- `response_count_http5xx`
36+
37+
## How to opt out
38+
39+
Set any one of the following:
40+
41+
```sh
42+
DISABLE_APPKIT_INTERNAL_TELEMETRY=true
43+
DO_NOT_TRACK=1
44+
```
45+
46+
Either fully disables the reporter — no events are emitted and no
47+
network calls are made.

packages/appkit/src/core/appkit.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ import type {
88
PluginData,
99
PluginMap,
1010
} from "shared";
11+
import { version as productVersion } from "../../package.json";
1112
import { CacheManager } from "../cache";
1213
import { ServiceContext } from "../context";
14+
import {
15+
isInternalTelemetryEnabled,
16+
TelemetryReporter,
17+
} from "../internal-telemetry";
1318
import { createLogger } from "../logging/logger";
1419
import { ResourceRegistry, ResourceType } from "../registry";
1520
import type { TelemetryConfig } from "../telemetry";
@@ -191,6 +196,7 @@ export class AppKit<TPlugins extends InputPluginMap> {
191196
cache?: CacheConfig;
192197
client?: WorkspaceClient;
193198
onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;
199+
disableInternalTelemetry?: boolean;
194200
} = {},
195201
): Promise<PluginMap<T>> {
196202
// Initialize core services
@@ -233,6 +239,10 @@ export class AppKit<TPlugins extends InputPluginMap> {
233239
logger.debug("onPluginsReady hook completed");
234240
}
235241

242+
if (isInternalTelemetryEnabled(config)) {
243+
AppKit.bootstrapInternalTelemetry();
244+
}
245+
236246
const serverPlugin = instance.#pluginInstances.server;
237247
if (serverPlugin && typeof (serverPlugin as any).start === "function") {
238248
await (serverPlugin as any).start();
@@ -241,6 +251,18 @@ export class AppKit<TPlugins extends InputPluginMap> {
241251
return handle;
242252
}
243253

254+
private static bootstrapInternalTelemetry(): void {
255+
const serviceCtx = ServiceContext.get();
256+
const reporter = TelemetryReporter.initialize({
257+
workspaceId: serviceCtx.workspaceId,
258+
client: serviceCtx.client,
259+
appId: process.env.DATABRICKS_CLIENT_ID || "",
260+
appkitVersion: productVersion,
261+
});
262+
reporter.start();
263+
reporter.sendStartup().catch(() => {});
264+
}
265+
244266
private static preparePlugins(
245267
plugins: PluginData<PluginConstructor, unknown, string>[],
246268
) {
@@ -300,6 +322,7 @@ export async function createApp<
300322
cache?: CacheConfig;
301323
client?: WorkspaceClient;
302324
onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;
325+
disableInternalTelemetry?: boolean;
303326
} = {},
304327
): Promise<PluginMap<T>> {
305328
return AppKit._createApp(config);

packages/appkit/src/core/tests/databricks.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@ import type { PluginManifest } from "../../registry/types";
66
import { ResourceType } from "../../registry/types";
77
import { AppKit, createApp } from "../appkit";
88

9+
const mockReporter = {
10+
start: vi.fn(),
11+
stop: vi.fn(),
12+
sendStartup: vi.fn().mockResolvedValue(undefined),
13+
sendHeartbeat: vi.fn().mockResolvedValue(undefined),
14+
flushRequestMetrics: vi.fn().mockResolvedValue(undefined),
15+
recordRequest: vi.fn(),
16+
};
17+
18+
vi.mock("../../internal-telemetry", () => ({
19+
isInternalTelemetryEnabled: vi.fn().mockReturnValue(true),
20+
TelemetryReporter: {
21+
initialize: vi.fn(() => mockReporter),
22+
getInstance: vi.fn(() => mockReporter),
23+
_reset: vi.fn(),
24+
},
25+
}));
26+
927
// Generic test manifest for test plugins
1028
const createTestManifest = (name: string): PluginManifest => ({
1129
name,
@@ -629,6 +647,60 @@ describe("AppKit", () => {
629647
});
630648
});
631649

650+
describe("internal telemetry", () => {
651+
test("initializes the reporter and fires sendStartup after createApp", async () => {
652+
const { TelemetryReporter } = await import("../../internal-telemetry");
653+
mockReporter.sendStartup.mockClear();
654+
mockReporter.start.mockClear();
655+
vi.mocked(TelemetryReporter.initialize).mockClear();
656+
657+
await createApp({
658+
plugins: [{ plugin: CoreTestPlugin, config: {}, name: "coreTest" }],
659+
});
660+
661+
// Allow the fire-and-forget promise chain to resolve
662+
await new Promise((r) => setTimeout(r, 10));
663+
664+
expect(TelemetryReporter.initialize).toHaveBeenCalledWith(
665+
expect.objectContaining({
666+
appkitVersion: expect.any(String),
667+
client: expect.anything(),
668+
}),
669+
);
670+
expect(mockReporter.start).toHaveBeenCalledOnce();
671+
expect(mockReporter.sendStartup).toHaveBeenCalledOnce();
672+
});
673+
674+
test("skips bootstrap when isInternalTelemetryEnabled returns false", async () => {
675+
const { isInternalTelemetryEnabled, TelemetryReporter } = await import(
676+
"../../internal-telemetry"
677+
);
678+
vi.mocked(TelemetryReporter.initialize).mockClear();
679+
mockReporter.sendStartup.mockClear();
680+
vi.mocked(isInternalTelemetryEnabled).mockReturnValue(false);
681+
682+
await createApp({ plugins: [] });
683+
684+
await new Promise((r) => setTimeout(r, 10));
685+
686+
expect(TelemetryReporter.initialize).not.toHaveBeenCalled();
687+
expect(mockReporter.sendStartup).not.toHaveBeenCalled();
688+
vi.mocked(isInternalTelemetryEnabled).mockReturnValue(true);
689+
});
690+
691+
test("does not crash startup if sendStartup rejects", async () => {
692+
mockReporter.sendStartup.mockRejectedValueOnce(
693+
new Error("telemetry failure"),
694+
);
695+
696+
const instance = await createApp({
697+
plugins: [{ plugin: CoreTestPlugin, config: {}, name: "coreTest" }],
698+
});
699+
700+
expect(instance).toBeDefined();
701+
});
702+
});
703+
632704
describe("SDK context binding", () => {
633705
test("should bind SDK methods to plugin instance", async () => {
634706
class ContextTestPlugin implements BasePlugin {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// IMPORTANT: keep this file in sync with the AppkitLog proto schema served by
2+
// the Databricks client telemetry endpoint. Field names use proto JSON
3+
// conventions (snake_case) so the wire format matches the backend.
4+
5+
export type AppkitEventName =
6+
| "APPKIT_EVENT_NAME_UNSPECIFIED"
7+
| "APP_STARTUP"
8+
| "HEARTBEAT"
9+
| "REQUEST_METRICS";
10+
11+
export type AppStartupEvent = Record<string, never>;
12+
13+
export type HeartbeatEvent = Record<string, never>;
14+
15+
export interface RequestMetricsEvent {
16+
endpoint?: string;
17+
request_count?: number;
18+
request_latency_ms_avg?: number;
19+
response_count_http4xx?: number;
20+
response_count_http5xx?: number;
21+
}
22+
23+
export interface AppkitLog {
24+
event_name: AppkitEventName;
25+
app_id?: string;
26+
appkit_version?: string;
27+
app_startup_event?: AppStartupEvent;
28+
heartbeat_event?: HeartbeatEvent;
29+
request_metrics_event?: RequestMetricsEvent;
30+
}
31+
32+
interface AppkitLogEnvelope {
33+
frontend_log_event_id: string;
34+
inferred_timestamp_millis: number;
35+
entry: { appkit_log: AppkitLog };
36+
}
37+
38+
interface TelemetryPayload {
39+
uploadTime: number;
40+
items: never[];
41+
protoLogs: string[];
42+
}
43+
44+
export function wrapAppkitLog(log: AppkitLog): AppkitLogEnvelope {
45+
return {
46+
frontend_log_event_id: `appkit-${log.event_name.toLowerCase()}-${crypto.randomUUID()}`,
47+
inferred_timestamp_millis: Date.now(),
48+
entry: { appkit_log: log },
49+
};
50+
}
51+
52+
export function buildAppkitPayload(logs: AppkitLog[]): TelemetryPayload {
53+
return {
54+
uploadTime: Date.now(),
55+
items: [],
56+
protoLogs: logs.map((log) => JSON.stringify(wrapAppkitLog(log))),
57+
};
58+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Checks whether internal telemetry is enabled.
3+
* Shared across all telemetry event types (startup, heartbeat, metrics, etc.).
4+
*/
5+
export function isInternalTelemetryEnabled(opts?: {
6+
disableInternalTelemetry?: boolean;
7+
}): boolean {
8+
if (opts?.disableInternalTelemetry) return false;
9+
if (process.env.DISABLE_APPKIT_INTERNAL_TELEMETRY === "true") return false;
10+
if (process.env.DO_NOT_TRACK === "1") return false;
11+
return true;
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Internal telemetry: APP_STARTUP, HEARTBEAT, and REQUEST_METRICS events
2+
// POSTed to /telemetry-ext so the Databricks team can prioritize SDK work.
3+
// Disable with disableInternalTelemetry: true on createApp,
4+
// DISABLE_APPKIT_INTERNAL_TELEMETRY=true, or DO_NOT_TRACK=1.
5+
// Full data inventory: docs/docs/privacy.mdx.
6+
7+
export { isInternalTelemetryEnabled } from "./config";
8+
export { TelemetryReporter } from "./reporter";

0 commit comments

Comments
 (0)