UNPKG

@graphql-hive/gateway

Version:
205 lines (199 loc) 7.12 kB
'use strict'; var setup = require('@graphql-hive/plugin-opentelemetry/setup'); var api = require('@opentelemetry/api'); require('@opentelemetry/core'); /* * Copyright The OpenTelemetry Authors * * 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 * * https://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. */ const isPromiseLike = (val) => { return (val !== null && typeof val === 'object' && typeof val.then === 'function'); }; /* * Copyright The OpenTelemetry Authors * * 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 * * https://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. */ class ResourceImpl { _rawAttributes; _asyncAttributesPending = false; _schemaUrl; _memoizedAttributes; static FromAttributeList(attributes, options) { const res = new ResourceImpl({}, options); res._rawAttributes = guardedRawAttributes(attributes); res._asyncAttributesPending = attributes.filter(([_, val]) => isPromiseLike(val)).length > 0; return res; } constructor( /** * A dictionary of attributes with string keys and values that provide * information about the entity as numbers, strings or booleans * TODO: Consider to add check/validation on attributes. */ resource, options) { const attributes = resource.attributes ?? {}; this._rawAttributes = Object.entries(attributes).map(([k, v]) => { if (isPromiseLike(v)) { // side-effect this._asyncAttributesPending = true; } return [k, v]; }); this._rawAttributes = guardedRawAttributes(this._rawAttributes); this._schemaUrl = validateSchemaUrl(options?.schemaUrl); } get asyncAttributesPending() { return this._asyncAttributesPending; } async waitForAsyncAttributes() { if (!this.asyncAttributesPending) { return; } for (let i = 0; i < this._rawAttributes.length; i++) { const [k, v] = this._rawAttributes[i]; this._rawAttributes[i] = [k, isPromiseLike(v) ? await v : v]; } this._asyncAttributesPending = false; } get attributes() { if (this.asyncAttributesPending) { api.diag.error('Accessing resource attributes before async attributes settled'); } if (this._memoizedAttributes) { return this._memoizedAttributes; } const attrs = {}; for (const [k, v] of this._rawAttributes) { if (isPromiseLike(v)) { api.diag.debug(`Unsettled resource attribute ${k} skipped`); continue; } if (v != null) { attrs[k] ??= v; } } // only memoize output if all attributes are settled if (!this._asyncAttributesPending) { this._memoizedAttributes = attrs; } return attrs; } getRawAttributes() { return this._rawAttributes; } get schemaUrl() { return this._schemaUrl; } merge(resource) { if (resource == null) return this; // Order is important // Spec states incoming attributes override existing attributes const mergedSchemaUrl = mergeSchemaUrl(this, resource); const mergedOptions = mergedSchemaUrl ? { schemaUrl: mergedSchemaUrl } : undefined; return ResourceImpl.FromAttributeList([...resource.getRawAttributes(), ...this.getRawAttributes()], mergedOptions); } } function resourceFromAttributes(attributes, options) { return ResourceImpl.FromAttributeList(Object.entries(attributes), options); } function guardedRawAttributes(attributes) { return attributes.map(([k, v]) => { if (isPromiseLike(v)) { return [ k, v.catch(err => { api.diag.debug('promise rejection for resource attribute: %s - %s', k, err); return undefined; }), ]; } return [k, v]; }); } function validateSchemaUrl(schemaUrl) { if (typeof schemaUrl === 'string' || schemaUrl === undefined) { return schemaUrl; } api.diag.warn('Schema URL must be string or undefined, got %s. Schema URL will be ignored.', schemaUrl); return undefined; } function mergeSchemaUrl(old, updating) { const oldSchemaUrl = old?.schemaUrl; const updatingSchemaUrl = updating?.schemaUrl; const isOldEmpty = oldSchemaUrl === undefined || oldSchemaUrl === ''; const isUpdatingEmpty = updatingSchemaUrl === undefined || updatingSchemaUrl === ''; if (isOldEmpty) { return updatingSchemaUrl; } if (isUpdatingEmpty) { return oldSchemaUrl; } if (oldSchemaUrl === updatingSchemaUrl) { return oldSchemaUrl; } api.diag.warn('Schema URL merge conflict: old resource has "%s", updating resource has "%s". Resulting resource will have undefined Schema URL.', oldSchemaUrl, updatingSchemaUrl); return undefined; } const openTelemetrySetup = (options) => { return setup.openTelemetrySetup({ ...options, resource: createGatewayResource(options) }); }; const hiveTracingSetup = (options) => { return setup.hiveTracingSetup({ ...options, resource: createGatewayResource(options) }); }; function createGatewayResource(options) { if (!options.resource) { return { serviceName: "hive-gateway", serviceVersion: globalThis.__OTEL_PLUGIN_VERSION__ ?? "unknown" }; } else if (!("serviceName" in options.resource)) { return resourceFromAttributes({ [setup.ATTR_SERVICE_NAME]: "hive-gateway", [setup.ATTR_SERVICE_VERSION]: globalThis.__OTEL_PLUGIN_VERSION__ ?? "unknown" }).merge(options.resource); } else { return options.resource; } } exports.hiveTracingSetup = hiveTracingSetup; exports.openTelemetrySetup = openTelemetrySetup; Object.keys(setup).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return setup[k]; } }); });