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
125 changes: 101 additions & 24 deletions core/packages/gax/samples/observability.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,38 @@
// [START gax_observability]
'use strict';

// 1. IMPORT OPENTELEMETRY MODULES
const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node');
const {BatchSpanProcessor} = require('@opentelemetry/sdk-trace-base');
const {
TraceExporter,
} = require('@google-cloud/opentelemetry-cloud-trace-exporter');

// 2. CONFIGURE TRACING: SET UP A TRACER PROVIDER AND EXPORTER
const cloudTraceExporter = new TraceExporter();
const spanProcessor = new BatchSpanProcessor(cloudTraceExporter);

const provider = new NodeTracerProvider({
spanProcessors: [spanProcessor],
});
provider.register();

// 3. ENABLE TRACING SPANS WITH ENV VARIABLE
// Sets the flag before client libraries or RPC callers initialize
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'true';

// 4. IMPORT CLIENT LIBRARIES AFTER OPENTELEMETRY SETUP
// Replace with your Google Cloud client library, for example:
// const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
// Logging and tracing in Google Cloud Node.js client libraries are completely
// orthogonal: you can enable logging without OpenTelemetry tracing
// (`!tracingEnabled && loggingEnabled`), tracing without logging, or both
// simultaneously. When both are active, failed RPC logs automatically include
// `telemetryTraceId` and `telemetrySpanId` for trace-log correlation.

async function main() {
// [START gax_observability_tracing]
async function enableTracing() {
// 1. IMPORT OPENTELEMETRY MODULES
const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node');
const {BatchSpanProcessor} = require('@opentelemetry/sdk-trace-base');
const {
TraceExporter,
} = require('@google-cloud/opentelemetry-cloud-trace-exporter');

// 2. CONFIGURE TRACING: SET UP A TRACER PROVIDER AND EXPORTER
const cloudTraceExporter = new TraceExporter();
const spanProcessor = new BatchSpanProcessor(cloudTraceExporter);

const provider = new NodeTracerProvider({
spanProcessors: [spanProcessor],
});
provider.register();

// 3. ENABLE TRACING SPANS
// Set the flag before client libraries or RPC callers initialize, or pass
// `{enableTelemetryTracing: true}` to an individual client constructor.
process.env.GOOGLE_SDK_NODE_ENABLE_TRACING = 'true';

// 4. IMPORT CLIENT LIBRARIES AFTER OPENTELEMETRY SETUP
// Replace with your Google Cloud client library, for example:
// const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
// const client = new SecretManagerServiceClient();
// await client.listSecrets({parent: 'projects/my-project'});

Expand All @@ -50,6 +57,76 @@ async function main() {
await provider.forceFlush();
console.log('Tracing initialized successfully.');
}
// [END gax_observability_tracing]

// [START gax_observability_logging]
async function enableLogging() {
// Telemetry logging does not require OpenTelemetry setup (`!tracingEnabled && loggingEnabled`).
const util = require('util');
const {loggingUtils} = require('google-gax');

// 1. ENABLE TELEMETRY LOGGING
// In production, prefer `{enableTelemetryLogging: true}` on the client or a
// service-scoped filter (e.g. `GOOGLE_SDK_NODE_LOGGING=secretmanager`) over
// `*`, `true`, or `all` so `'auth'` debug logs remain disabled.
process.env.GOOGLE_SDK_NODE_LOGGING = 'secretmanager';

// Or enable logging for a specific client without setting an env variable:
// const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
// const client = new SecretManagerServiceClient({enableTelemetryLogging: true});

// 2. CONFIGURE AN OUTPUT BACKEND (DEFAULT NODEBACKEND OR PLUGGABLE BACKEND)
// Option A — Default `NodeBackend` (writes formatted log lines to `stderr`):
// No configuration is needed to use the default backend (`undefined` resets to default).
loggingUtils.setBackend(undefined);

// Option B — Built-in structured JSON backend (writes Cloud Logging JSON):
// loggingUtils.setBackend(loggingUtils.getStructuredBackend());

// Option C — Pluggable custom backend (e.g. Pino, Winston, Bunyan, or custom filtering):
const customBackend = {
setFilters() {
// Receives filters parsed from `GOOGLE_SDK_NODE_LOGGING`, if needed.
},
log(namespace, fields, ...args) {
// Skip `'auth'` debug logs if only client RPC telemetry logs are desired.
if (namespace === 'auth') {
return;
}
const entry = {
namespace,
severity: fields.severity,
message: util.format(...args),
...(fields.telemetryTraceId
? {telemetryTraceId: fields.telemetryTraceId}
: {}),
...(fields.telemetrySpanId
? {telemetrySpanId: fields.telemetrySpanId}
: {}),
...(fields.other || {}),
};
// Forward `entry` to your preferred logger (Pino, Winston, stdout, etc.):
console.log(JSON.stringify(entry));
},
};
loggingUtils.setBackend(customBackend);

// 3. CALL CLIENT LIBRARIES AS USUAL
// Failed API calls emit structured `WARNING` logs and failed transport
// attempts emit `DEBUG` logs through the configured backend.
// await client.listSecrets({parent: 'projects/my-project'});

console.log('Logging initialized successfully.');
}
// [END gax_observability_logging]

async function main() {
// Enable logging independently (works with or without OpenTelemetry tracing):
await enableLogging();

// Enable OpenTelemetry tracing independently (works with or without logging):
await enableTracing();
}

main().catch(console.error);
// [END gax_observability]
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const cwd = path.join(__dirname, '..');
describe('Observability', () => {
it('should run observability sample', async () => {
const stdout = execSync('node observability.js', {cwd});
assert(/Logging initialized successfully/.test(stdout));
assert(/Tracing initialized successfully/.test(stdout));
});
});
10 changes: 5 additions & 5 deletions core/packages/gax/src/clientInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ export interface ClientOptions
/**
* Whether to enable telemetry logging for the client.
*
* Logging is only enabled when `GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING` is enabled
* AND either `enableTelemetryLogging: true` is passed here or, when unset, the
* `GOOGLE_SDK_NODE_LOGGING` environment variable matches the client service
* namespace. Explicitly setting `enableTelemetryLogging: false` disables logging
* for this client even when `GOOGLE_SDK_NODE_LOGGING` matches.
* Logging is only enabled when `enableTelemetryLogging: true` is passed here
* or, when unset, the `GOOGLE_SDK_NODE_LOGGING` environment variable matches
* the client service namespace. Explicitly setting
* `enableTelemetryLogging: false` disables logging for this client even when
* `GOOGLE_SDK_NODE_LOGGING` matches.
*/
enableTelemetryLogging?: boolean;
}
Expand Down
15 changes: 3 additions & 12 deletions core/packages/gax/src/gax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,8 @@ export function constructSettings(
? isEnvEnabled
: Boolean(enableTelemetryTracing);

const experimentalLogging =
env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING?.trim().toLowerCase();
const isExperimentalLoggingEnabled =
experimentalLogging === 'true' || experimentalLogging === '1';
const isLoggingEnvEnabled =
isExperimentalLoggingEnabled && enableTelemetryLogging === undefined
enableTelemetryLogging === undefined
? checkLoggingEnabled(new CallSettings({apiName: serviceName}))
: false;
const resolvedLogging =
Expand All @@ -935,8 +931,7 @@ export function constructSettings(
: isLoggingEnvEnabled
? true
: undefined;
const loggingRequested =
isExperimentalLoggingEnabled && Boolean(resolvedLogging);
const loggingRequested = Boolean(resolvedLogging);

otherArgs = otherArgs || {};

Expand Down Expand Up @@ -1007,11 +1002,7 @@ export function constructSettings(
: otherArgs,
apiName,
enableTelemetryTracing: isEnvSet ? isEnvEnabled : enableTelemetryTracing,
enableTelemetryLogging: isExperimentalLoggingEnabled
? resolvedLogging
: enableTelemetryLogging !== undefined
? false
: undefined,
enableTelemetryLogging: resolvedLogging,
});
}

Expand Down
10 changes: 5 additions & 5 deletions core/packages/gax/src/grpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ export interface GrpcClientOptions extends GoogleAuthOptions {
/**
* Whether to enable telemetry logging for the client.
*
* Logging is only enabled when `GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING` is enabled
* AND either `enableTelemetryLogging: true` is passed here or, when unset, the
* `GOOGLE_SDK_NODE_LOGGING` environment variable matches the client service
* namespace. Explicitly setting `enableTelemetryLogging: false` disables logging
* for this client even when `GOOGLE_SDK_NODE_LOGGING` matches.
* Logging is only enabled when `enableTelemetryLogging: true` is passed here
* or, when unset, the `GOOGLE_SDK_NODE_LOGGING` environment variable matches
* the client service namespace. Explicitly setting
* `enableTelemetryLogging: false` disables logging for this client even when
* `GOOGLE_SDK_NODE_LOGGING` matches.
*/
enableTelemetryLogging?: boolean;
}
Expand Down
30 changes: 12 additions & 18 deletions core/packages/gax/src/observability/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,36 +71,30 @@ function matchesLoggingEnvFilter(envOptIn: string, namespace: string): boolean {
/**
* Checks if telemetry logging is enabled.
*
* Logging is only enabled when `GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING` is enabled
* AND either a user explicitly passes `enableTelemetryLogging: true` in their
* client/call options or, when `enableTelemetryLogging` is unset, the
* `GOOGLE_SDK_NODE_LOGGING` environment variable matches the client service
* namespace using `google-logging-utils` filter semantics (`'*'`, exact `'all'` /
* `'true'`, or comma-separated wildcard patterns). Passing
* `enableTelemetryLogging: false` suppresses logging for that client/call even
* when `GOOGLE_SDK_NODE_LOGGING` matches.
* Logging is only enabled when a user explicitly passes
* `enableTelemetryLogging: true` in their client/call options or, when
* `enableTelemetryLogging` is unset, the `GOOGLE_SDK_NODE_LOGGING` environment
* variable matches the client service namespace using `google-logging-utils`
* filter semantics (`'*'`, exact `'all'` / `'true'`, or comma-separated
* wildcard patterns). Passing `enableTelemetryLogging: false` suppresses
* logging for that client/call even when `GOOGLE_SDK_NODE_LOGGING` matches.
*
* @param settings
* @returns true if telemetry logging is enabled, false otherwise
*/
export function checkLoggingEnabled(settings?: CallSettings): boolean {
const env: Record<string, string | undefined> =
typeof process === 'object' && typeof process.env === 'object'
? process.env
: {};
const experimentalOptIn =
env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING?.trim().toLowerCase();
if (experimentalOptIn !== 'true' && experimentalOptIn !== '1') {
return false;
}

if (typeof settings?.enableTelemetryLogging === 'boolean') {
return settings.enableTelemetryLogging;
}
if (settings?.otherArgs?.enableTelemetryLogging === true) {
return true;
}

const env: Record<string, string | undefined> =
typeof process === 'object' && typeof process.env === 'object'
? process.env
: {};

const envOptIn = env.GOOGLE_SDK_NODE_LOGGING;
if (!envOptIn) {
return false;
Expand Down
4 changes: 0 additions & 4 deletions core/packages/gax/test/unit/apiCallable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1876,14 +1876,12 @@ describe('createApiCall', () => {
loggingUtils.setBackend(sink);
harness = new OtelHarness();
harness.setup();
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING = 'true';
});

afterEach(() => {
clearMetadataCache();
loggingUtils.setBackend(undefined);
harness.teardown();
delete process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING;
delete process.env.GOOGLE_SDK_NODE_LOGGING;
delete process.env.GOOGLE_SDK_NODE_ENABLE_TRACING;
});
Expand Down Expand Up @@ -2809,11 +2807,9 @@ describe('createApiCall', () => {

for (const childEnv of [
{
GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING: 'true',
CHILD_ENABLE_OPT: 'true',
},
{
GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING: 'true',
GOOGLE_SDK_NODE_LOGGING: 'storage,example',
},
]) {
Expand Down
10 changes: 3 additions & 7 deletions core/packages/gax/test/unit/gax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ describe('gax construct settings', () => {
afterEach(() => {
delete process.env.GOOGLE_SDK_NODE_ENABLE_TRACING;
delete process.env.GOOGLE_SDK_NODE_LOGGING;
delete process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING;
});

it('creates settings', () => {
Expand Down Expand Up @@ -280,8 +279,7 @@ describe('gax construct settings', () => {
assert.strictEqual(settings.otherArgs.internalMethodName, undefined);
});

it('creates settings with enableTelemetryLogging when GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING=true', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING = 'true';
it('creates settings with enableTelemetryLogging', () => {
const otherArgs = {key: 'value'};
const defaults = gax.constructSettings(
SERVICE_NAME,
Expand All @@ -301,7 +299,7 @@ describe('gax construct settings', () => {
assert.strictEqual(settings.otherArgs.enableTelemetryLogging, true);
});

it('disables logging when GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING is not set even if enableTelemetryLogging=true', () => {
it('creates settings with enableTelemetryLogging set to false', () => {
const defaults = gax.constructSettings(
SERVICE_NAME,
A_CONFIG,
Expand All @@ -310,15 +308,14 @@ describe('gax construct settings', () => {
{},
undefined,
undefined,
true,
false,
);
const settings = defaults.bundlingMethod;
assert.strictEqual(settings.enableTelemetryLogging, false);
assert.strictEqual(settings.otherArgs.internalMethodName, undefined);
});

it('enables logging when enableTelemetryLogging is unset and GOOGLE_SDK_NODE_LOGGING matches the service namespace or wildcard', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING = 'true';
for (const envValue of ['true', '*', 'all', 'auth,interface']) {
process.env.GOOGLE_SDK_NODE_LOGGING = envValue;
const defaults = gax.constructSettings(
Expand Down Expand Up @@ -360,7 +357,6 @@ describe('gax construct settings', () => {
});

it('preserves enableTelemetryLogging=true when GOOGLE_SDK_NODE_LOGGING targets another namespace or is false', () => {
process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_LOGGING = 'true';
for (const envValue of ['auth,storage', 'false']) {
process.env.GOOGLE_SDK_NODE_LOGGING = envValue;
const enabledDefaults = gax.constructSettings(
Expand Down
Loading
Loading