Skip to content

Commit

Permalink
feat: (observability, samples): add tracing end-to-end sample (#2130)
Browse files Browse the repository at this point in the history
* feat: (observability, samples): add tracing end-to-end sample

This change documents an end-to-end observability tracing sample
using OpenTelemetry, which then exports trace spans to Google Cloud Trace.

Updates #2079

* Update with code review comments

* Minimize observability sample

* Tailor samples to match rubric

* Add OBSERVABILITY.md docs file

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* system-test: add sample test

* samples: remove demo of User Defined Traces

* Address review feedback

* Remove the vestige of tag until the team decides an appropriate one

* Address Surbhi's review feedback

* Update samples/system-test/spanner.test.js

---------

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
Co-authored-by: surbhigarg92 <[email protected]>
  • Loading branch information
3 people authored Oct 30, 2024
1 parent 701e226 commit 66d99e8
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 2 deletions.
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');
const {
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({
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({
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.`);
rows.forEach(row => console.log(row));
} finally {
spanner.close();
}

provider.forceFlush();

// This sleep gives ample time for the trace
// 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

0 comments on commit 66d99e8

Please sign in to comment.