Skip to content

Commit a477344

Browse files
committed
feat(observability): trace Transaction
This change adds observability tracing for Transaction along with tests. Updates googleapis#2079 Built from PR googleapis#2087 Updates googleapis#2114
1 parent d51aae9 commit a477344

File tree

2 files changed

+562
-277
lines changed

2 files changed

+562
-277
lines changed

observability-test/transaction.ts

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
import * as assert from 'assert';
18+
import {grpc} from 'google-gax';
19+
import {google} from '../protos/protos';
20+
import {Database, Instance, SessionPool, Snapshot, Spanner} from '../src';
21+
import protobuf = google.spanner.v1;
22+
import * as mock from '../test/mockserver/mockspanner';
23+
import * as mockInstanceAdmin from '../test/mockserver/mockinstanceadmin';
24+
import * as mockDatabaseAdmin from '../test/mockserver/mockdatabaseadmin';
25+
const {
26+
AlwaysOnSampler,
27+
NodeTracerProvider,
28+
InMemorySpanExporter,
29+
} = require('@opentelemetry/sdk-trace-node');
30+
// eslint-disable-next-line n/no-extraneous-require
31+
const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base');
32+
33+
/** A simple result set for SELECT 1. */
34+
function createSelect1ResultSet(): protobuf.ResultSet {
35+
const fields = [
36+
protobuf.StructType.Field.create({
37+
name: 'NUM',
38+
type: protobuf.Type.create({code: protobuf.TypeCode.INT64}),
39+
}),
40+
];
41+
const metadata = new protobuf.ResultSetMetadata({
42+
rowType: new protobuf.StructType({
43+
fields,
44+
}),
45+
});
46+
return protobuf.ResultSet.create({
47+
metadata,
48+
rows: [{values: [{stringValue: '1'}]}],
49+
});
50+
}
51+
52+
interface setupResults {
53+
server: grpc.Server;
54+
spanner: Spanner;
55+
spannerMock: mock.MockSpanner;
56+
}
57+
58+
const selectSql = 'SELECT 1';
59+
const updateSql = 'UPDATE FOO SET BAR=1 WHERE BAZ=2';
60+
61+
async function setup(): Promise<setupResults> {
62+
const server = new grpc.Server();
63+
64+
const spannerMock = mock.createMockSpanner(server);
65+
mockInstanceAdmin.createMockInstanceAdmin(server);
66+
mockDatabaseAdmin.createMockDatabaseAdmin(server);
67+
68+
const port: number = await new Promise((resolve, reject) => {
69+
server.bindAsync(
70+
'0.0.0.0:0',
71+
grpc.ServerCredentials.createInsecure(),
72+
(err, assignedPort) => {
73+
if (err) {
74+
reject(err);
75+
} else {
76+
resolve(assignedPort);
77+
}
78+
}
79+
);
80+
});
81+
spannerMock.putStatementResult(
82+
selectSql,
83+
mock.StatementResult.resultSet(createSelect1ResultSet())
84+
);
85+
spannerMock.putStatementResult(
86+
updateSql,
87+
mock.StatementResult.updateCount(1)
88+
);
89+
90+
const spanner = new Spanner({
91+
projectId: 'observability-project-id',
92+
servicePath: 'localhost',
93+
port,
94+
sslCreds: grpc.credentials.createInsecure(),
95+
});
96+
97+
return Promise.resolve({
98+
spanner: spanner,
99+
server: server,
100+
spannerMock: spannerMock,
101+
});
102+
}
103+
104+
describe('Transaction', () => {
105+
let server: grpc.Server;
106+
let spanner: Spanner;
107+
let database: Database;
108+
let spannerMock: mock.MockSpanner;
109+
let traceExporter: typeof InMemorySpanExporter;
110+
111+
after(() => {
112+
spanner.close();
113+
server.tryShutdown(() => {});
114+
});
115+
116+
before(async () => {
117+
const setupResult = await setup();
118+
spanner = setupResult.spanner;
119+
server = setupResult.server;
120+
spannerMock = setupResult.spannerMock;
121+
122+
const selectSql = 'SELECT 1';
123+
const updateSql = 'UPDATE FOO SET BAR=1 WHERE BAZ=2';
124+
const upsertSql = 'INSERTORUPDATE INTO FOO(BAR, BAZ) VALUES(@bar, @baz)';
125+
spannerMock.putStatementResult(
126+
selectSql,
127+
mock.StatementResult.resultSet(createSelect1ResultSet())
128+
);
129+
spannerMock.putStatementResult(
130+
updateSql,
131+
mock.StatementResult.updateCount(1)
132+
);
133+
spannerMock.putStatementResult(
134+
upsertSql,
135+
mock.StatementResult.updateCount(1)
136+
);
137+
138+
traceExporter = new InMemorySpanExporter();
139+
const sampler = new AlwaysOnSampler();
140+
141+
const provider = new NodeTracerProvider({
142+
sampler: sampler,
143+
exporter: traceExporter,
144+
});
145+
provider.addSpanProcessor(new SimpleSpanProcessor(traceExporter));
146+
147+
const instance = spanner.instance('instance');
148+
database = instance.database('database');
149+
database.observabilityConfig = {
150+
tracerProvider: provider,
151+
enableExtendedTracing: false,
152+
};
153+
});
154+
155+
beforeEach(() => {
156+
spannerMock.resetRequests();
157+
});
158+
159+
afterEach(() => {
160+
traceExporter.reset();
161+
});
162+
163+
it('run', done => {
164+
database.runTransaction((err, tx) => {
165+
assert.ifError(err);
166+
assert.strictEqual(!tx, false, 'Expecting a non-null transaction');
167+
console.log('db.runTransaction');
168+
// Reset the trace exporter to clear any prior spans
169+
// so that we only have spans for Transaction.
170+
// traceExporter.reset();
171+
172+
console.log('ts.run', tx);
173+
console.log('fini');
174+
tx!.run('SELECT 1', (err, rows) => {
175+
console.log('tx!.run');
176+
traceExporter.forceFlush();
177+
const spans = traceExporter.getFinishedSpans();
178+
assert.strictEqual(spans.length, 2, 'Exactly 2 spans expected');
179+
180+
const actualSpanNames: string[] = [];
181+
spans.forEach(span => {
182+
actualSpanNames.push(span.name);
183+
});
184+
185+
const expectedSpanNames = ['CloudSpanner.Transaction.upsert'];
186+
assert.deepStrictEqual(
187+
actualSpanNames,
188+
expectedSpanNames,
189+
`span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}`
190+
);
191+
192+
done();
193+
});
194+
});
195+
});
196+
});

0 commit comments

Comments
 (0)