Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions handwritten/spanner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import * as v1 from './v1';
import {
ObservabilityOptions,
ensureInitialContextManagerSet,
isTracingEnabled,
} from './instrument';
import {
attributeXGoogSpannerRequestIdToActiveSpan,
Expand Down Expand Up @@ -496,12 +497,14 @@ class Spanner extends GrpcService {
this.directedReadOptions = directedReadOptions;
this.defaultTransactionOptions = defaultTransactionOptions;
this._observabilityOptions = options.observabilityOptions;
if (isTracingEnabled(this._observabilityOptions)) {
ensureInitialContextManagerSet();
}
this.sessionLabels = options.sessionLabels || null;
this.commonHeaders_ = getCommonHeaders(
this.projectFormattedName_,
this._observabilityOptions?.enableEndToEndTracing,
);
ensureInitialContextManagerSet();
this._nthClientId = nextSpannerClientId();
this._universeDomain = universeEndpoint;
this.projectId_ = options.projectId;
Expand Down Expand Up @@ -1721,14 +1724,16 @@ class Spanner extends GrpcService {
config.headers[CLOUD_RESOURCE_HEADER],
projectId!,
);
// Do context propagation
propagation.inject(context.active(), config.headers, {
set: (carrier, key, value) => {
carrier[key] = value; // Set the span context (trace and span ID)
},
});
// Attach the x-goog-spanner-request-id to the currently active span.
attributeXGoogSpannerRequestIdToActiveSpan(config);
if (isTracingEnabled(this._observabilityOptions)) {
// Do context propagation
propagation.inject(context.active(), config.headers, {
set: (carrier, key, value) => {
carrier[key] = value; // Set the span context (trace and span ID)
},
});
// Attach the x-goog-spanner-request-id to the currently active span.
attributeXGoogSpannerRequestIdToActiveSpan(config);
}
const interceptors: any[] = [];
if (this._metricsEnabled) {
interceptors.push(MetricInterceptor);
Expand Down
65 changes: 65 additions & 0 deletions handwritten/spanner/src/instrument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,67 @@ function ensureInitialContextManagerSet() {

export {ensureInitialContextManagerSet};

let globalTracingEnabled: boolean | undefined = undefined;
let lastCheckTime = 0;
const CACHE_TTL_MS = 10000; // 10 seconds TTL

/**
* isGlobalTracingEnabled returns true if tracing is enabled globally,
* respecting cached status and active recording spans.
*
* @returns {boolean} True if global tracing is enabled.
*/
Comment thread
surbhigarg92 marked this conversation as resolved.
function isGlobalTracingEnabled(): boolean {
const now = Date.now();
if (
globalTracingEnabled !== undefined &&
(globalTracingEnabled || now - lastCheckTime < CACHE_TTL_MS)
) {
return globalTracingEnabled;
Comment thread
surbhigarg92 marked this conversation as resolved.
}

lastCheckTime = now;
const globalProvider = trace.getTracerProvider();
if (globalProvider) {
let delegate = globalProvider;
if (typeof (globalProvider as any).getDelegate === 'function') {
delegate = (globalProvider as any).getDelegate();
}
if (delegate) {
const name = delegate.constructor.name;
// Exclude the dummy NoopTracerProvider and uninitialized ProxyTracerProvider
if (name !== 'NoopTracerProvider' && name !== 'ProxyTracerProvider') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this check depends on the stability of these class names. That has two problems:

  • If OpenTelemetry decides to change the name, then this check is no longer valid. This is however unlikely.
  • If a project is minified, then these class names can be mangled by the minifier, which would (probably) break this check. At least, as far as I know, minifiers do not actively change this type of checks when minifying code.

An alternative could be to do the check like this:

const probeSpan = globalProvider
  .getTracer(TRACER_NAME, TRACER_VERSION)
  .startSpan('probe');
// isSpanContextValid returns false for NoopSpan, as it does not have a traceID.
const isValid = trace.isSpanContextValid(probeSpan.spanContext());
probeSpan.end();
if (isValid) {
  globalTracingEnabled = true;
  ensureInitialContextManagerSet();
  return true;
}

globalTracingEnabled = true;
ensureInitialContextManagerSet();
return true;
}
}
Comment thread
surbhigarg92 marked this conversation as resolved.
}
globalTracingEnabled = false;
return false;
}

/**
* isTracingEnabled returns true if tracing is enabled for the given options
* or globally.
*
* @param {ObservabilityOptions} [opts] The observability options.
* @returns {boolean} True if tracing is enabled.
*/
export function isTracingEnabled(opts?: ObservabilityOptions): boolean {
if (opts?.tracerProvider) {
return true;
}

return isGlobalTracingEnabled();
}

/** Only exported for resetting state in unit tests. */
export function _resetTracingEnabledForTest(): void {
globalTracingEnabled = undefined;
lastCheckTime = 0;
}

/**
* startTrace begins an active span in the current active context
* and passes it back to the set callback function. Each span will
Expand All @@ -132,6 +193,10 @@ export function startTrace<T>(
config: traceConfig | undefined,
cb: (span: Span) => T,
): T {
if (!isTracingEnabled(config?.opts)) {
return cb(new noopSpan());
}

if (!config) {
config = {} as traceConfig;
}
Expand Down
56 changes: 13 additions & 43 deletions handwritten/spanner/src/request_id_header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const randIdForProcess = randomBytes(8)
.readUint32LE(0)
.toString(16)
.padStart(8, '0');
const REQUEST_HEADER_VERSION = 1;
const PROCESS_PREFIX = `${REQUEST_HEADER_VERSION}.${randIdForProcess}.`;
const X_GOOG_SPANNER_REQUEST_ID_HEADER = 'x-goog-spanner-request-id';

class AtomicCounter {
Expand Down Expand Up @@ -57,15 +59,13 @@ class AtomicCounter {
}
}

const REQUEST_HEADER_VERSION = 1;

function craftRequestId(
nthClientId: number,
channelId: number,
nthRequest: number,
attempt: number,
) {
return `${REQUEST_HEADER_VERSION}.${randIdForProcess}.${nthClientId}.${channelId}.${nthRequest}.${attempt}`;
return `${PROCESS_PREFIX}${nthClientId}.${channelId}.${nthRequest}.${attempt}`;
}

const nthClientId = new AtomicCounter();
Expand Down Expand Up @@ -118,15 +118,6 @@ function injectRequestIDIntoError(config: any, err: Error) {
}
}

interface withNextNthRequest {
_nextNthRequest: Function;
}

interface withMetadataWithRequestId {
_nthClientId: number;
_channelId: number;
}

function injectRequestIDIntoHeaders(
headers: {[k: string]: string},
session: any,
Expand All @@ -136,52 +127,31 @@ function injectRequestIDIntoHeaders(
if (!session) {
return headers;
}

const database = session.parent;
if (!nthRequest) {
const database = session.parent as withNextNthRequest;
if (!(database && typeof database._nextNthRequest === 'function')) {
if (!database || typeof database._nextNthRequest !== 'function') {
return headers;
}
nthRequest = database._nextNthRequest();
}
const clientId = database ? database._nthClientId || 1 : 1;
const channelId = database ? database._channelId || 1 : 1;

attempt = attempt || 1;
return _metadataWithRequestId(session, nthRequest!, attempt, headers);
}

function _metadataWithRequestId(
session: any,
nthRequest: number,
attempt: number,
priorMetadata?: {[k: string]: string},
): {[k: string]: string} {
if (!priorMetadata) {
priorMetadata = {};
}
const withReqId = {
...priorMetadata,
};
const database = session.parent as withMetadataWithRequestId;
let clientId = 1;
let channelId = 1;
if (database) {
clientId = database._nthClientId || 1;
channelId = database._channelId || 1;
}
const withReqId = {...headers};
withReqId[X_GOOG_SPANNER_REQUEST_ID_HEADER] = craftRequestId(
clientId,
channelId,
nthRequest,
attempt,
nthRequest || 1,
attempt || 1,
);
return withReqId;
}

function nextNthRequest(database): number {
if (!(database && typeof database._nextNthRequest === 'function')) {
return 1;
if (database && typeof database._nextNthRequest === 'function') {
return database._nextNthRequest();
}
return database._nextNthRequest();
return 1;
}

export interface RequestIDError extends grpc.ServiceError {
Expand Down
Loading
Loading