@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
JavaScript
"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