blueprints/apigee/apigee-x-foundations/functions/instance-monitor/index.js (110 lines of code) (raw):

/** * Copyright 2024 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. */ const functions = require("@google-cloud/functions-framework"); const monitoring = require("@google-cloud/monitoring"); const logging = require("@google-cloud/logging"); const { LoggingBunyan } = require("@google-cloud/logging-bunyan"); const bunyan = require("bunyan"); const loggingBunyan = new LoggingBunyan(); const logger = bunyan.createLogger({ name: "instance-monitor", streams: [{ stream: process.stdout, level: "info" }, loggingBunyan.stream("info")], }); const SEVERITY_THRESHOLD = logging.Severity.warning; const METRIC_DESCRIPTION = "Apigee instance health."; const METRIC_DISPLAY_NAME = "Apigee instance health."; const METRIC_TYPE = "custom.googleapis.com/apigee/instance_health"; const METRIC_KIND = "GAUGE"; const METRIC_VALUE_TYPE = "BOOL"; const METRIC_UNIT = "1"; const METRIC_LABELS = [ { key: "org", valueType: "STRING", description: "The name of the apigee organization.", }, { key: "instance_id", valueType: "STRING", description: "The ID of the apigee instance.", } ]; const RESOURCE_TYPE = "global"; const METRIC_DESCRIPTOR = { description: METRIC_DESCRIPTION, displayName: METRIC_DISPLAY_NAME, type: METRIC_TYPE, metricKind: METRIC_KIND, valueType: METRIC_VALUE_TYPE, unit: METRIC_UNIT, labels: METRIC_LABELS, }; const client = new monitoring.MetricServiceClient(); async function createMetricDescriptor(projectId, metricDescriptor) { const request = { name: client.projectPath(projectId), metricDescriptor: metricDescriptor, }; return await client.createMetricDescriptor(request); } async function getMetricDescriptor(projectId, metricType) { const request = { name: client.projectMetricDescriptorPath(projectId, metricType), }; return await client.getMetricDescriptor(request); } async function writeTimeSeriesData(projectId, metricType, resourceType, value, metricLabels) { const dataPoint = { interval: { endTime: { seconds: Date.now() / 1000, }, }, value: { boolValue: value, }, }; const timeSeriesData = { metric: { type: metricType, labels: metricLabels, }, resource: { type: resourceType, labels: { project_id: projectId, }, }, points: [dataPoint], }; const request = { name: client.projectPath(projectId), timeSeries: [timeSeriesData], }; return await client.createTimeSeries(request); } async function processEvent(cloudEvent) { const [, projectId, instanceId] = /^organizations\/(.+)\/instances\/(.+)$/g.exec(cloudEvent.resourcename); const severity = logging.Severity[cloudEvent.data.severity.toLowerCase()]; const value = severity >= SEVERITY_THRESHOLD; if (!value) { logger.error(`Instance ${instanceId} in ${organization} is down`); } try { logger.debug("Checking if metric exists..."); const result = await getMetricDescriptor(projectId, METRIC_TYPE); logger.debug("Metric already exists", result); } catch (error) { logger.debug("Metric does not exist. Creating it..."); const result = await createMetricDescriptor(projectId, METRIC_DESCRIPTOR); logger.debug("Metric created", result); } logger.debug("Writing data point..."); await writeTimeSeriesData(projectId, METRIC_TYPE, RESOURCE_TYPE, value, { org: projectId, instance_id: instanceId, }); } functions.cloudEvent("writeMetric", async cloudEvent => { logger.debug("Notification received. Let's process it..."); processEvent(cloudEvent); logger.debug("Notification processed."); });