Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 22
feat(appkit): send internal telemetry via AppkitLog schema#332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
59458ca4e720edaf6f6f3e45773bb207f28986c42ff7ac315651377d688059eeab7c4b08378b9f9550156e80df3fb4e05adb00fcd9547764ce2e36ea9f6a18be24186689408590fcb585776907ea682b7File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| --- | ||
| sidebar_position: 99 | ||
| --- | ||
| # Privacy | ||
| AppKit sends a small amount of anonymized usage telemetry to Databricks | ||
| so the team can understand how the SDK is used and prioritize | ||
| improvements. This page documents exactly what is sent, when, and how | ||
| to turn it off. | ||
| ## What we collect | ||
| Every event is a single record with three top-level fields: | ||
| | Field | Type | Source | | ||
| | ---------------- | ------ | ----------------------------------- | | ||
| | `event_name` | enum | One of `APP_STARTUP`, `HEARTBEAT`, `REQUEST_METRICS` | | ||
| | `app_id` | string | The app's OAuth client UUID (`DATABRICKS_CLIENT_ID`) | | ||
| | `appkit_version` | string | The AppKit SDK version | | ||
| Each event also carries one of three event-specific bodies: | ||
| - **`APP_STARTUP`** — emitted once when `createApp` finishes booting. | ||
| Empty body. | ||
| - **`HEARTBEAT`** — emitted every five minutes from a running app. | ||
| Empty body. | ||
| - **`REQUEST_METRICS`** — emitted once per minute, one record per HTTP | ||
| endpoint that received traffic in the window. Each record contains: | ||
| - `endpoint` — the route template (e.g. `GET /api/genie/:space_id/messages`), | ||
| never the raw request URL or any user-provided values. | ||
| - `request_count` | ||
| - `request_latency_ms_avg` | ||
| - `response_count_http4xx` | ||
| - `response_count_http5xx` | ||
| ## How to opt out | ||
| Set any one of the following: | ||
| ```sh | ||
| DISABLE_APPKIT_INTERNAL_TELEMETRY=true | ||
| DO_NOT_TRACK=1 | ||
| ``` | ||
| Either fully disables the reporter — no events are emitted and no | ||
| network calls are made. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -8,8 +8,13 @@ import type { | ||
| PluginData, | ||
| PluginMap, | ||
| } from "shared"; | ||
| import { version as productVersion } from "../../package.json"; | ||
| import { CacheManager } from "../cache"; | ||
| import { ServiceContext } from "../context"; | ||
| import { | ||
| isInternalTelemetryEnabled, | ||
| TelemetryReporter, | ||
| } from "../internal-telemetry"; | ||
| import { createLogger } from "../logging/logger"; | ||
| import { ResourceRegistry, ResourceType } from "../registry"; | ||
| import type { TelemetryConfig } from "../telemetry"; | ||
| @@ -191,6 +196,7 @@ export class AppKit<TPlugins extends InputPluginMap> { | ||
| cache?: CacheConfig; | ||
| client?: WorkspaceClient; | ||
| onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>; | ||
| disableInternalTelemetry?: boolean; | ||
calvarjorge marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } = {}, | ||
| ): Promise<PluginMap<T>> { | ||
| // Initialize core services | ||
| @@ -233,6 +239,10 @@ export class AppKit<TPlugins extends InputPluginMap> { | ||
| logger.debug("onPluginsReady hook completed"); | ||
| } | ||
| if (isInternalTelemetryEnabled(config)) { | ||
| AppKit.bootstrapInternalTelemetry(); | ||
| } | ||
| const serverPlugin = instance.#pluginInstances.server; | ||
| if (serverPlugin && typeof (serverPlugin as any).start === "function") { | ||
| await (serverPlugin as any).start(); | ||
| @@ -241,6 +251,18 @@ export class AppKit<TPlugins extends InputPluginMap> { | ||
| return handle; | ||
| } | ||
| private static bootstrapInternalTelemetry(): void { | ||
| const serviceCtx = ServiceContext.get(); | ||
| const reporter = TelemetryReporter.initialize({ | ||
| workspaceId: serviceCtx.workspaceId, | ||
| client: serviceCtx.client, | ||
| appId: process.env.DATABRICKS_CLIENT_ID || "", | ||
| appkitVersion: productVersion, | ||
| }); | ||
| reporter.start(); | ||
| reporter.sendStartup().catch(() => {}); | ||
MarioCadenas marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| private static preparePlugins( | ||
| plugins: PluginData<PluginConstructor, unknown, string>[], | ||
| ) { | ||
| @@ -300,6 +322,7 @@ export async function createApp< | ||
| cache?: CacheConfig; | ||
| client?: WorkspaceClient; | ||
| onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>; | ||
| disableInternalTelemetry?: boolean; | ||
| } = {}, | ||
| ): Promise<PluginMap<T>> { | ||
| return AppKit._createApp(config); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // IMPORTANT: keep this file in sync with the AppkitLog proto schema served by | ||
| // the Databricks client telemetry endpoint. Field names use proto JSON | ||
| // conventions (snake_case) so the wire format matches the backend. | ||
| export type AppkitEventName = | ||
| | "APPKIT_EVENT_NAME_UNSPECIFIED" | ||
| | "APP_STARTUP" | ||
| | "HEARTBEAT" | ||
| | "REQUEST_METRICS"; | ||
| export type AppStartupEvent = Record<string, never>; | ||
| export type HeartbeatEvent = Record<string, never>; | ||
| export interface RequestMetricsEvent { | ||
| endpoint?: string; | ||
| request_count?: number; | ||
| request_latency_ms_avg?: number; | ||
| response_count_http4xx?: number; | ||
| response_count_http5xx?: number; | ||
| } | ||
| export interface AppkitLog { | ||
calvarjorge marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| event_name: AppkitEventName; | ||
| app_id?: string; | ||
| appkit_version?: string; | ||
| app_startup_event?: AppStartupEvent; | ||
| heartbeat_event?: HeartbeatEvent; | ||
| request_metrics_event?: RequestMetricsEvent; | ||
| } | ||
| interface AppkitLogEnvelope { | ||
| frontend_log_event_id: string; | ||
| inferred_timestamp_millis: number; | ||
| entry: { appkit_log: AppkitLog }; | ||
| } | ||
| interface TelemetryPayload { | ||
| uploadTime: number; | ||
| items: never[]; | ||
| protoLogs: string[]; | ||
| } | ||
| export function wrapAppkitLog(log: AppkitLog): AppkitLogEnvelope { | ||
| return { | ||
| frontend_log_event_id: `appkit-${log.event_name.toLowerCase()}-${crypto.randomUUID()}`, | ||
| inferred_timestamp_millis: Date.now(), | ||
| entry: { appkit_log: log }, | ||
| }; | ||
| } | ||
| export function buildAppkitPayload(logs: AppkitLog[]): TelemetryPayload { | ||
| return { | ||
| uploadTime: Date.now(), | ||
| items: [], | ||
| protoLogs: logs.map((log) => JSON.stringify(wrapAppkitLog(log))), | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /** | ||
| * Checks whether internal telemetry is enabled. | ||
| * Shared across all telemetry event types (startup, heartbeat, metrics, etc.). | ||
| */ | ||
| export function isInternalTelemetryEnabled(opts?: { | ||
| disableInternalTelemetry?: boolean; | ||
| }): boolean { | ||
| if (opts?.disableInternalTelemetry) return false; | ||
| if (process.env.DISABLE_APPKIT_INTERNAL_TELEMETRY === "true") return false; | ||
| if (process.env.DO_NOT_TRACK === "1") return false; | ||
| return true; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // Internal telemetry: APP_STARTUP, HEARTBEAT, and REQUEST_METRICS events | ||
| // POSTed to /telemetry-ext so the Databricks team can prioritize SDK work. | ||
| // Disable with disableInternalTelemetry: true on createApp, | ||
| // DISABLE_APPKIT_INTERNAL_TELEMETRY=true, or DO_NOT_TRACK=1. | ||
| // Full data inventory: docs/docs/privacy.mdx. | ||
| export { isInternalTelemetryEnabled } from "./config"; | ||
| export { TelemetryReporter } from "./reporter"; |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.