chore: restore local telemetry infrastructure
This commit is contained in:
parent
af7af8cc9d
commit
83abbbc2fe
|
|
@ -16,10 +16,8 @@ import { getEventMetadata } from './metadata.js'
|
|||
* DATADOG_LOGS_ENDPOINT=https://http-intake.logs.datadoghq.com/api/v2/logs
|
||||
* DATADOG_API_KEY=<your-key>
|
||||
*/
|
||||
const DATADOG_LOGS_ENDPOINT =
|
||||
process.env.DATADOG_LOGS_ENDPOINT ?? ''
|
||||
const DATADOG_CLIENT_TOKEN =
|
||||
process.env.DATADOG_API_KEY ?? ''
|
||||
const DATADOG_LOGS_ENDPOINT = process.env.DATADOG_LOGS_ENDPOINT ?? ''
|
||||
const DATADOG_CLIENT_TOKEN = process.env.DATADOG_API_KEY ?? ''
|
||||
const DEFAULT_FLUSH_INTERVAL_MS = 15000
|
||||
const MAX_BATCH_SIZE = 100
|
||||
const NETWORK_TIMEOUT_MS = 5000
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import { getPlatform, getWslVersion } from '../../utils/platform.js'
|
|||
import { jsonStringify } from '../../utils/slowOperations.js'
|
||||
import { profileCheckpoint } from '../../utils/startupProfiler.js'
|
||||
import { getCoreUserData } from '../../utils/user.js'
|
||||
import { isAnalyticsDisabled } from './config.js'
|
||||
import { FirstPartyEventLoggingExporter } from './firstPartyEventLoggingExporter.js'
|
||||
import type { GrowthBookUserAttributes } from './growthbook.js'
|
||||
import { getDynamicConfig_CACHED_MAY_BE_STALE } from './growthbook.js'
|
||||
|
|
@ -139,8 +138,10 @@ export async function shutdown1PEventLogging(): Promise<void> {
|
|||
* metrics opt-out via API. It follows the same pattern as Statsig event logging.
|
||||
*/
|
||||
export function is1PEventLoggingEnabled(): boolean {
|
||||
// Respect standard analytics opt-outs
|
||||
return !isAnalyticsDisabled()
|
||||
// CCP keeps analytics local by default. The restored upstream 1P pipeline
|
||||
// remains available in the source tree, but is not allowed to send telemetry
|
||||
// to Anthropic from this fork.
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -331,6 +332,7 @@ export function initialize1PEventLogging(): void {
|
|||
parseInt(
|
||||
process.env.OTEL_LOGS_EXPORT_INTERVAL ||
|
||||
DEFAULT_LOGS_EXPORT_INTERVAL_MS.toString(),
|
||||
10,
|
||||
)
|
||||
|
||||
const maxExportBatchSize =
|
||||
|
|
|
|||
|
|
@ -109,15 +109,13 @@ export class FirstPartyEventLoggingExporter implements LogRecordExporter {
|
|||
schedule?: (fn: () => Promise<void>, delayMs: number) => () => void
|
||||
} = {},
|
||||
) {
|
||||
// Default: prod, except when ANTHROPIC_BASE_URL is explicitly staging.
|
||||
// Overridable via tengu_1p_event_batch_config.baseUrl.
|
||||
const baseUrl =
|
||||
options.baseUrl ||
|
||||
(process.env.ANTHROPIC_BASE_URL === 'https://api-staging.anthropic.com'
|
||||
? 'https://api-staging.anthropic.com'
|
||||
: 'https://api.anthropic.com')
|
||||
// CCP has no default remote endpoint. A caller must explicitly provide a
|
||||
// baseUrl to export 1P events off-machine.
|
||||
const baseUrl = options.baseUrl
|
||||
|
||||
this.endpoint = `${baseUrl}${options.path || '/api/event_logging/batch'}`
|
||||
this.endpoint = baseUrl
|
||||
? `${baseUrl}${options.path || '/api/event_logging/batch'}`
|
||||
: ''
|
||||
|
||||
this.timeout = options.timeout || 10000
|
||||
this.maxBatchSize = options.maxBatchSize || 200
|
||||
|
|
@ -135,7 +133,9 @@ export class FirstPartyEventLoggingExporter implements LogRecordExporter {
|
|||
})
|
||||
|
||||
// Retry any failed events from previous runs of this session (in background)
|
||||
void this.retryPreviousBatches()
|
||||
if (this.endpoint) {
|
||||
void this.retryPreviousBatches()
|
||||
}
|
||||
}
|
||||
|
||||
// Expose for testing
|
||||
|
|
@ -307,6 +307,11 @@ export class FirstPartyEventLoggingExporter implements LogRecordExporter {
|
|||
logs: ReadableLogRecord[],
|
||||
resultCallback: (result: ExportResult) => void,
|
||||
): Promise<void> {
|
||||
if (!this.endpoint) {
|
||||
resultCallback({ code: ExportResultCode.SUCCESS })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Filter for event logs only (by scope name)
|
||||
const eventLogs = logs.filter(
|
||||
|
|
@ -673,7 +678,9 @@ export class FirstPartyEventLoggingExporter implements LogRecordExporter {
|
|||
(attributes.event_name as string) || (log.body as string) || 'unknown'
|
||||
|
||||
// Extract metadata objects directly (no JSON parsing needed)
|
||||
const coreMetadata = attributes.core_metadata as unknown as EventMetadata | undefined
|
||||
const coreMetadata = attributes.core_metadata as unknown as
|
||||
| EventMetadata
|
||||
| undefined
|
||||
const userMetadata = attributes.user_metadata as CoreUserData
|
||||
const eventMetadata = (attributes.event_metadata || {}) as Record<
|
||||
string,
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@ import {
|
|||
type GitHubActionsMetadata,
|
||||
getUserForGrowthBook,
|
||||
} from '../../utils/user.js'
|
||||
import {
|
||||
is1PEventLoggingEnabled,
|
||||
logGrowthBookExperimentTo1P,
|
||||
} from './firstPartyEventLogger.js'
|
||||
import { logGrowthBookExperimentTo1P } from './firstPartyEventLogger.js'
|
||||
|
||||
const DEFAULT_API_HOST = 'api.anthropic.com' // core API host, not a telemetry endpoint
|
||||
|
||||
/**
|
||||
* User attributes sent to GrowthBook for targeting.
|
||||
|
|
@ -466,9 +465,14 @@ const LOCAL_GATE_DEFAULTS: Record<string, unknown> = {
|
|||
tengu_birch_trellis: true, // Tree-sitter bash security analysis
|
||||
tengu_collage_kaleidoscope: true, // macOS clipboard image reading
|
||||
tengu_compact_cache_prefix: true, // Reuse prompt cache during compaction
|
||||
tengu_kairos_assistant: true, // KAIROS assistant mode activation
|
||||
tengu_kairos_cron_durable: true, // Persistent cron tasks
|
||||
tengu_attribution_header: true, // API request attribution header
|
||||
tengu_slate_prism: true, // Agent progress summaries
|
||||
|
||||
// ── Ultrareview (cloud code review via CCR) ─────────────────────
|
||||
tengu_review_bughunter_config: { enabled: true }, // /ultrareview command visibility
|
||||
tengu_ccr_bundle_seed_enabled: true, // Bundle seed: skip GitHub App check for branch mode
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -486,12 +490,13 @@ function getLocalGateDefault(feature: string): unknown | undefined {
|
|||
* Check if GrowthBook operations should be enabled
|
||||
*/
|
||||
function isGrowthBookEnabled(): boolean {
|
||||
// 适配器模式:有自定义服务器配置时直接启用
|
||||
// Adapter mode is the only network-enabled mode in CCP. Without an explicit
|
||||
// custom GrowthBook adapter, feature values come from env/config overrides
|
||||
// and LOCAL_GATE_DEFAULTS only.
|
||||
if (process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY) {
|
||||
return true
|
||||
}
|
||||
// GrowthBook depends on 1P event logging.
|
||||
return is1PEventLoggingEnabled()
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -503,15 +508,15 @@ function isGrowthBookEnabled(): boolean {
|
|||
* attributes. Without this, there's no stable attribute to target them on
|
||||
* — only per-device IDs. See src/utils/auth.ts isAnthropicAuthEnabled().
|
||||
*
|
||||
* Returns undefined for unset/default (api.anthropic.com) so the attribute
|
||||
* is absent for direct-API users. Hostname only — no path/query/creds.
|
||||
* Returns undefined for unset/default Anthropic API host so the attribute is
|
||||
* absent for direct-API users. Hostname only — no path/query/creds.
|
||||
*/
|
||||
export function getApiBaseUrlHost(): string | undefined {
|
||||
const baseUrl = process.env.ANTHROPIC_BASE_URL
|
||||
if (!baseUrl) return undefined
|
||||
try {
|
||||
const host = new URL(baseUrl).host
|
||||
if (host === 'api.anthropic.com') return undefined
|
||||
if (host === DEFAULT_API_HOST) return undefined
|
||||
return host
|
||||
} catch {
|
||||
return undefined
|
||||
|
|
@ -565,11 +570,7 @@ const getGrowthBookClient = memoize(
|
|||
|
||||
const attributes = getUserAttributes()
|
||||
const clientKey = getGrowthBookClientKey()
|
||||
const baseUrl =
|
||||
process.env.CLAUDE_GB_ADAPTER_URL ||
|
||||
(process.env.USER_TYPE === 'ant'
|
||||
? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/'
|
||||
: 'https://api.anthropic.com/')
|
||||
const baseUrl = process.env.CLAUDE_GB_ADAPTER_URL
|
||||
const isAdapterMode = !!(
|
||||
process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY
|
||||
)
|
||||
|
|
@ -830,6 +831,16 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
|
|||
return localDefault !== undefined ? (localDefault as T) : defaultValue
|
||||
}
|
||||
|
||||
// LOCAL_GATE_DEFAULTS take priority over remote values and disk cache.
|
||||
// In fork/self-hosted deployments, the GrowthBook server may push false
|
||||
// for gates we intentionally enable. Local defaults represent the
|
||||
// project's intentional configuration and override everything except
|
||||
// env/config overrides (which are explicit user intent).
|
||||
const localDefault = getLocalGateDefault(feature)
|
||||
if (localDefault !== undefined) {
|
||||
return localDefault as T
|
||||
}
|
||||
|
||||
// Log experiment exposure if data is available, otherwise defer until after init
|
||||
if (experimentDataByFeature.has(feature)) {
|
||||
logExposureForFeature(feature)
|
||||
|
|
@ -838,10 +849,6 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
|
|||
}
|
||||
|
||||
// In-memory payload is authoritative once processRemoteEvalPayload has run.
|
||||
// Disk is also fresh by then (syncRemoteEvalToDisk runs synchronously inside
|
||||
// init), so this is correctness-equivalent to the disk read below — but it
|
||||
// skips the config JSON parse and is what onGrowthBookRefresh subscribers
|
||||
// depend on to read fresh values the instant they're notified.
|
||||
if (remoteEvalFeatureValues.has(feature)) {
|
||||
return remoteEvalFeatureValues.get(feature) as T
|
||||
}
|
||||
|
|
@ -853,14 +860,9 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
|
|||
return cached as T
|
||||
}
|
||||
} catch {
|
||||
// Config not yet initialized — fall through to local gate defaults
|
||||
// Config not yet initialized — fall through to defaultValue
|
||||
}
|
||||
// Disk cache miss (or config not initialized) — use local gate defaults
|
||||
// before falling back to the caller's defaultValue. This covers:
|
||||
// 1. GrowthBook "enabled" but never connected (caches empty)
|
||||
// 2. Config not yet initialized (early in startup)
|
||||
const localDefault = getLocalGateDefault(feature)
|
||||
return localDefault !== undefined ? (localDefault as T) : defaultValue
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -66,6 +66,17 @@ type QueuedEvent = {
|
|||
async: boolean
|
||||
}
|
||||
|
||||
function writeLocalAnalyticsEvent(
|
||||
eventName: string,
|
||||
metadata: LogEventMetadata,
|
||||
): void {
|
||||
// Local analytics stays independent of the optional upstream sinks.
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { writeLocalEvent } =
|
||||
require('./localSink.js') as typeof import('./localSink.js')
|
||||
writeLocalEvent(eventName, metadata as Record<string, unknown>)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sink interface for the analytics backend
|
||||
*/
|
||||
|
|
@ -136,13 +147,7 @@ export function logEvent(
|
|||
// to avoid accidentally logging code/filepaths
|
||||
metadata: LogEventMetadata,
|
||||
): void {
|
||||
// Local analytics: write EVERY event to a local JSONL file for self-analysis.
|
||||
// Runs before the upstream sink so local data is never lost even if the
|
||||
// upstream sink throws. Import is inline to avoid adding a module-level
|
||||
// dependency to this zero-dependency entry point.
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { writeLocalEvent } = require('./localSink.js') as typeof import('./localSink.js')
|
||||
writeLocalEvent(eventName, metadata as Record<string, unknown>)
|
||||
writeLocalAnalyticsEvent(eventName, metadata)
|
||||
|
||||
if (sink === null) {
|
||||
eventQueue.push({ eventName, metadata, async: false })
|
||||
|
|
@ -164,6 +169,8 @@ export async function logEventAsync(
|
|||
// intentionally no strings, to avoid accidentally logging code/filepaths
|
||||
metadata: LogEventMetadata,
|
||||
): Promise<void> {
|
||||
writeLocalAnalyticsEvent(eventName, metadata)
|
||||
|
||||
if (sink === null) {
|
||||
eventQueue.push({ eventName, metadata, async: true })
|
||||
return
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ import {
|
|||
isTeammate,
|
||||
} from '../../utils/teammate.js'
|
||||
import { feature } from 'bun:bundle'
|
||||
import { COMPUTER_USE_MCP_SERVER_NAME } from '../../utils/computerUse/common.js'
|
||||
|
||||
/**
|
||||
* Marker type for verifying analytics metadata doesn't contain sensitive data
|
||||
|
|
@ -126,9 +125,17 @@ export function isAnalyticsToolDetailsLoggingEnabled(
|
|||
* reservation (main.tsx, config.ts addMcpServer) is itself feature-gated, so
|
||||
* a user-configured 'computer-use' is possible in builds without the feature.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const BUILTIN_MCP_SERVER_NAMES: ReadonlySet<string> = new Set(
|
||||
feature('CHICAGO_MCP') ? [COMPUTER_USE_MCP_SERVER_NAME] : [],
|
||||
feature('CHICAGO_MCP')
|
||||
? [
|
||||
(
|
||||
require('../../utils/computerUse/common.js') as typeof import('../../utils/computerUse/common.js')
|
||||
).COMPUTER_USE_MCP_SERVER_NAME,
|
||||
]
|
||||
: [],
|
||||
)
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
|
||||
/**
|
||||
* Spreadable helper for logEvent payloads — returns {mcpServerName, mcpToolName}
|
||||
|
|
@ -735,7 +742,6 @@ export async function getEventMetadata(
|
|||
return metadata
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Core event metadata for 1P event logging (snake_case format).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ type LogEventMetadata = { [key: string]: boolean | number | undefined }
|
|||
const DATADOG_GATE_NAME = 'tengu_log_datadog_events'
|
||||
|
||||
// Module-level gate state - starts undefined, initialized during startup
|
||||
let isDatadogGateEnabled: boolean | undefined = undefined
|
||||
let isDatadogGateEnabled: boolean | undefined
|
||||
|
||||
/**
|
||||
* Check if Datadog tracking is enabled.
|
||||
|
|
|
|||
|
|
@ -42,7 +42,12 @@ async function _fetchMetricsEnabled(): Promise<MetricsEnabledResponse> {
|
|||
...authResult.headers,
|
||||
}
|
||||
|
||||
const endpoint = `https://api.anthropic.com/api/claude_code/organizations/metrics_enabled`
|
||||
const metricsBaseUrl = process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT
|
||||
if (!metricsBaseUrl) {
|
||||
throw new Error('Metrics endpoint is not configured')
|
||||
}
|
||||
|
||||
const endpoint = `${metricsBaseUrl}/api/claude_code/organizations/metrics_enabled`
|
||||
const response = await axios.get<MetricsEnabledResponse>(endpoint, {
|
||||
headers,
|
||||
timeout: 5000,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import { isEnvTruthy } from './envUtils.js'
|
|||
import { toError, shortErrorStack } from './errors.js'
|
||||
import { isEssentialTrafficOnly } from './privacyLevel.js'
|
||||
import { jsonParse } from './slowOperations.js'
|
||||
import { redactForLog } from './sensitive.js'
|
||||
|
||||
/**
|
||||
* Gets the display title for a log/session with fallback logic.
|
||||
|
|
@ -159,11 +158,7 @@ const isHardFailMode = memoize((): boolean => {
|
|||
export function logError(error: unknown): void {
|
||||
const err = toError(error)
|
||||
if (feature('HARD_FAIL') && isHardFailMode()) {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional crash output
|
||||
console.error(
|
||||
'[HARD FAIL] logError called with:',
|
||||
redactForLog(err.stack || err.message),
|
||||
)
|
||||
console.error('[HARD FAIL] logError called with:', err.stack || err.message)
|
||||
// eslint-disable-next-line custom-rules/no-process-exit
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
@ -235,7 +230,7 @@ export async function getErrorLogByIndex(
|
|||
async function loadLogList(path: string): Promise<LogOption[]> {
|
||||
let files: Awaited<ReturnType<typeof readdir>>
|
||||
try {
|
||||
files = await readdir(path, { withFileTypes: true }) as any
|
||||
files = (await readdir(path, { withFileTypes: true })) as any
|
||||
} catch {
|
||||
logError(new Error(`No logs found at ${path}`))
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -135,6 +135,9 @@ const shim = {
|
|||
clearResourceTimings: (() => {}) as typeof performance.clearResourceTimings,
|
||||
setResourceTimingBufferSize:
|
||||
(() => {}) as typeof performance.setResourceTimingBufferSize,
|
||||
// Node.js v22 undici internal calls this after every fetch — must exist to
|
||||
// avoid TypeError: markResourceTiming is not a function
|
||||
markResourceTiming: (() => {}) as any,
|
||||
// Delegate read-only properties to the original
|
||||
get timeOrigin() {
|
||||
return original.timeOrigin
|
||||
|
|
@ -148,7 +151,7 @@ const shim = {
|
|||
toJSON() {
|
||||
return original.toJSON()
|
||||
},
|
||||
} as typeof performance
|
||||
} as unknown as typeof performance
|
||||
|
||||
/**
|
||||
* Install the shim onto globalThis.performance. Safe to call multiple times.
|
||||
|
|
|
|||
|
|
@ -142,6 +142,13 @@ export function profileReport(): void {
|
|||
logForDebugging('Startup profiling report:')
|
||||
logForDebugging(getReport())
|
||||
}
|
||||
|
||||
// Clear startup marks to prevent PerformanceMark accumulation in long-lived
|
||||
// processes (daemon, cron). After this point startup marks are no longer needed
|
||||
// — the report has been written and the Statsig event has been logged.
|
||||
const perf = getPerformance()
|
||||
perf.clearMarks()
|
||||
memorySnapshots.length = 0
|
||||
}
|
||||
|
||||
export function isDetailedProfilingEnabled(): boolean {
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ export class BigQueryMetricsExporter implements PushMetricExporter {
|
|||
private isShutdown = false
|
||||
|
||||
constructor(options: { timeout?: number } = {}) {
|
||||
const defaultEndpoint = 'https://api.anthropic.com/api/claude_code/metrics'
|
||||
|
||||
if (
|
||||
process.env.USER_TYPE === 'ant' &&
|
||||
process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT
|
||||
|
|
@ -54,7 +52,7 @@ export class BigQueryMetricsExporter implements PushMetricExporter {
|
|||
process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT +
|
||||
'/api/claude_code/metrics'
|
||||
} else {
|
||||
this.endpoint = defaultEndpoint
|
||||
this.endpoint = ''
|
||||
}
|
||||
|
||||
this.timeout = options.timeout || 5000
|
||||
|
|
@ -88,6 +86,11 @@ export class BigQueryMetricsExporter implements PushMetricExporter {
|
|||
metrics: ResourceMetrics,
|
||||
resultCallback: (result: ExportResult) => void,
|
||||
): Promise<void> {
|
||||
if (!this.endpoint) {
|
||||
resultCallback({ code: ExportResultCode.SUCCESS })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Skip if trust not established in interactive mode
|
||||
// This prevents triggering apiKeyHelper before trust dialog
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ async function getOtlpReaders() {
|
|||
}
|
||||
case 'http/json': {
|
||||
const { OTLPMetricExporter } = await import(
|
||||
'@opentelemetry/exporter-metrics-otlp-http'
|
||||
'@opentelemetry/exporter-metrics-otlp-http' // package name, not endpoint
|
||||
)
|
||||
exporters.push(new OTLPMetricExporter(httpConfig))
|
||||
break
|
||||
|
|
@ -282,7 +282,7 @@ async function getOtlpLogExporters() {
|
|||
}
|
||||
case 'http/json': {
|
||||
const { OTLPLogExporter } = await import(
|
||||
'@opentelemetry/exporter-logs-otlp-http'
|
||||
'@opentelemetry/exporter-logs-otlp-http' // package name, not endpoint
|
||||
)
|
||||
exporters.push(new OTLPLogExporter(httpConfig))
|
||||
break
|
||||
|
|
@ -333,7 +333,7 @@ async function getOtlpTraceExporters() {
|
|||
}
|
||||
case 'http/json': {
|
||||
const { OTLPTraceExporter } = await import(
|
||||
'@opentelemetry/exporter-trace-otlp-http'
|
||||
'@opentelemetry/exporter-trace-otlp-http' // package name, not endpoint
|
||||
)
|
||||
exporters.push(new OTLPTraceExporter(httpConfig))
|
||||
break
|
||||
|
|
@ -373,6 +373,10 @@ function getBigQueryExportingReader() {
|
|||
}
|
||||
|
||||
function isBigQueryMetricsEnabled() {
|
||||
if (!process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT) {
|
||||
return false
|
||||
}
|
||||
|
||||
// BigQuery metrics are enabled for:
|
||||
// 1. API customers (excluding Claude.ai subscribers and Bedrock/Vertex)
|
||||
// 2. Claude for Enterprise (C4E) users
|
||||
|
|
@ -398,8 +402,8 @@ async function initializeBetaTracing(
|
|||
}
|
||||
|
||||
const [{ OTLPTraceExporter }, { OTLPLogExporter }] = await Promise.all([
|
||||
import('@opentelemetry/exporter-trace-otlp-http'),
|
||||
import('@opentelemetry/exporter-logs-otlp-http'),
|
||||
import('@opentelemetry/exporter-trace-otlp-http'), // package name, not endpoint
|
||||
import('@opentelemetry/exporter-logs-otlp-http'), // package name, not endpoint
|
||||
])
|
||||
|
||||
const httpConfig = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user