"use strict"; /** * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ATTEMPT_SPAN_KEY = exports.CLIENT_REQUEST_SPAN_KEY = exports.DEFAULT_GCP_REPO = void 0; exports.extractClientServiceFromPackageName = extractClientServiceFromPackageName; exports.extractServiceFromApiName = extractServiceFromApiName; exports.extractFromSettings = extractFromSettings; exports.extractFromEnvironment = extractFromEnvironment; exports.clearMetadataCache = clearMetadataCache; exports.resolveStaticTraceContext = resolveStaticTraceContext; exports.buildInitialAttributes = buildInitialAttributes; exports.getActiveTraceContext = getActiveTraceContext; const api_1 = require("@opentelemetry/api"); const util_1 = require("../util"); exports.DEFAULT_GCP_REPO = 'googleapis/google-cloud-node'; // Context keys for GAX client request and per-attempt spans. exports.CLIENT_REQUEST_SPAN_KEY = (0, api_1.createContextKey)('google-gax-client-request-span'); exports.ATTEMPT_SPAN_KEY = (0, api_1.createContextKey)('google-gax-attempt-span'); /** * Derives the GCP client service name from an NPM package name. * @deprecated Retained for backwards compatibility. */ function extractClientServiceFromPackageName(name) { const scopedMatch = name.match(/^@[^/]+\/(?:google-cloud-)?(.*)$/); if (scopedMatch && scopedMatch[1]) { return scopedMatch[1]; } const unscopedMatch = name.match(/^google-cloud-(.*)$/); if (unscopedMatch && unscopedMatch[1]) { return unscopedMatch[1]; } return name; } /** * Derives the GCP service name from a protobuf apiName (e.g. google.cloud.redis.v1.CloudRedis). */ function extractServiceFromApiName(apiName) { if (!apiName) { return undefined; } const parts = apiName.split('.'); const versionIndex = parts.findIndex(p => /^v\d+(?:[a-z\d_]+)?$/i.test(p)); if (versionIndex > 0) { return parts[versionIndex - 1]; } if (parts.length >= 2) { const candidate = parts[parts.length - 2]; if (candidate && candidate !== 'google' && candidate !== 'cloud') { return candidate; } const last = parts[parts.length - 1]; if (last) { return last[0].toLowerCase() + last.slice(1); } } return undefined; } /** * Extracts static metadata from CallSettings headers or apiName. */ function extractFromSettings(settings) { const result = {}; if (!settings) { return result; } // Extract artifact and version from x-goog-api-client header. const otherArgsWithMeta = settings.otherArgs; let rawClientHeader = otherArgsWithMeta?.headers?.['x-goog-api-client']; // Fall back to metadataBuilder if header is not in otherArgs.headers. if (!rawClientHeader && typeof otherArgsWithMeta?.metadataBuilder === 'function') { try { const built = otherArgsWithMeta.metadataBuilder(); // Support both gRPC Metadata instances (.get) and plain objects. if (built && typeof built.get === 'function') { rawClientHeader = built.get('x-goog-api-client'); } else if (built && typeof built === 'object') { rawClientHeader = built['x-goog-api-client']; } } catch { // Ignore metadataBuilder errors } } // Normalize array values into a single space-delimited string. const clientHeader = Array.isArray(rawClientHeader) ? rawClientHeader.join(' ') : rawClientHeader; if (typeof clientHeader === 'string') { const tokens = clientHeader.trim().split(/\s+/); for (const token of tokens) { const match = token.match(/^([^/]+(?:\/[^/]+)?)\/([^\s]+)$/); if (match) { const [, name, ver] = match; if (name === 'gapic') { result.gcpVersion = ver; } else if (name === 'gccl') { if (!result.gcpVersion) { result.gcpVersion = ver; } } else if (!util_1.ignoredClientHeaderTokens.includes(name)) { result.gcpArtifact = name; if (!result.gcpVersion) { result.gcpVersion = ver; } } } } } // Resolve service name and default artifact from apiName. if (typeof settings.apiName === 'string' && settings.apiName) { const serviceFromApi = extractServiceFromApiName(settings.apiName); if (serviceFromApi) { result.gcpClientService = serviceFromApi; if (!result.gcpArtifact) { result.gcpArtifact = serviceFromApi === 'gax' ? 'google-gax' : `@google-cloud/${serviceFromApi}`; } } } // Extract server address and port from endpoint settings. const otherArgs = settings.otherArgs; const endpoint = settings.servicePath || settings.apiEndpoint || otherArgs?.servicePath || otherArgs?.apiEndpoint; if (endpoint && typeof endpoint === 'string') { const match = endpoint.match(/^(\[[^\]]+\]|[^:]+):(\d+)$/); if (match) { result.serverAddress = match[1]; result.serverPort = Number(match[2]); } else { result.serverAddress = endpoint; } } // Resolve server port if not already parsed from endpoint. const rawPort = settings .port || settings .servicePort || otherArgs?.port || otherArgs?.servicePort; const port = typeof rawPort === 'string' ? parseInt(rawPort, 10) : rawPort; if (port && typeof port === 'number' && !result.serverPort) { result.serverPort = port; } return result; } /** * Extracts static metadata from process environment variables. */ function extractFromEnvironment() { const env = typeof process === 'object' && typeof process.env === 'object' ? process.env : {}; const result = {}; const service = env.GOOGLE_SDK_NODE_CLIENT_SERVICE || env.GCP_CLIENT_SERVICE; if (service?.trim()) { result.gcpClientService = service.trim(); } const version = env.GOOGLE_SDK_NODE_CLIENT_VERSION || env.GCP_CLIENT_VERSION; if (version?.trim()) { result.gcpVersion = version.trim(); } const artifact = env.GOOGLE_SDK_NODE_ARTIFACT || env.GCP_ARTIFACT; if (artifact?.trim()) { result.gcpArtifact = artifact.trim(); } const serverAddress = env.GOOGLE_SDK_NODE_SERVER_ADDRESS || env.SERVER_ADDRESS; if (serverAddress?.trim()) { result.serverAddress = serverAddress.trim(); } const serverPort = env.GOOGLE_SDK_NODE_SERVER_PORT || env.SERVER_PORT; if (serverPort && !isNaN(Number(serverPort))) { result.serverPort = Number(serverPort); } return result; } /** * Clears the cached static metadata. * @deprecated Static metadata is no longer cached across calls; retained as a no-op for backwards compatibility. */ function clearMetadataCache() { } /** * Resolves static trace context dynamically at runtime by inspecting CallSettings, * environment variables, and standard defaults. * * Precedence: environment variables > dynamic resolution from settings. * * @param {CallSettings} [settings] - Call settings for the RPC invocation. * @param {string} [_callerFilePath] - Optional explicit path to the caller source file (deprecated). * @returns {StaticTraceContext} The resolved static trace context. */ function resolveStaticTraceContext(settings, // eslint-disable-next-line @typescript-eslint/no-unused-vars _callerFilePath) { const envMeta = extractFromEnvironment(); const dynamic = extractFromSettings(settings); const context = { gcpClientService: envMeta.gcpClientService ?? dynamic.gcpClientService, gcpVersion: envMeta.gcpVersion ?? dynamic.gcpVersion, gcpRepo: exports.DEFAULT_GCP_REPO, gcpArtifact: envMeta.gcpArtifact ?? dynamic.gcpArtifact, }; const serverAddress = envMeta.serverAddress ?? dynamic.serverAddress; if (serverAddress !== undefined) { context.serverAddress = serverAddress; } const serverPort = envMeta.serverPort ?? dynamic.serverPort; if (serverPort !== undefined) { context.serverPort = serverPort; } return context; } /** * Builds the common initial client, transport, and domain attributes shared by * client request spans and low level network attempt spans. * * @param {DynamicTraceContext} dynamicArgs - Dynamic trace context for the call or attempt. * @param {StaticTraceContext} staticArgs - Static trace context for the client library. * @param {string} [urlDomain] - Resolved `url.domain` attribute value, if available. * @returns {Attributes} The initial span attributes map. */ function buildInitialAttributes(dynamicArgs, staticArgs, urlDomain) { const attributes = { 'gcp.client.service': staticArgs.gcpClientService, 'gcp.client.version': staticArgs.gcpVersion, 'gcp.client.repo': staticArgs.gcpRepo, 'gcp.client.artifact': staticArgs.gcpArtifact, 'rpc.system.name': dynamicArgs.rpcType, }; if (urlDomain !== undefined) { attributes['url.domain'] = urlDomain; } return attributes; } /** * Returns the span if defined and its span context is valid. */ function getValidSpan(span) { return span && (0, api_1.isSpanContextValid)(span.spanContext()) ? span : undefined; } /** * Returns the active trace and span IDs (`traceId` and `spanId`) along with * the active span references (`span`, `attemptSpan`, `clientRequestSpan`) if a * GAX attempt span, GAX client request span, or outer OpenTelemetry span is * active and has a valid span context, or `undefined` otherwise. * * @param {Context} [activeContext=context.active()] - The OpenTelemetry context to inspect. * @returns {ActiveTraceContext | undefined} The active trace context, or undefined. */ function getActiveTraceContext(activeContext = api_1.context.active()) { // Retrieve GAX-managed spans from the context if valid. const attemptSpan = getValidSpan(activeContext.getValue(exports.ATTEMPT_SPAN_KEY)); const clientRequestSpan = getValidSpan(activeContext.getValue(exports.CLIENT_REQUEST_SPAN_KEY)); // Prefer attempt span > client request span > outer OpenTelemetry span. const activeSpan = attemptSpan ?? clientRequestSpan ?? getValidSpan(api_1.trace.getSpan(activeContext)); if (!activeSpan) { return undefined; } const spanContext = activeSpan.spanContext(); return { traceId: spanContext.traceId, spanId: spanContext.spanId, span: activeSpan, attemptSpan, clientRequestSpan, }; } //# sourceMappingURL=contextUtil.js.map