@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
218 lines • 8.03 kB
JavaScript
/*!
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SPAN_NAMESPACE_PREFIX = exports.TRACER_VERSION = exports.TRACER_NAME = void 0;
exports.getTracer = getTracer;
exports.ensureInitialContextManagerSet = ensureInitialContextManagerSet;
exports.startTrace = startTrace;
exports.setSpanError = setSpanError;
exports.setSpanErrorAndException = setSpanErrorAndException;
exports.getActiveOrNoopSpan = getActiveOrNoopSpan;
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const api_1 = require("@opentelemetry/api");
const optedInPII = process.env.SPANNER_ENABLE_EXTENDED_TRACING === 'true';
const TRACER_NAME = 'cloud.google.com/nodejs/spanner';
exports.TRACER_NAME = TRACER_NAME;
const TRACER_VERSION = require('../../package.json').version;
exports.TRACER_VERSION = TRACER_VERSION;
/**
* getTracer fetches the tracer from the provided tracerProvider.
* @param {TracerProvider} [tracerProvider] optional custom tracer provider
* to use for fetching the tracer. If not provided, the global provider will be used.
*
* @returns {Tracer} The tracer instance associated with the provided or global provider.
*/
function getTracer(tracerProvider) {
if (tracerProvider) {
return tracerProvider.getTracer(TRACER_NAME, TRACER_VERSION);
}
// Otherwise use the global tracer.
return api_1.trace.getTracer(TRACER_NAME, TRACER_VERSION);
}
const SPAN_NAMESPACE_PREFIX = 'CloudSpanner'; // TODO: discuss & standardize this prefix.
exports.SPAN_NAMESPACE_PREFIX = SPAN_NAMESPACE_PREFIX;
const { AsyncHooksContextManager, } = require('@opentelemetry/context-async-hooks');
/*
* This function ensures that async/await works correctly by
* checking if context.active() returns an invalid/unset context
* and if so, sets a global AsyncHooksContextManager otherwise
* spans resulting from async/await invocations won't be correctly
* associated in their respective hierarchies.
*/
function ensureInitialContextManagerSet() {
if (api_1.context.active() === api_1.ROOT_CONTEXT) {
// If no active context was set previously, trace context propagation cannot
// function correctly with async/await for OpenTelemetry
// See {@link https://opentelemetry.io/docs/languages/js/context/#active-context}
api_1.context.disable(); // Disable any prior contextManager.
const contextManager = new AsyncHooksContextManager();
contextManager.enable();
api_1.context.setGlobalContextManager(contextManager);
}
}
/**
* startTrace begins an active span in the current active context
* and passes it back to the set callback function. Each span will
* be prefixed with "cloud.google.com/nodejs/spanner". It is the
* responsibility of the caller to invoke [span.end] when finished tracing.
*
* @returns {Span} The created span.
*/
function startTrace(spanNameSuffix, config, cb) {
if (!config) {
config = {};
}
return getTracer(config.opts?.tracerProvider).startActiveSpan(SPAN_NAMESPACE_PREFIX + '.' + spanNameSuffix, { kind: api_1.SpanKind.CLIENT }, span => {
span.setAttribute(semantic_conventions_1.ATTR_OTEL_SCOPE_NAME, TRACER_NAME);
span.setAttribute(semantic_conventions_1.ATTR_OTEL_SCOPE_VERSION, TRACER_VERSION);
span.setAttribute('gcp.client.service', 'spanner');
span.setAttribute('gcp.client.version', TRACER_VERSION);
span.setAttribute('gcp.client.repo', 'googleapis/nodejs-spanner');
if (config.tableName) {
span.setAttribute('db.sql.table', config.tableName);
}
if (config.dbName) {
span.setAttribute('db.name', config.dbName);
}
if (config.requestTag) {
span.setAttribute('request.tag', config.requestTag);
}
if (config.transactionTag) {
span.setAttribute('transaction.tag', config.transactionTag);
}
const allowExtendedTracing = optedInPII || config.opts?.enableExtendedTracing;
if (config.sql && allowExtendedTracing) {
const sql = config.sql;
if (typeof sql === 'string') {
span.setAttribute('db.statement', sql);
}
else {
const stmt = sql;
span.setAttribute('db.statement', stmt.sql);
}
}
// If at all the invoked function throws an exception,
// record the exception and then end this span.
try {
return cb(span);
}
catch (e) {
setSpanErrorAndException(span, e);
span.end();
// Finally re-throw the exception.
throw e;
}
});
}
/**
* Sets the span status with err, if non-null onto the span with
* status.code=ERROR and the message of err.toString()
*
* @returns {boolean} to signify if the status was set.
*/
function setSpanError(span, err) {
if (!err || !span) {
return false;
}
let message = '';
if (typeof err === 'object' && 'message' in err) {
message = err.message;
}
else {
message = err.toString();
}
span.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: message,
});
return true;
}
/**
* Sets err, if non-null onto the span with
* status.code=ERROR and the message of err.toString()
* as well as recording an exception on the span.
* @param {Span} [span] the subject span
* @param {Error} [err] the error whose message to use to record
* the span error and the span exception.
*
* @returns {boolean} to signify if the status and exception were set.
*/
function setSpanErrorAndException(span, err) {
if (setSpanError(span, err)) {
span.recordException(err);
return true;
}
return false;
}
/**
* getActiveOrNoopSpan queries the global tracer for the currently active
* span and returns it, otherwise if there is no active span available, it'll
* simply create a NoopSpan. This is important in the cases where we don't
* want to create a new span, such as in sensitive and frequently called code
* for which the new spans would be too many and thus pollute the trace,
* but yet we'd like to record an important annotation.
*
* @returns {Span} the non-null span.
*/
function getActiveOrNoopSpan() {
const span = api_1.trace.getActiveSpan();
if (span) {
return span;
}
return new noopSpan();
}
/**
* noopSpan is a pass-through Span that does nothing and shall not
* be exported, nor added into any context. It serves as a placeholder
* to allow calls in sensitive areas like sessionPools to transparently
* add attributes to spans without lots of ugly null checks.
*
* It exists because OpenTelemetry-JS does not seem to export the NoopSpan.
*/
class noopSpan {
constructor() { }
spanContext() {
return api_1.INVALID_SPAN_CONTEXT;
}
setAttribute(key, value) {
return this;
}
setAttributes(attributes) {
return this;
}
addEvent(name, attributes) {
return this;
}
addLink(link) {
return this;
}
addLinks(links) {
return this;
}
setStatus(status) {
return this;
}
end(endTime) { }
isRecording() {
return false;
}
recordException(exc, timeAt) { }
updateName(name) {
return this;
}
}
//# sourceMappingURL=instrument.js.map
;