@google-cloud/opentelemetry-cloud-monitoring-exporter
Version:
OpenTelemetry Google Cloud Monitoring Exporter allows the user to send collected metrics to Google Cloud Monitoring.
231 lines • 9.36 kB
JavaScript
;
// 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");
// Import directly from this module instead of googleapis to improve bundler tree shaking
const monitoring_1 = require("googleapis/build/src/apis/monitoring");
const transform_1 = require("./transform");
const utils_1 = require("./utils");
const api_1 = require("@opentelemetry/api");
const opentelemetry_resource_util_1 = require("@google-cloud/opentelemetry-resource-util");
const version_1 = require("./version");
// Stackdriver Monitoring v3 only accepts up to 200 TimeSeries per
// CreateTimeSeries call.
const MAX_BATCH_EXPORT_SIZE = 200;
const OT_USER_AGENTS = [
{
product: 'opentelemetry-js',
version: core_1.VERSION,
},
{
product: 'google-cloud-metric-exporter',
version: version_1.VERSION,
},
];
const OT_REQUEST_HEADER = {
'x-opentelemetry-outgoing-request': 0x1,
};
/**
* Format and sends metrics information to Google Cloud Monitoring.
*/
class MetricExporter {
constructor(options = {}) {
var _a;
/**
* Set of OTel metric names that have already had their metric descriptors successfully
* created
*/
this.createdMetricDescriptors = new Set();
this._metricPrefix = (_a = options.prefix) !== null && _a !== void 0 ? _a : MetricExporter.DEFAULT_METRIC_PREFIX;
this._disableCreateMetricDescriptors =
!!options.disableCreateMetricDescriptors;
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'],
});
this._monitoring = (0, monitoring_1.monitoring)({
version: 'v3',
rootUrl: 'https://' + (options.apiEndpoint || 'monitoring.googleapis.com:443'),
headers: OT_REQUEST_HEADER,
userAgentDirectives: OT_USER_AGENTS,
});
// 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 => {
api_1.diag.error(err);
});
}
/**
* Implementation for {@link PushMetricExporter.export}.
* Calls the async wrapper method {@link _exportAsync} and
* assures no rejected promises bubble up to the caller.
*
* @param metrics Metrics to be sent to the Google Cloud Monitoring backend
* @param resultCallback result callback to be called on finish
*/
export(metrics, resultCallback) {
this._exportAsync(metrics).then(resultCallback, err => {
api_1.diag.error(err.message);
resultCallback({ code: core_1.ExportResultCode.FAILED, error: err });
});
}
async shutdown() { }
async forceFlush() { }
/**
* Asnyc wrapper for the {@link export} implementation.
* Writes the current values of all exported {@link MetricRecord}s
* to the Google Cloud Monitoring backend.
*
* @param resourceMetrics Metrics to be sent to the Google Cloud Monitoring backend
*/
async _exportAsync(resourceMetrics) {
if (this._projectId instanceof Promise) {
this._projectId = await this._projectId;
}
if (!this._projectId) {
const error = new Error('expecting a non-blank ProjectID');
api_1.diag.error(error.message);
return { code: core_1.ExportResultCode.FAILED, error };
}
api_1.diag.debug('Google Cloud Monitoring export');
const resource = (0, opentelemetry_resource_util_1.mapOtelResourceToMonitoredResource)(resourceMetrics.resource);
const timeSeries = [];
for (const scopeMetric of resourceMetrics.scopeMetrics) {
for (const metric of scopeMetric.metrics) {
const isRegistered = this._disableCreateMetricDescriptors ||
(await this._registerMetricDescriptor(metric));
if (isRegistered) {
timeSeries.push(...(0, transform_1.createTimeSeries)(metric, resource, this._metricPrefix));
}
}
}
let failure = {
sendFailed: false,
};
for (const batchedTimeSeries of (0, utils_1.partitionList)(timeSeries, MAX_BATCH_EXPORT_SIZE)) {
try {
await this._sendTimeSeries(batchedTimeSeries);
}
catch (e) {
const err = asError(e);
err.message = `Send TimeSeries failed: ${err.message}`;
failure = { sendFailed: true, error: err };
api_1.diag.error(err.message);
}
}
if (failure.sendFailed) {
return { code: core_1.ExportResultCode.FAILED, error: failure.error };
}
return { code: core_1.ExportResultCode.SUCCESS };
}
/**
* 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 and should be skipped.
*
* @param metric The OpenTelemetry MetricData.
*/
async _registerMetricDescriptor(metric) {
const isDescriptorCreated = this.createdMetricDescriptors.has(metric.descriptor.name);
if (isDescriptorCreated) {
return true;
}
const res = await this._createMetricDescriptorIfNeeded(metric);
if (res) {
this.createdMetricDescriptors.add(metric.descriptor.name);
return true;
}
return false;
}
/**
* Returns true if a descriptor already exists within the requested GCP project id;
* @param descriptor The metric descriptor to check
* @param projectIdPath The GCP project id path
* @param authClient The authenticated client which will be used to make the request
* @returns {boolean}
*/
async _checkIfDescriptorExists(descriptor, projectIdPath, authClient) {
try {
await this._monitoring.projects.metricDescriptors.get({
name: `${projectIdPath}/metricDescriptors/${descriptor.type}`,
auth: authClient,
});
return true;
}
catch (error) {
return false;
}
}
/**
* Calls CreateMetricDescriptor in the GCM API for the given InstrumentDescriptor if needed
* @param metric The OpenTelemetry MetricData.
* @returns whether or not the descriptor was successfully created
*/
async _createMetricDescriptorIfNeeded(metric) {
const authClient = await this._authorize();
const descriptor = (0, transform_1.transformMetricDescriptor)(metric, this._metricPrefix);
const projectIdPath = (0, utils_1.mountProjectIdPath)(this._projectId);
try {
const descriptorExists = await this._checkIfDescriptorExists(descriptor, projectIdPath, authClient);
if (!descriptorExists) {
await this._monitoring.projects.metricDescriptors.create({
name: projectIdPath,
requestBody: descriptor,
auth: authClient,
});
api_1.diag.debug('sent metric descriptor', descriptor);
}
return true;
}
catch (e) {
const err = asError(e);
api_1.diag.error('Failed to create metric descriptor: %s', err.message);
return false;
}
}
async _sendTimeSeries(timeSeries) {
if (timeSeries.length === 0) {
return Promise.resolve();
}
const authClient = await this._authorize();
await this._monitoring.projects.timeSeries.create({
name: (0, utils_1.mountProjectIdPath)(this._projectId),
requestBody: { timeSeries },
auth: authClient,
});
api_1.diag.debug('sent time series', timeSeries);
}
/**
* 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_METRIC_PREFIX = 'workload.googleapis.com';
function asError(error) {
if (error instanceof Error) {
return error;
}
return new Error(String(error));
}
//# sourceMappingURL=monitoring.js.map