@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
159 lines • 5.64 kB
JavaScript
/**
* Copyright 2025 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.randIdForProcess = exports.X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR = exports.X_GOOG_SPANNER_REQUEST_ID_HEADER = exports.X_GOOG_REQ_ID_REGEX = exports.AtomicCounter = void 0;
exports.resetNthClientId = resetNthClientId;
exports.attributeXGoogSpannerRequestIdToActiveSpan = attributeXGoogSpannerRequestIdToActiveSpan;
exports.craftRequestId = craftRequestId;
exports.injectRequestIDIntoError = injectRequestIDIntoError;
exports.injectRequestIDIntoHeaders = injectRequestIDIntoHeaders;
exports.nextNthRequest = nextNthRequest;
exports.nextSpannerClientId = nextSpannerClientId;
exports.newAtomicCounter = newAtomicCounter;
const crypto_1 = require("crypto");
const instrument_1 = require("./instrument");
const randIdForProcess = (0, crypto_1.randomBytes)(8)
.readUint32LE(0)
.toString(16)
.padStart(8, '0');
exports.randIdForProcess = randIdForProcess;
const X_GOOG_SPANNER_REQUEST_ID_HEADER = 'x-goog-spanner-request-id';
exports.X_GOOG_SPANNER_REQUEST_ID_HEADER = X_GOOG_SPANNER_REQUEST_ID_HEADER;
class AtomicCounter {
backingBuffer;
constructor(initialValue) {
this.backingBuffer = new Uint32Array(new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT));
if (initialValue) {
this.increment(initialValue);
}
}
increment(n) {
if (!n) {
n = 1;
}
Atomics.add(this.backingBuffer, 0, n);
return this.value();
}
value() {
return Atomics.load(this.backingBuffer, 0);
}
toString() {
return `${this.value()}`;
}
reset() {
Atomics.store(this.backingBuffer, 0, 0);
}
}
exports.AtomicCounter = AtomicCounter;
const REQUEST_HEADER_VERSION = 1;
function craftRequestId(nthClientId, channelId, nthRequest, attempt) {
return `${REQUEST_HEADER_VERSION}.${randIdForProcess}.${nthClientId}.${channelId}.${nthRequest}.${attempt}`;
}
const nthClientId = new AtomicCounter();
// Only exported for deterministic testing.
function resetNthClientId() {
nthClientId.reset();
}
/*
* nextSpannerClientId increments the internal
* counter for created SpannerClients, for use
* with x-goog-spanner-request-id.
*/
function nextSpannerClientId() {
nthClientId.increment(1);
return nthClientId.value();
}
function newAtomicCounter(n) {
return new AtomicCounter(n);
}
function extractRequestID(config) {
if (!config) {
return '';
}
const hdrs = config;
if (hdrs && hdrs.headers) {
return hdrs.headers[X_GOOG_SPANNER_REQUEST_ID_HEADER];
}
return '';
}
function injectRequestIDIntoError(config, err) {
if (!err) {
return;
}
// Inject that RequestID into the actual
// error object regardless of the type.
const requestID = extractRequestID(config);
if (requestID) {
Object.assign(err, { requestID: requestID });
}
}
function injectRequestIDIntoHeaders(headers, session, nthRequest, attempt) {
if (!session) {
return headers;
}
if (!nthRequest) {
const database = session.parent;
if (!(database && typeof database._nextNthRequest === 'function')) {
return headers;
}
nthRequest = database._nextNthRequest();
}
attempt = attempt || 1;
return _metadataWithRequestId(session, nthRequest, attempt, headers);
}
function _metadataWithRequestId(session, nthRequest, attempt, priorMetadata) {
if (!priorMetadata) {
priorMetadata = {};
}
const withReqId = {
...priorMetadata,
};
const database = session.parent;
let clientId = 1;
let channelId = 1;
if (database) {
clientId = database._nthClientId || 1;
channelId = database._channelId || 1;
}
withReqId[X_GOOG_SPANNER_REQUEST_ID_HEADER] = craftRequestId(clientId, channelId, nthRequest, attempt);
return withReqId;
}
function nextNthRequest(database) {
if (!(database && typeof database._nextNthRequest === 'function')) {
return 1;
}
return database._nextNthRequest();
}
const X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR = 'x_goog_spanner_request_id';
exports.X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR = X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR;
/*
* attributeXGoogSpannerRequestIdToActiveSpan extracts the x-goog-spanner-request-id
* from config, if possible and then adds it as an attribute to the current/active span.
* Since x-goog-spanner-request-id is associated with RPC invoking methods, it is invoked
* long after tracing has been performed.
*/
function attributeXGoogSpannerRequestIdToActiveSpan(config) {
const reqId = extractRequestID(config);
if (!(reqId && reqId.length > 0)) {
return;
}
const span = (0, instrument_1.getActiveOrNoopSpan)();
span.setAttribute(X_GOOG_SPANNER_REQUEST_ID_SPAN_ATTR, reqId);
}
const X_GOOG_REQ_ID_REGEX = /^1\.[0-9A-Fa-f]{8}(\.\d+){3}\.\d+/;
exports.X_GOOG_REQ_ID_REGEX = X_GOOG_REQ_ID_REGEX;
//# sourceMappingURL=request_id_header.js.map
;