Skip to content

Commit 66d99e8

Browse files
odeke-emgcf-owl-bot[bot]surbhigarg92
authored
feat: (observability, samples): add tracing end-to-end sample (#2130)
* 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]>
1 parent 701e226 commit 66d99e8

File tree

6 files changed

+250
-2
lines changed

6 files changed

+250
-2
lines changed

OBSERVABILITY.md

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
## Observability with OpenTelemetry
2+
3+
This Cloud Spanner client supports [OpenTelemetry Traces](https://opentelemetry.io/), which gives insight into the client internals and aids in debugging/troubleshooting production issues.
4+
5+
By default, the functionality is disabled. You shall need to add OpenTelemetry dependencies, and must configure and
6+
enable OpenTelemetry with appropriate exporters at the startup of your application:
7+
8+
**Table of contents:**
9+
10+
* [Observability](#observability)
11+
* [Tracing](#tracing)
12+
* [OpenTelemetry Dependencies](#opentelemetry-dependencies)
13+
* [OpenTelemetry Configuration](#opentelemetry-configuration)
14+
* [SQL Statement span annotation](#sql-statement-span-annotation)
15+
* [OpenTelemetry gRCP instrumentation](#opentelemetry-grpc-instrumentation)
16+
* [Tracing Sample](#tracing-sample)
17+
18+
### Tracing
19+
20+
#### OpenTelemetry Dependencies
21+
22+
Add the following dependencies in your `package.json` or install them directly.
23+
```javascript
24+
// Required packages for OpenTelemetry SDKs
25+
"@opentelemetry/sdk-trace-base": "^1.26.0",
26+
"@opentelemetry/sdk-trace-node": "^1.26.0",
27+
28+
// Package to use Google Cloud Trace exporter
29+
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
30+
31+
// Packages to enable gRPC instrumentation
32+
"@opentelemetry/instrumentation": "^0.53.0",
33+
"@opentelemetry/instrumentation-grpc": "^0.53.0",
34+
```
35+
36+
#### OpenTelemetry Configuration
37+
38+
```javascript
39+
const {
40+
NodeTracerProvider,
41+
TraceIdRatioBasedSampler,
42+
} = require('@opentelemetry/sdk-trace-node');
43+
const {
44+
BatchSpanProcessor,
45+
} = require('@opentelemetry/sdk-trace-base');
46+
const {
47+
TraceExporter,
48+
} = require('@google-cloud/opentelemetry-cloud-trace-exporter');
49+
const exporter = new TraceExporter();
50+
51+
// Create the tracerProvider that the exporter shall be attached to.
52+
const provider = new NodeTracerProvider({resource: resource});
53+
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
54+
55+
// Create the Cloud Spanner Client.
56+
const {Spanner} = require('@google-cloud/spanner');
57+
const spanner = new Spanner({
58+
projectId: projectId,
59+
observabilityOptions: {
60+
// Inject the TracerProvider via SpannerOptions or
61+
// register it as a global by invoking `provider.register()`
62+
tracerProvider: provider,
63+
},
64+
});
65+
```
66+
67+
#### SQL Statement span annotation
68+
69+
To allow your SQL statements to be annotated in the appropriate spans, you need to opt-in, because
70+
SQL statements can contain sensitive personally-identifiable-information (PII).
71+
72+
You can opt-in by either:
73+
74+
* Setting the environment variable `SPANNER_ENABLE_EXTENDED_TRACING=true` before your application is started
75+
* In code, setting `enableExtendedTracing: true` in your SpannerOptions before creating the Cloud Spanner client
76+
77+
```javascript
78+
const spanner = new Spanner({
79+
projectId: projectId,
80+
observabilityOptions: {
81+
tracerProvider: provider,
82+
enableExtendedTracing: true,
83+
},
84+
```
85+
86+
#### OpenTelemetry gRPC instrumentation
87+
88+
Optionally, you can enable OpenTelemetry gRPC instrumentation which produces traces of executed remote procedure calls (RPCs)
89+
in your programs by these imports and instantiation. You could pass in the traceProvider or register it globally
90+
by invoking `tracerProvider.register()`
91+
92+
```javascript
93+
const {registerInstrumentations} = require('@opentelemetry/instrumentation');
94+
const {GrpcInstrumentation} = require('@opentelemetry/instrumentation-grpc');
95+
registerInstrumentations({
96+
tracerProvider: tracerProvider,
97+
instrumentations: [new GrpcInstrumentation()],
98+
});
99+
```
100+
101+
#### Tracing Sample
102+
For more information please see this [sample code](./samples/observability-traces.js)

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre
155155
| 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) |
156156
| 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) |
157157
| 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) |
158+
| 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) |
158159
| 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) |
159160
| 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) |
160161
| 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) |

samples/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ and automatic, synchronous replication for high availability.
8080
* [Numeric-add-column](#numeric-add-column)
8181
* [Numeric-query-parameter](#numeric-query-parameter)
8282
* [Numeric-update-data](#numeric-update-data)
83+
* [Observability (Tracing) with OpenTelemetry](#observability-tracing-with-opentelemetry)
8384
* [Adds a column to an existing table in a Spanner PostgreSQL database.](#adds-a-column-to-an-existing-table-in-a-spanner-postgresql-database.)
8485
* [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.)
8586
* [Creates a PostgreSQL Database.](#creates-a-postgresql-database.)
@@ -1268,6 +1269,23 @@ __Usage:__
12681269

12691270

12701271

1272+
### Observability (Tracing) with OpenTelemetry
1273+
1274+
View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/observability-traces.js).
1275+
1276+
[![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)
1277+
1278+
__Usage:__
1279+
1280+
1281+
`node observability-traces.js <PROJECT-ID> <INSTANCE-ID> <DATABASE-ID>`
1282+
1283+
1284+
-----
1285+
1286+
1287+
1288+
12711289
### Adds a column to an existing table in a Spanner PostgreSQL database.
12721290

12731291
View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-add-column.js).

samples/observability-traces.js

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*!
2+
* Copyright 2024 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// sample-metadata:
18+
// title: Observability (Tracing) with OpenTelemetry
19+
// usage: node observability-traces.js <PROJECT-ID> <INSTANCE-ID> <DATABASE-ID>
20+
21+
'use strict';
22+
23+
// Setup OpenTelemetry and the trace exporter.
24+
const {
25+
NodeTracerProvider,
26+
TraceIdRatioBasedSampler,
27+
} = require('@opentelemetry/sdk-trace-node');
28+
const {BatchSpanProcessor} = require('@opentelemetry/sdk-trace-base');
29+
30+
// Create the Google Cloud Trace exporter for OpenTelemetry.
31+
const {
32+
TraceExporter,
33+
} = require('@google-cloud/opentelemetry-cloud-trace-exporter');
34+
const exporter = new TraceExporter();
35+
36+
// Create the OpenTelemetry tracerProvider that the exporter shall be attached to.
37+
const provider = new NodeTracerProvider({
38+
// Modify the following line to adjust the sampling rate.
39+
// It is currently set to 1.0, meaning all requests will be traced.
40+
sampler: new TraceIdRatioBasedSampler(1.0),
41+
});
42+
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
43+
44+
// Uncomment following line to register global tracerProvider instead
45+
// of passing it into SpannerOptions.observabilityOptions.
46+
// provider.register();
47+
48+
// Set `enableGrpcInstrumentation` to `true` to enable gRPC instrumentation.
49+
const enableGrpcInstrumentation = false;
50+
if (enableGrpcInstrumentation) {
51+
const {registerInstrumentations} = require('@opentelemetry/instrumentation');
52+
const {GrpcInstrumentation} = require('@opentelemetry/instrumentation-grpc');
53+
registerInstrumentations({
54+
tracerProvider: provider,
55+
instrumentations: [new GrpcInstrumentation()],
56+
});
57+
}
58+
59+
async function main(
60+
projectId = 'my-project-id',
61+
instanceId = 'my-instance-id',
62+
databaseId = 'my-project-id'
63+
) {
64+
// Create the Cloud Spanner Client.
65+
const {Spanner} = require('@google-cloud/spanner');
66+
67+
/**
68+
* TODO(developer): Uncomment these variables before running the sample.
69+
*/
70+
// const projectId = 'my-project-id';
71+
// const instanceId = 'my-instance-id';
72+
// const databaseId = 'my-database-id';
73+
74+
const spanner = new Spanner({
75+
projectId: projectId,
76+
observabilityOptions: {
77+
tracerProvider: provider,
78+
enableExtendedTracing: true,
79+
},
80+
});
81+
82+
// Acquire the database handle.
83+
const instance = spanner.instance(instanceId);
84+
const database = instance.database(databaseId);
85+
86+
try {
87+
const query = {
88+
sql: 'SELECT 1',
89+
};
90+
const [rows] = await database.run(query);
91+
console.log(`Query: ${rows.length} found.`);
92+
rows.forEach(row => console.log(row));
93+
} finally {
94+
spanner.close();
95+
}
96+
97+
provider.forceFlush();
98+
99+
// This sleep gives ample time for the trace
100+
// spans to be exported to Google Cloud Trace.
101+
await new Promise(resolve => {
102+
setTimeout(() => {
103+
resolve();
104+
}, 8800);
105+
});
106+
}
107+
108+
process.on('unhandledRejection', err => {
109+
console.error(err.message);
110+
process.exitCode = 1;
111+
});
112+
main(...process.argv.slice(2));

samples/package.json

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818
"@google-cloud/kms": "^4.0.0",
1919
"@google-cloud/precise-date": "^4.0.0",
2020
"@google-cloud/spanner": "^7.14.0",
21-
"yargs": "^17.0.0",
22-
"protobufjs": "^7.0.0"
21+
"protobufjs": "^7.0.0",
22+
"yargs": "^17.0.0"
2323
},
2424
"devDependencies": {
25+
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
26+
"@opentelemetry/instrumentation": "^0.53.0",
27+
"@opentelemetry/instrumentation-grpc": "^0.53.0",
28+
"@opentelemetry/sdk-trace-base": "^1.26.0",
29+
"@opentelemetry/sdk-trace-node": "^1.26.0",
2530
"chai": "^4.2.0",
2631
"mocha": "^9.0.0",
2732
"p-limit": "^3.0.1"

samples/system-test/spanner.test.js

+10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const alterTableWithForeignKeyDeleteCascadeCommand =
5050
'node table-alter-with-foreign-key-delete-cascade.js';
5151
const dropForeignKeyConstraintDeleteCascaseCommand =
5252
'node table-drop-foreign-key-constraint-delete-cascade.js';
53+
const traceObservabilityCommand = 'node observability-traces.js';
5354

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

1578+
describe('observability', () => {
1579+
it('traces', () => {
1580+
const output = execSync(
1581+
`${traceObservabilityCommand} ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID}`
1582+
);
1583+
assert.match(output, /Query: \d+ found./);
1584+
});
1585+
});
1586+
15771587
describe('leader options', () => {
15781588
before(async () => {
15791589
const instance = spanner.instance(SAMPLE_INSTANCE_ID);

0 commit comments

Comments
 (0)