Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: (observability, samples): add tracing end-to-end sample #2130

Merged
merged 12 commits into from
Oct 30, 2024
102 changes: 102 additions & 0 deletions OBSERVABILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
## Observability with OpenTelemetry

This Cloud Spanner client supports [OpenTelemetry Traces](https://opentelemetry.io/), which gives insight into the client internals and aids in debugging/troubleshooting production issues.

By default, the functionality is disabled. You shall need to add OpenTelemetry dependencies, and must configure and
enable OpenTelemetry with appropriate exporters at the startup of your application:

**Table of contents:**

* [Observability](#observability)
* [Tracing](#tracing)
* [OpenTelemetry Dependencies](#opentelemetry-dependencies)
* [OpenTelemetry Configuration](#opentelemetry-configuration)
* [SQL Statement span annotation](#sql-statement-span-annotation)
* [OpenTelemetry gRCP instrumentation](#opentelemetry-grpc-instrumentation)
* [Tracing Sample](#tracing-sample)

### Tracing

#### OpenTelemetry Dependencies

Add the following dependencies in your `package.json` or install them directly.
```javascript
// Required packages for OpenTelemetry SDKs
"@opentelemetry/sdk-trace-base": "^1.26.0",
"@opentelemetry/sdk-trace-node": "^1.26.0",

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

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

#### OpenTelemetry Configuration

```javascript
const {
NodeTracerProvider,
TraceIdRatioBasedSampler,
} = require('@opentelemetry/sdk-trace-node');
const {
BatchSpanProcessor,
} = require('@opentelemetry/sdk-trace-base');
odeke-em marked this conversation as resolved.
Show resolved Hide resolved
const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the resource and these attributes as these were deprecated and not working

TraceExporter,
} = require('@google-cloud/opentelemetry-cloud-trace-exporter');
const exporter = new TraceExporter();

// Create the tracerProvider that the exporter shall be attached to.
const provider = new NodeTracerProvider({resource: resource});
provider.addSpanProcessor(new BatchSpanProcessor(exporter));

// Create the Cloud Spanner Client.
const {Spanner} = require('@google-cloud/spanner');
const spanner = new Spanner({
projectId: projectId,
observabilityOptions: {
// Inject the TracerProvider via SpannerOptions or
// register it as a global by invoking `provider.register()`
tracerProvider: provider,
},
});
```

#### SQL Statement span annotation

To allow your SQL statements to be annotated in the appropriate spans, you need to opt-in, because
SQL statements can contain sensitive personally-identifiable-information (PII).

You can opt-in by either:

* Setting the environment variable `SPANNER_ENABLE_EXTENDED_TRACING=true` before your application is started
* In code, setting `enableExtendedTracing: true` in your SpannerOptions before creating the Cloud Spanner client

```javascript
const spanner = new Spanner({
projectId: projectId,
observabilityOptions: {
tracerProvider: provider,
enableExtendedTracing: true,
},
```

#### OpenTelemetry gRPC instrumentation

Optionally, you can enable OpenTelemetry gRPC instrumentation which produces traces of executed remote procedure calls (RPCs)
in your programs by these imports and instantiation. You could pass in the traceProvider or register it globally
by invoking `tracerProvider.register()`

```javascript
const {registerInstrumentations} = require('@opentelemetry/instrumentation');
const {GrpcInstrumentation} = require('@opentelemetry/instrumentation-grpc');
registerInstrumentations({
odeke-em marked this conversation as resolved.
Show resolved Hide resolved
tracerProvider: tracerProvider,
instrumentations: [new GrpcInstrumentation()],
});
```

#### Tracing Sample
For more information please see this [sample code](./samples/observability-traces.js)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre
| Numeric-add-column | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/numeric-add-column.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/numeric-add-column.js,samples/README.md) |
| Numeric-query-parameter | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/numeric-query-parameter.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/numeric-query-parameter.js,samples/README.md) |
| Numeric-update-data | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/numeric-update-data.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/numeric-update-data.js,samples/README.md) |
| Observability (Tracing) with OpenTelemetry | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/observability-traces.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/observability-traces.js,samples/README.md) |
| Adds a column to an existing table in a Spanner PostgreSQL database. | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-add-column.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/pg-add-column.js,samples/README.md) |
| Showcase the rules for case-sensitivity and case folding for a Spanner PostgreSQL database. | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-case-sensitivity.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/pg-case-sensitivity.js,samples/README.md) |
| Creates a PostgreSQL Database. | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-database-create.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/pg-database-create.js,samples/README.md) |
Expand Down
18 changes: 18 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ and automatic, synchronous replication for high availability.
* [Numeric-add-column](#numeric-add-column)
* [Numeric-query-parameter](#numeric-query-parameter)
* [Numeric-update-data](#numeric-update-data)
* [Observability (Tracing) with OpenTelemetry](#observability-tracing-with-opentelemetry)
* [Adds a column to an existing table in a Spanner PostgreSQL database.](#adds-a-column-to-an-existing-table-in-a-spanner-postgresql-database.)
* [Showcase the rules for case-sensitivity and case folding for a Spanner PostgreSQL database.](#showcase-the-rules-for-case-sensitivity-and-case-folding-for-a-spanner-postgresql-database.)
* [Creates a PostgreSQL Database.](#creates-a-postgresql-database.)
Expand Down Expand Up @@ -1268,6 +1269,23 @@ __Usage:__



### Observability (Tracing) with OpenTelemetry

View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/observability-traces.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/observability-traces.js,samples/README.md)

__Usage:__


`node observability-traces.js <PROJECT-ID> <INSTANCE-ID> <DATABASE-ID>`


-----




### Adds a column to an existing table in a Spanner PostgreSQL database.

View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-add-column.js).
Expand Down
112 changes: 112 additions & 0 deletions samples/observability-traces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*!
* Copyright 2024 Google LLC. All Rights Reserved.
*
* 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.
*/

// sample-metadata:
// title: Observability (Tracing) with OpenTelemetry
// usage: node observability-traces.js <PROJECT-ID> <INSTANCE-ID> <DATABASE-ID>

'use strict';

// Setup OpenTelemetry and the trace exporter.
const {
NodeTracerProvider,
TraceIdRatioBasedSampler,
} = require('@opentelemetry/sdk-trace-node');
const {BatchSpanProcessor} = require('@opentelemetry/sdk-trace-base');

// Create the Google Cloud Trace exporter for OpenTelemetry.
const {
TraceExporter,
} = require('@google-cloud/opentelemetry-cloud-trace-exporter');
const exporter = new TraceExporter();

// Create the OpenTelemetry tracerProvider that the exporter shall be attached to.
const provider = new NodeTracerProvider({
// Modify the following line to adjust the sampling rate.
// It is currently set to 1.0, meaning all requests will be traced.
sampler: new TraceIdRatioBasedSampler(1.0),
});
provider.addSpanProcessor(new BatchSpanProcessor(exporter));

// Uncomment following line to register global tracerProvider instead
// of passing it into SpannerOptions.observabilityOptions.
// provider.register();

// Set `enableGrpcInstrumentation` to `true` to enable gRPC instrumentation.
const enableGrpcInstrumentation = false;
if (enableGrpcInstrumentation) {
const {registerInstrumentations} = require('@opentelemetry/instrumentation');
const {GrpcInstrumentation} = require('@opentelemetry/instrumentation-grpc');
registerInstrumentations({
odeke-em marked this conversation as resolved.
Show resolved Hide resolved
tracerProvider: provider,
instrumentations: [new GrpcInstrumentation()],
});
}

async function main(
projectId = 'my-project-id',
instanceId = 'my-instance-id',
databaseId = 'my-project-id'
) {
// Create the Cloud Spanner Client.
const {Spanner} = require('@google-cloud/spanner');

/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// const projectId = 'my-project-id';
// const instanceId = 'my-instance-id';
// const databaseId = 'my-database-id';

const spanner = new Spanner({
projectId: projectId,
observabilityOptions: {
tracerProvider: provider,
enableExtendedTracing: true,
},
});

// Acquire the database handle.
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

try {
const query = {
sql: 'SELECT 1',
};
const [rows] = await database.run(query);
console.log(`Query: ${rows.length} found.`);
surbhigarg92 marked this conversation as resolved.
Show resolved Hide resolved
rows.forEach(row => console.log(row));
} finally {
spanner.close();
}

provider.forceFlush();

// This sleep gives ample time for the trace
surbhigarg92 marked this conversation as resolved.
Show resolved Hide resolved
// spans to be exported to Google Cloud Trace.
await new Promise(resolve => {
setTimeout(() => {
resolve();
}, 8800);
});
}

process.on('unhandledRejection', err => {
console.error(err.message);
process.exitCode = 1;
});
main(...process.argv.slice(2));
9 changes: 7 additions & 2 deletions samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@
"@google-cloud/kms": "^4.0.0",
"@google-cloud/precise-date": "^4.0.0",
"@google-cloud/spanner": "^7.14.0",
"yargs": "^17.0.0",
"protobufjs": "^7.0.0"
"protobufjs": "^7.0.0",
"yargs": "^17.0.0"
},
"devDependencies": {
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
"@opentelemetry/instrumentation": "^0.53.0",
"@opentelemetry/instrumentation-grpc": "^0.53.0",
"@opentelemetry/sdk-trace-base": "^1.26.0",
"@opentelemetry/sdk-trace-node": "^1.26.0",
"chai": "^4.2.0",
"mocha": "^9.0.0",
"p-limit": "^3.0.1"
Expand Down
10 changes: 10 additions & 0 deletions samples/system-test/spanner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const alterTableWithForeignKeyDeleteCascadeCommand =
'node table-alter-with-foreign-key-delete-cascade.js';
const dropForeignKeyConstraintDeleteCascaseCommand =
'node table-drop-foreign-key-constraint-delete-cascade.js';
const traceObservabilityCommand = 'node observability-traces.js';

const CURRENT_TIME = Math.round(Date.now() / 1000).toString();
const PROJECT_ID = process.env.GCLOUD_PROJECT;
Expand Down Expand Up @@ -1574,6 +1575,15 @@ describe('Autogenerated Admin Clients', () => {
);
});

describe('observability', () => {
it('traces', () => {
const output = execSync(
`${traceObservabilityCommand} ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID}`
);
assert.match(output, /Query: \d+ found./);
});
});

describe('leader options', () => {
before(async () => {
const instance = spanner.instance(SAMPLE_INSTANCE_ID);
Expand Down
Loading