UNPKG

@google-cloud/opentelemetry-cloud-monitoring-exporter

Version:

OpenTelemetry Google Cloud Monitoring Exporter allows the user to send collected metrics to Google Cloud Monitoring.

183 lines 7.68 kB
"use strict"; // Copyright 2020 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.MetricExporter = void 0; const core_1 = require("@opentelemetry/core"); const google_auth_library_1 = require("google-auth-library"); const googleapis_1 = require("googleapis"); const transform_1 = require("./transform"); const utils_1 = require("./utils"); // Stackdriver Monitoring v3 only accepts up to 200 TimeSeries per // CreateTimeSeries call. const MAX_BATCH_EXPORT_SIZE = 200; const OT_USER_AGENT = { product: 'opentelemetry-js', version: core_1.VERSION, }; const OT_REQUEST_HEADER = { 'x-opentelemetry-outgoing-request': 0x1, }; googleapis_1.google.options({ headers: OT_REQUEST_HEADER }); /** * Format and sends metrics information to Google Cloud Monitoring. */ class MetricExporter { constructor(options = {}) { this._startTime = new Date().toISOString(); this.registeredMetricDescriptors = new Map(); this._logger = options.logger || new core_1.NoopLogger(); this._metricPrefix = options.prefix || MetricExporter.CUSTOM_OPENTELEMETRY_DOMAIN; this._displayNamePrefix = options.prefix || MetricExporter.DEFAULT_DISPLAY_NAME_PREFIX; this._auth = new google_auth_library_1.GoogleAuth({ credentials: options.credentials, keyFile: options.keyFile, keyFilename: options.keyFilename, projectId: options.projectId, scopes: ['https://www.googleapis.com/auth/cloud-platform'], }); // Start this async process as early as possible. It will be // awaited on the first export because constructors are synchronous this._projectId = this._auth.getProjectId().catch(err => { this._logger.error(err); }); } /** * Saves the current values of all exported {@link MetricRecord}s so that * they can be pulled by the Google Cloud Monitoring backend. * * @param metrics Metrics to be sent to the Google Cloud Monitoring backend * @param cb result callback to be called on finish */ async export(metrics, cb) { if (this._projectId instanceof Promise) { this._projectId = await this._projectId; } if (!this._projectId) { const error = new Error('expecting a non-blank ProjectID'); this._logger.error(error.message); return cb({ code: core_1.ExportResultCode.FAILED, error }); } this._logger.debug('Google Cloud Monitoring export'); const timeSeries = []; for (const metric of metrics) { const isRegistered = await this._registerMetricDescriptor(metric.descriptor); if (isRegistered) { timeSeries.push(transform_1.createTimeSeries(metric, this._metricPrefix, this._startTime, this._projectId)); } } let failure = { sendFailed: false, }; for (const batchedTimeSeries of utils_1.partitionList(timeSeries, MAX_BATCH_EXPORT_SIZE)) { try { await this._sendTimeSeries(batchedTimeSeries); } catch (err) { err.message = `Send TimeSeries failed: ${err.message}`; failure = { sendFailed: true, error: err }; this._logger.error(err.message); } } if (failure.sendFailed) { return cb({ code: core_1.ExportResultCode.FAILED, error: failure.error }); } cb({ code: core_1.ExportResultCode.SUCCESS }); } async shutdown() { } /** * Returns true if the given metricDescriptor is successfully registered to * Google Cloud Monitoring, or the exact same metric has already been * registered. Returns false otherwise. * @param metricDescriptor The OpenTelemetry MetricDescriptor. */ async _registerMetricDescriptor(metricDescriptor) { const existingMetricDescriptor = this.registeredMetricDescriptors.get(metricDescriptor.name); if (existingMetricDescriptor) { if (existingMetricDescriptor === metricDescriptor) { // Ignore metricDescriptor that are already registered. return true; } else { this._logger.warn(`A different metric with the same name is already registered: ${existingMetricDescriptor}`); return false; } } const isRegistered = await this._createMetricDescriptor(metricDescriptor) .then(() => { this.registeredMetricDescriptors.set(metricDescriptor.name, metricDescriptor); return true; }) .catch(err => { this._logger.error(err); return false; }); return isRegistered; } /** * Creates a new metric descriptor. * @param metricDescriptor The OpenTelemetry MetricDescriptor. */ async _createMetricDescriptor(metricDescriptor) { const authClient = await this._authorize(); const request = { name: `projects/${this._projectId}`, resource: transform_1.transformMetricDescriptor(metricDescriptor, this._metricPrefix, this._displayNamePrefix), auth: authClient, }; try { return new Promise((resolve, reject) => { MetricExporter._monitoring.projects.metricDescriptors.create(request, { headers: OT_REQUEST_HEADER, userAgentDirectives: [OT_USER_AGENT] }, (err) => { this._logger.debug('sent metric descriptor', request.resource); err ? reject(err) : resolve(); }); }); } catch (err) { this._logger.error(`MetricExporter: Failed to write data: ${err.message}`); } } async _sendTimeSeries(timeSeries) { if (timeSeries.length === 0) { return Promise.resolve(); } return this._authorize().then(authClient => { const request = { name: `projects/${this._projectId}`, resource: { timeSeries }, auth: authClient, }; return new Promise((resolve, reject) => { MetricExporter._monitoring.projects.timeSeries.create(request, { headers: OT_REQUEST_HEADER, userAgentDirectives: [OT_USER_AGENT] }, (err) => { this._logger.debug('sent time series', request.resource.timeSeries); err ? reject(err) : resolve(); }); }); }); } /** * Gets the Google Application Credentials from the environment variables * and authenticates the client. */ async _authorize() { return (await this._auth.getClient()); } } exports.MetricExporter = MetricExporter; MetricExporter.DEFAULT_DISPLAY_NAME_PREFIX = 'OpenTelemetry'; MetricExporter.CUSTOM_OPENTELEMETRY_DOMAIN = 'custom.googleapis.com/opentelemetry'; MetricExporter._monitoring = googleapis_1.google.monitoring('v3'); //# sourceMappingURL=monitoring.js.map