@google-cloud/logging
Version:
Cloud Logging Client Library for Node.js
215 lines • 8.15 kB
JavaScript
;
/*!
* Copyright 2021 Google LLC
*
* 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.W3C_TRACE_PARENT_HEADER = exports.X_CLOUD_TRACE_HEADER = void 0;
exports.makeHeaderWrapper = makeHeaderWrapper;
exports.getOrInjectContext = getOrInjectContext;
exports.getContextFromOtelContext = getContextFromOtelContext;
exports.getContextFromXCloudTrace = getContextFromXCloudTrace;
exports.getContextFromTraceParent = getContextFromTraceParent;
exports.parseXCloudTraceHeader = parseXCloudTraceHeader;
exports.parseTraceParentHeader = parseTraceParentHeader;
const crypto = require("crypto");
const api_1 = require("@opentelemetry/api");
/** Header that carries span context across Google infrastructure. */
exports.X_CLOUD_TRACE_HEADER = 'x-cloud-trace-context';
const SPAN_ID_RANDOM_BYTES = 8;
const spanIdBuffer = Buffer.alloc(SPAN_ID_RANDOM_BYTES);
const randomFillSync = crypto.randomFillSync;
const randomBytes = crypto.randomBytes;
const spanRandomBuffer = randomFillSync
? () => randomFillSync(spanIdBuffer)
: () => randomBytes(SPAN_ID_RANDOM_BYTES);
/** Header that carries span context across W3C compliant infrastructure. */
exports.W3C_TRACE_PARENT_HEADER = 'traceparent';
/**
* makeHeaderWrapper returns a wrapper with set and get header functionality,
* returning null if the incoming request object doesn't contain the 'header'
* propery.
* @param req
*/
function makeHeaderWrapper(req) {
if (!req.headers)
return null;
const wrapper = {
setHeader(name, value) {
req.headers[name] = value;
},
getHeader(name) {
return req.headers[name];
},
};
return wrapper;
}
/**
* getOrInjectContext returns a CloudTraceContext with as many available trace
* and span properties as possible. It examines HTTP headers for trace context.
* Optionally, it can inject a Google compliant trace context when no context is
* available from headers.
*
* @param req
* @param projectId
* @param inject
*/
function getOrInjectContext(req, projectId, inject) {
const defaultContext = toCloudTraceContext({}, projectId);
// Get trace context from OpenTelemetry span context.
const otelContext = getContextFromOtelContext(projectId);
if (otelContext)
return otelContext;
const wrapper = makeHeaderWrapper(req);
if (wrapper) {
// Detect 'traceparent' header.
const traceContext = getContextFromTraceParent(wrapper, projectId);
if (traceContext)
return traceContext;
// Detect 'X-Cloud-Trace-Context' header.
const cloudContext = getContextFromXCloudTrace(wrapper, projectId);
if (cloudContext)
return cloudContext;
// Optional: Generate and inject a context for the user as last resort.
if (inject) {
wrapper.setHeader(exports.X_CLOUD_TRACE_HEADER, makeCloudTraceHeader());
return getContextFromXCloudTrace(wrapper, projectId);
}
}
return defaultContext;
}
/**
* toCloudTraceContext converts any context format to cloudTraceContext format.
* @param context
* @param projectId
*/
function toCloudTraceContext(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
anyContext, projectId) {
const context = {
trace: '',
};
if (anyContext === null || anyContext === void 0 ? void 0 : anyContext.trace) {
context.trace = `projects/${projectId}/traces/${anyContext.trace}`;
}
if (anyContext === null || anyContext === void 0 ? void 0 : anyContext.spanId) {
context.spanId = anyContext.spanId;
}
if ('traceSampled' in anyContext) {
context.traceSampled = anyContext.traceSampled;
}
return context;
}
/**
* makeCloudTraceHeader generates valid X-Cloud-Trace-Context trace and spanId.
*/
function makeCloudTraceHeader() {
const trace = crypto.randomUUID().replace(/-/g, '');
const spanId = spanRandomBuffer().toString('hex');
return `${trace}/${spanId}`;
}
/**
* getContextFromOtelContext looks for the active open telemetry span context
* per Open Telemetry specifications for tracing contexts.
*
* @param projectId
*/
function getContextFromOtelContext(projectId) {
var _a;
const spanContext = (_a = api_1.trace.getActiveSpan()) === null || _a === void 0 ? void 0 : _a.spanContext();
const FLAG_SAMPLED = 1; // 00000001
if (spanContext !== undefined && (0, api_1.isSpanContextValid)(spanContext)) {
const otelSpanContext = {
trace: spanContext === null || spanContext === void 0 ? void 0 : spanContext.traceId,
spanId: spanContext === null || spanContext === void 0 ? void 0 : spanContext.spanId,
traceSampled: (spanContext.traceFlags & FLAG_SAMPLED) !== 0,
};
return toCloudTraceContext(otelSpanContext, projectId);
}
return null;
}
/**
* getContextFromXCloudTrace looks for the HTTP header 'x-cloud-trace-context'
* per Google Cloud specifications for Cloud Tracing.
*
* @param headerWrapper
* @param projectId
*/
function getContextFromXCloudTrace(headerWrapper, projectId) {
const context = parseXCloudTraceHeader(headerWrapper);
if (!context)
return null;
return toCloudTraceContext(context, projectId);
}
/**
* getOrInjectTraceParent looks for the HTTP header 'traceparent'
* per W3C specifications for OpenTelemetry and OpenCensus
* Read more about W3C protocol: https://www.w3.org/TR/trace-context/
*
* @param headerWrapper
* @param projectId
*/
function getContextFromTraceParent(headerWrapper, projectId) {
const context = parseTraceParentHeader(headerWrapper);
if (!context)
return null;
return toCloudTraceContext(context, projectId);
}
/**
* parseXCloudTraceHeader looks for trace context in `X-Cloud-Trace-Context`
* header
* @param headerWrapper
*/
function parseXCloudTraceHeader(headerWrapper) {
var _a;
const regex = /([a-f\d]+)?(\/?([a-f\d]+))?(;?o=(\d))?/;
const match = (_a = headerWrapper
.getHeader(exports.X_CLOUD_TRACE_HEADER)) === null || _a === void 0 ? void 0 : _a.toString().match(regex);
if (!match)
return null;
return {
trace: match[1],
spanId: match[3],
traceSampled: match[5] === '1',
};
}
/**
* parseTraceParentHeader is a custom implementation of the `parseTraceParent`
* function in @opentelemetry-core/trace.
* For more information see {@link https://www.w3.org/TR/trace-context/}
* @param headerWrapper
*/
function parseTraceParentHeader(headerWrapper) {
var _a;
const VERSION_PART = '(?!ff)[\\da-f]{2}';
const TRACE_ID_PART = '(?![0]{32})[\\da-f]{32}';
const PARENT_ID_PART = '(?![0]{16})[\\da-f]{16}';
const FLAGS_PART = '[\\da-f]{2}';
const TRACE_PARENT_REGEX = new RegExp(`^\\s?(${VERSION_PART})-(${TRACE_ID_PART})-(${PARENT_ID_PART})-(${FLAGS_PART})(-.*)?\\s?$`);
const match = (_a = headerWrapper
.getHeader(exports.W3C_TRACE_PARENT_HEADER)) === null || _a === void 0 ? void 0 : _a.toString().match(TRACE_PARENT_REGEX);
if (!match)
return null;
// According to the specification the implementation should be compatible
// with future versions. If there are more parts, we only reject it if it's using version 00
// See https://www.w3.org/TR/trace-context/#versioning-of-traceparent
if (match[1] === '00' && match[5])
return null;
return {
trace: match[2],
spanId: match[3],
traceSampled: (parseInt(match[4], 16) & 1) === 1,
};
}
//# sourceMappingURL=context.js.map