Skip to content
Open
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
3 changes: 3 additions & 0 deletions core/packages/gax/src/clientInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export interface ClientOptions
universe_domain?: string;
/**
* Whether to enable telemetry tracing for the client.
*
* The `GOOGLE_SDK_NODE_ENABLE_TRACING` environment variable overrides this
* option whenever it is set.
*/
enableTelemetryTracing?: boolean;
}
Expand Down
41 changes: 35 additions & 6 deletions core/packages/gax/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,45 @@
globalThis.crypto?.randomUUID() || require('crypto').randomUUID();

/**
* Checks if telemetry tracing is enabled
* Checks if telemetry tracing is enabled.
*
* Tracing is opt-in, and a caller opts in either with the
* `enableTelemetryTracing` client option or with the
* `GOOGLE_SDK_NODE_ENABLE_TRACING` environment variable. The environment
* variable wins whenever it is set, so tracing can be switched on or off for a
* process without touching the code that constructs the client.
*
* Two further conditions apply while the feature is experimental:
* `GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED` must be `true`, and the client
* must have supplied `internalTelemetryInfo` — a client generated without
* tracing has no span metadata to report, so there is nothing to trace.
*
* @param settings
* @returns true if telemetry tracing is enabled, false otherwise
*/
export function checkTelemetryEnabled(settings?: CallSettings): boolean {
const tracingEnabled =
Boolean(settings?.enableTelemetryTracing) &&
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED === 'true' &&
settings?.otherArgs?.internalTelemetryInfo !== undefined;
return Boolean(tracingEnabled);
// `process` is undeclared in browsers and some edge runtimes, where reading
// it would throw a ReferenceError rather than yield undefined, so it is
// reached through a `typeof` guard and stands in as an empty environment.
// Tracing is then simply off there, since the environment cannot opt in.
const env: Record<string, string | undefined> =
typeof process === 'object' && typeof process.env === 'object'
? process.env
: {};

// An absent or empty environment variable counts as unset, which is what
// separates it from an explicit `false`: only an explicit value overrides
// the opt-in the caller passed in `clientOptions`.
const envOptIn = env.GOOGLE_SDK_NODE_ENABLE_TRACING?.trim();
const tracingRequested = envOptIn
? envOptIn.toLowerCase() === 'true'
: Boolean(settings?.enableTelemetryTracing);

return (
tracingRequested &&
env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED === 'true' &&
settings?.otherArgs?.internalTelemetryInfo !== undefined
);
}

function words(str: string, normalize = false) {
Expand Down Expand Up @@ -144,7 +173,7 @@

// Given a proto Any and a set of protos, decode using the set of protos.
export const decodeProtobufAny = (
anyValue: any,

Check warning on line 176 in core/packages/gax/src/util.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
protobuf: protobuf.Type,
): protobuf.Message<{}> => {
if (anyValue.type_url === '') {
Expand All @@ -167,7 +196,7 @@
// Proto is Any we try to decode with protos in protobuf.
const decodedAnyProto = decodeProtobufAny(proto, protobuf);
protoListDecoded.push(decodedAnyProto);
} catch (e: any) {

Check warning on line 199 in core/packages/gax/src/util.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
// Skip we can't process it.
}
continue;
Expand Down
71 changes: 71 additions & 0 deletions core/packages/gax/test/unit/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ describe('util.ts', () => {
describe('checkTelemetryEnabled', () => {
afterEach(() => {
delete process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED;
delete process.env.GOOGLE_SDK_NODE_ENABLE_TRACING;
});

const mockTelemetryInfo: StaticTraceContext = {
Expand Down Expand Up @@ -257,5 +258,75 @@ describe('util.ts', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true';
assert.strictEqual(checkTelemetryEnabled(undefined), false);
});

it('returns true when GOOGLE_SDK_NODE_ENABLE_TRACING=true and the client option is not set', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true';
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'true';
const noOptInSettings = new CallSettings({
otherArgs: {
internalTelemetryInfo: mockTelemetryInfo,
},
});
assert.strictEqual(checkTelemetryEnabled(noOptInSettings), true);
});

it('accepts GOOGLE_SDK_NODE_ENABLE_TRACING case-insensitively', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true';
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'TRUE';
const noOptInSettings = new CallSettings({
otherArgs: {
internalTelemetryInfo: mockTelemetryInfo,
},
});
assert.strictEqual(checkTelemetryEnabled(noOptInSettings), true);
});

it('returns false when GOOGLE_SDK_NODE_ENABLE_TRACING=false overrides the client option', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true';
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'false';
assert.strictEqual(checkTelemetryEnabled(mockSettings), false);
});

it('falls back to the client option when GOOGLE_SDK_NODE_ENABLE_TRACING is empty', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true';
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = '';
assert.strictEqual(checkTelemetryEnabled(mockSettings), true);
});

it('treats an unrecognized GOOGLE_SDK_NODE_ENABLE_TRACING value as disabled', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true';
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'yes';
assert.strictEqual(checkTelemetryEnabled(mockSettings), false);
});

it('returns false when GOOGLE_SDK_NODE_ENABLE_TRACING=true but the experimental flag is unset', () => {
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'true';
assert.strictEqual(checkTelemetryEnabled(mockSettings), false);
});

it('returns false when GOOGLE_SDK_NODE_ENABLE_TRACING=true but internalTelemetryInfo is not set', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true';
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'true';
assert.strictEqual(checkTelemetryEnabled(new CallSettings({})), false);
});

it('does not throw when process is undefined, as in a browser', () => {
const globals = globalThis as {process?: NodeJS.Process};
const originalProcess = globals.process;
// The result is captured and `process` restored before asserting,
// because mocha and assert need `process` themselves.
let result: boolean | undefined;
let thrown: unknown;
delete globals.process;
try {
result = checkTelemetryEnabled(mockSettings);
} catch (e) {
thrown = e;
} finally {
globals.process = originalProcess;
}
assert.strictEqual(thrown, undefined);
assert.strictEqual(result, false);
});
});
});
Loading