Skip to content

Commit 9cd2de5

Browse files
committed
chore: add x_goog_spanner_request_id as attribute to spans
This change adds the x-goog-spanner-request-id value that is sent as a gRPC header, but as a span attribute with the key `x_goog_spanner_request_id` to aid in better debugging and correlation. Updates #2200
1 parent 8c886cb commit 9cd2de5

File tree

3 files changed

+68
-0
lines changed

3 files changed

+68
-0
lines changed

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import {
9090
ensureInitialContextManagerSet,
9191
} from './instrument';
9292
import {
93+
attributeXGoogSpannerRequestIdToActiveSpan,
9394
injectRequestIDIntoError,
9495
nextSpannerClientId,
9596
} from './request_id_header';
@@ -1562,6 +1563,8 @@ class Spanner extends GrpcService {
15621563
carrier[key] = value; // Set the span context (trace and span ID)
15631564
},
15641565
});
1566+
// Attach the x-goog-spanner-request-id to the currently active span.
1567+
attributeXGoogSpannerRequestIdToActiveSpan(config);
15651568
const requestFn = gaxClient[config.method].bind(
15661569
gaxClient,
15671570
reqOpts,

src/request_id_header.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import {randomBytes} from 'crypto';
1818
// eslint-disable-next-line n/no-extraneous-import
1919
import * as grpc from '@grpc/grpc-js';
20+
import {getActiveOrNoopSpan} from './instrument';
2021
const randIdForProcess = randomBytes(8)
2122
.readUint32LE(0)
2223
.toString(16)
@@ -187,12 +188,31 @@ export interface RequestIDError extends grpc.ServiceError {
187188
requestID: string;
188189
}
189190

191+
const X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR = 'x_goog_spanner_request_id';
192+
193+
/*
194+
* attributeXGoogSpannerRequestIdToActiveSpan extracts the x-goog-spanner-request-id
195+
* from config, if possible and then adds it as an attribute to the current/active span.
196+
* Since x-goog-spanner-request-id is associated with RPC invoking methods, it is invoked
197+
* long after tracing has been performed.
198+
*/
199+
function attributeXGoogSpannerRequestIdToActiveSpan(config: any) {
200+
const reqId = extractRequestID(config);
201+
if (!(reqId && reqId.length > 0)) {
202+
return;
203+
}
204+
const span = getActiveOrNoopSpan();
205+
span.setAttribute(X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR, reqId);
206+
}
207+
190208
const X_GOOG_REQ_ID_REGEX = /^1\.[0-9A-Fa-f]{8}(\.\d+){3}\.\d+/;
191209

192210
export {
193211
AtomicCounter,
194212
X_GOOG_REQ_ID_REGEX,
195213
X_GOOG_SPANNER_REQUEST_ID_HEADER,
214+
X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR,
215+
attributeXGoogSpannerRequestIdToActiveSpan,
196216
craftRequestId,
197217
injectRequestIDIntoError,
198218
injectRequestIDIntoHeaders,

test/spanner.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
RequestIDError,
6262
X_GOOG_REQ_ID_REGEX,
6363
X_GOOG_SPANNER_REQUEST_ID_HEADER,
64+
X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR,
6465
randIdForProcess,
6566
resetNthClientId,
6667
} from '../src/request_id_header';
@@ -5780,6 +5781,22 @@ describe('Spanner with mock server', () => {
57805781
});
57815782

57825783
describe('XGoogRequestId', () => {
5784+
const exporter = new InMemorySpanExporter();
5785+
const provider = new NodeTracerProvider({
5786+
sampler: new AlwaysOnSampler(),
5787+
exporter: exporter,
5788+
});
5789+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
5790+
provider.register();
5791+
5792+
beforeEach(async () => {
5793+
await exporter.forceFlush();
5794+
});
5795+
5796+
after(async () => {
5797+
await provider.shutdown();
5798+
});
5799+
57835800
it('with retry on aborted query', async () => {
57845801
let attempts = 0;
57855802
const database = newTestDatabase();
@@ -5850,6 +5867,34 @@ describe('Spanner with mock server', () => {
58505867
await database.close();
58515868
});
58525869

5870+
it('check span attributes for x-goog-spanner-request-id', async () => {
5871+
const database = newTestDatabase();
5872+
await database.runTransactionAsync(async transaction => {
5873+
await transaction!.run(selectSql);
5874+
await transaction!.commit();
5875+
});
5876+
5877+
await exporter.forceFlush();
5878+
const spans = exporter.getFinishedSpans();
5879+
5880+
// The RPC invoking spans that we expect to have our value.
5881+
const rpcMakingSpans = [
5882+
'CloudSpanner.Database.batchCreateSessions',
5883+
'CloudSpanner.Snapshot.run',
5884+
'CloudSpanner.Transaction.commit',
5885+
];
5886+
5887+
spans.forEach(span => {
5888+
if (rpcMakingSpans.includes(span.name)) {
5889+
assert.strictEqual(
5890+
X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR in span.attributes,
5891+
true,
5892+
`Missing ${X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR} for ${span.name}`
5893+
);
5894+
}
5895+
});
5896+
});
5897+
58535898
// TODO(@odeke-em): introduce tests for incremented attempts to verify
58545899
// that retries from GAX produce the required results.
58555900
});

0 commit comments

Comments
 (0)