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
31 changes: 23 additions & 8 deletions handwritten/spanner/OBSERVABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,24 @@ enable OpenTelemetry with appropriate exporters at the startup of your applicati
#### OpenTelemetry Dependencies

Add the following dependencies in your `package.json` or install them directly.

> **Note:** `@google-cloud/spanner` uses the OpenTelemetry **v2** SDK. The versions
> below are the v2-compatible releases. If you are upgrading from an older version
> of this client, see the [OpenTelemetry JS 2.x migration guide](https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/upgrade-to-2.x.md).

```javascript
// Required packages for OpenTelemetry SDKs
"@opentelemetry/sdk-trace-base": "^1.26.0",
"@opentelemetry/sdk-trace-node": "^1.26.0",
"@opentelemetry/sdk-trace-base": "^2.11.0",
"@opentelemetry/sdk-trace-node": "^2.0.0",
"@opentelemetry/resources": "^2.11.0",
"@opentelemetry/semantic-conventions": "^1.30.0",

// Package to use Google Cloud Trace exporter
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",

// Packages to enable gRPC instrumentation
"@opentelemetry/instrumentation": "^0.53.0",
"@opentelemetry/instrumentation-grpc": "^0.53.0",
"@opentelemetry/instrumentation": "^0.222.0",
"@opentelemetry/instrumentation-grpc": "^0.222.0",
```

#### OpenTelemetry Configuration
Expand All @@ -40,17 +47,25 @@ const {
NodeTracerProvider,
TraceIdRatioBasedSampler,
} = require('@opentelemetry/sdk-trace-node');
const {
BatchSpanProcessor,
} = require('@opentelemetry/sdk-trace-base');
const {BatchSpanProcessor} = require('@opentelemetry/sdk-trace-base');
const {
TraceExporter,
} = require('@google-cloud/opentelemetry-cloud-trace-exporter');
const {resourceFromAttributes} = require('@opentelemetry/resources');
const {ATTR_SERVICE_NAME} = require('@opentelemetry/semantic-conventions');
const exporter = new TraceExporter();

// Describe the service that is emitting the traces.
// Note: in OpenTelemetry v2 the `Resource` class was replaced by the
// `resourceFromAttributes` factory function.
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'my-service-name',
});

// Create the tracerProvider that the exporter shall be attached to.
const provider = new NodeTracerProvider({
resource: resource,
sampler: new TraceIdRatioBasedSampler(0.1), // sample 10%
Comment thread
olavloite marked this conversation as resolved.
spanProcessors: [new BatchSpanProcessor(exporter)]
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import {trace} from '@opentelemetry/api';
import {NodeTracerProvider} from '@opentelemetry/sdk-trace-node';
import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-grpc';
import {Resource} from '@opentelemetry/resources';
import {resourceFromAttributes} from '@opentelemetry/resources';
import {ATTR_SERVICE_NAME} from '@opentelemetry/semantic-conventions';
import {
BatchSpanProcessor,
Expand All @@ -45,17 +45,17 @@
url: 'https://test-telemetry.sandbox.googleapis.com',
credentials: grpc.credentials.combineChannelCredentials(
grpc.credentials.createSsl(),
grpc.credentials.createFromGoogleCredential(authenticatedClient as any),

Check warning on line 48 in handwritten/spanner/google-cloud-spanner-executor/src/cloud-util.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
),
});

const provider = new NodeTracerProvider({
resource: new Resource({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'spanner-node-worker-proxy',
'gcp.project_id': WorkerProxy.PROJECT_ID,
}) as any,

Check warning on line 56 in handwritten/spanner/google-cloud-spanner-executor/src/cloud-util.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
sampler: new TraceIdRatioBasedSampler(this.TRACE_SAMPLING_RATE),
spanProcessors: [new BatchSpanProcessor(traceExporter as any)],

Check warning on line 58 in handwritten/spanner/google-cloud-spanner-executor/src/cloud-util.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
});

provider.register();
Expand All @@ -71,7 +71,7 @@
* Creates the configuration object for the Spanner client for connecting to a
* test GFE, including gRPC channel setup.
*/
public static getSpannerOptions(): any {

Check warning on line 74 in handwritten/spanner/google-cloud-spanner-executor/src/cloud-util.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const options: SpannerOptions = {
projectId: WorkerProxy.PROJECT_ID,
servicePath: 'localhost',
Expand Down Expand Up @@ -106,7 +106,7 @@
this.TEST_HOST_IN_CERT;
}

(options as any).grpcOptions = grpcOptions;

Check warning on line 109 in handwritten/spanner/google-cloud-spanner-executor/src/cloud-util.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

return options;
}
Expand Down
10 changes: 5 additions & 5 deletions handwritten/spanner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"@babel/traverse": "7.27.7",
"@google-cloud/common": "^6.0.0",
"@google-cloud/monitoring": "^5.0.0",
"@google-cloud/opentelemetry-resource-util": "^2.4.0",
"@google-cloud/precise-date": "^5.0.0",
"@google-cloud/promisify": "^5.0.0",
"@google-cloud/spanner-api": "^0.2.0",
Expand All @@ -65,8 +64,9 @@
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^2.0.0",
"@opentelemetry/core": "^2.0.0",
"@opentelemetry/resources": "^1.8.0",
"@opentelemetry/sdk-metrics": "^1.30.1",
"@opentelemetry/resource-detector-gcp": "^0.57.0",
"@opentelemetry/resources": "^2.11.0",
"@opentelemetry/sdk-metrics": "^2.11.0",
"@opentelemetry/semantic-conventions": "^1.30.0",
"@types/big.js": "^6.2.2",
"@types/stack-trace": "^0.0.33",
Expand All @@ -87,9 +87,9 @@
},
"devDependencies": {
"@grpc/reflection": "^1.0.4",
"@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/sdk-trace-base": "^2.11.0",
"@opentelemetry/sdk-trace-node": "^2.0.0",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.57.0",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.222.0",
"@types/concat-stream": "^2.0.3",
"@types/extend": "^3.0.4",
"@types/is": "^0.0.25",
Expand Down
90 changes: 53 additions & 37 deletions handwritten/spanner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
IProtoMessageParams,
IProtoEnumParams,
} from './codec';
import {context, propagation} from '@opentelemetry/api';
import {context, propagation, ROOT_CONTEXT} from '@opentelemetry/api';
import {Backup} from './backup';
import {Database} from './database';
import {
Expand Down Expand Up @@ -169,7 +169,7 @@
>;
observabilityOptions?: ObservabilityOptions;
disableBuiltInMetrics?: boolean;
interceptors?: any[];

Check warning on line 172 in handwritten/spanner/src/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
sessionLabels?: {[key: string]: string};
/**
* The Trusted Cloud Domain (TPC) DNS of the service used to make requests.
Expand Down Expand Up @@ -534,7 +534,7 @@
if (!this.clients_.has(clientName)) {
this.clients_.set(
clientName,
new v1[clientName](this.options as ClientOptions),
new v1.InstanceAdminClient(this.options as ClientOptions),
);
}
Comment thread
olavloite marked this conversation as resolved.
return this.clients_.get(clientName)! as v1.InstanceAdminClient;
Expand All @@ -558,7 +558,7 @@
if (!this.clients_.has(clientName)) {
this.clients_.set(
clientName,
new v1[clientName](this.options as ClientOptions),
new v1.DatabaseAdminClient(this.options as ClientOptions),
);
}
Comment thread
olavloite marked this conversation as resolved.
return this.clients_.get(clientName)! as v1.DatabaseAdminClient;
Expand Down Expand Up @@ -615,10 +615,9 @@

if (callback) {
// process.nextTick prevents Unhandled Promise Rejections if callback throws
res.then(
() => process.nextTick(() => callback(null)),
err => process.nextTick(() => callback(err)),
);
res
.then(() => process.nextTick(() => callback(null)))

Check warning on line 619 in handwritten/spanner/src/index.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
.catch(err => process.nextTick(() => callback(err)));

Check warning on line 620 in handwritten/spanner/src/index.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
} else {
return res;
}
Expand Down Expand Up @@ -1676,35 +1675,46 @@
!metricsExplicitlyDisabled && !this._isInSecureCredentials;
MetricsTracerFactory.enabled = this._metricsEnabled;
if (this._metricsEnabled) {
try {
this.auth.getProjectId((err, projectId) => {
if (err || !projectId) {
console.error(
'Unable to get Project Id for client side metrics, will skip exporting client' +
' side metrics' +
err,
);
return;
}
const initializeMetrics = (projectId: string) => {
this.projectId_ = projectId;
const factory = MetricsTracerFactory.getInstance(projectId);
if (factory && !factory.hasMetricReaders()) {
context.with(ROOT_CONTEXT, () => {
const periodicReader = new PeriodicExportingMetricReader({
exporter: new CloudMonitoringMetricsExporter(
{auth: this.auth},
projectId,
),
exportIntervalMillis: 60000,
});
factory.getMeterProvider([periodicReader]);
});
}
};

this.projectId_ = projectId;
const factory = MetricsTracerFactory.getInstance(projectId);
const periodicReader = new PeriodicExportingMetricReader({
exporter: new CloudMonitoringMetricsExporter(
{auth: this.auth},
projectId,
),
exportIntervalMillis: 60000,
if (this.projectId_ && this.projectId_ !== '{{projectId}}') {
initializeMetrics(this.projectId_);
} else {
try {
this.auth.getProjectId((err, projectId) => {
if (err || !projectId) {
console.error(
'Unable to get Project Id for client side metrics, will skip exporting client' +
' side metrics' +
err,
);
return;
}

initializeMetrics(projectId);
});
// Retrieve the MeterProvider to trigger construction
factory!.getMeterProvider([periodicReader]);
});
} catch (err) {
console.error(
'Unable to configure client side metrics, will skip exporting client' +
' side metrics' +
err,
);
} catch (err) {
console.error(
'Unable to configure client side metrics, will skip exporting client' +
' side metrics' +
err,
);
}
}
}
}
Expand All @@ -1727,7 +1737,10 @@
const clientName = config.client;
try {
if (!this.clients_.has(clientName)) {
this.clients_.set(clientName, new v1[clientName](this.options));
this.clients_.set(
clientName,
new (v1 as Record<string, any>)[clientName](this.options),

Check warning on line 1742 in handwritten/spanner/src/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
);
}
} catch (err) {
callback(err, null);
Expand Down Expand Up @@ -1773,7 +1786,7 @@
// Attach the x-goog-spanner-request-id to the currently active span.
attributeXGoogSpannerRequestIdToActiveSpan(config);
}
const interceptors: any[] = [];

Check warning on line 1789 in handwritten/spanner/src/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
if (this._metricsEnabled) {
interceptors.push(MetricInterceptor);
}
Expand Down Expand Up @@ -1860,7 +1873,8 @@
if (
this._metricsEnabled &&
config.client === 'SpannerClient' &&
this.projectId_
this.projectId_ &&
this.projectId_ !== '{{projectId}}'
) {
metricsTracer =
MetricsTracerFactory?.getInstance(this.projectId_)?.createMetricsTracer(
Expand Down Expand Up @@ -1896,6 +1910,7 @@
.then(val => {
metricsTracer?.recordOperationCompletion();
resolve(val);
return val;
})
.catch(error => {
metricsTracer?.recordOperationCompletion();
Expand Down Expand Up @@ -1928,7 +1943,8 @@
if (
this._metricsEnabled &&
config.client === 'SpannerClient' &&
this.projectId_
this.projectId_ &&
this.projectId_ !== '{{projectId}}'
) {
metricsTracer =
MetricsTracerFactory?.getInstance(this.projectId_)?.createMetricsTracer(
Expand Down
43 changes: 19 additions & 24 deletions handwritten/spanner/src/metrics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
// 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.
import {
View,
ExplicitBucketHistogramAggregation,
} from '@opentelemetry/sdk-metrics';
import {AggregationType, ViewOptions} from '@opentelemetry/sdk-metrics';
Comment thread
olavloite marked this conversation as resolved.

export const SPANNER_METER_NAME = 'spanner-nodejs';
export const CLIENT_METRICS_PREFIX = 'spanner.googleapis.com/internal/client';
Expand All @@ -25,6 +22,7 @@ export const TRACER_CLEANUP_INTERVAL_MS = 30 * 60 * 1000; // 30 Minutes
// OTel semantic conventions
// See https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv
export const ATTR_CLOUD_REGION = 'cloud.region';
export const ATTR_CLOUD_AVAILABILITY_ZONE = 'cloud.availability_zone';

// Minimum period that must past between metric exports
export const MIN_EXPORT_FREQUENCY_MS = 30 * 1000;
Expand Down Expand Up @@ -91,33 +89,30 @@ export const HISTOGRAM_BUCKET_BOUNDARIES = [
];

// Defined Views for metric aggregation
export const OPERATION_LATENCY_VIEW = new View({
const HISTOGRAM_AGGREGATION = {
type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM as const,
options: {boundaries: HISTOGRAM_BUCKET_BOUNDARIES},
};

export const OPERATION_LATENCY_VIEW: ViewOptions = {
instrumentName: METRIC_NAME_OPERATION_LATENCIES,
aggregation: new ExplicitBucketHistogramAggregation(
HISTOGRAM_BUCKET_BOUNDARIES,
),
});
aggregation: HISTOGRAM_AGGREGATION,
};

export const ATTEMPT_LATENCY_VIEW = new View({
export const ATTEMPT_LATENCY_VIEW: ViewOptions = {
instrumentName: METRIC_NAME_ATTEMPT_LATENCIES,
aggregation: new ExplicitBucketHistogramAggregation(
HISTOGRAM_BUCKET_BOUNDARIES,
),
});
aggregation: HISTOGRAM_AGGREGATION,
};

export const GFE_LATENCY_VIEW = new View({
export const GFE_LATENCY_VIEW: ViewOptions = {
instrumentName: METRIC_NAME_GFE_LATENCIES,
aggregation: new ExplicitBucketHistogramAggregation(
HISTOGRAM_BUCKET_BOUNDARIES,
),
});
aggregation: HISTOGRAM_AGGREGATION,
};

export const AFE_LATENCY_VIEW = new View({
export const AFE_LATENCY_VIEW: ViewOptions = {
instrumentName: METRIC_NAME_AFE_LATENCIES,
aggregation: new ExplicitBucketHistogramAggregation(
HISTOGRAM_BUCKET_BOUNDARIES,
),
});
aggregation: HISTOGRAM_AGGREGATION,
};
Comment thread
alkatrivedi marked this conversation as resolved.
Comment thread
olavloite marked this conversation as resolved.

export const METRIC_VIEWS = [
OPERATION_LATENCY_VIEW,
Expand Down
13 changes: 13 additions & 0 deletions handwritten/spanner/src/metrics/external-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,16 @@ export enum ValueType {
DOUBLE = 'DOUBLE',
DISTRIBUTION = 'DISTRIBUTION',
}

/**
* A Google Cloud Monitoring monitored resource.
*
* Previously imported from `@google-cloud/opentelemetry-resource-util`, which is
* deprecated and scheduled for archival. The interface is a plain data shape, so
* it is declared locally instead.
* See https://cloud.google.com/monitoring/api/ref_v3/rest/v3/MonitoredResource
*/
export interface MonitoredResource {
type: string;
labels: {[key: string]: string};
}
Loading
Loading