/**
 * Copyright 2018, OpenCensus 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
 *
 *      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.
 */ import { logger , Logger , Measurement , Metric , MetricDescriptor as OCMetricDescriptor , MetricProducerManager , Metrics , StatsEventListener , TagKey , TagValue , version , View , } from '@opencensus/core' ; import { auth as globalAuth , GoogleAuth , JWT } from 'google-auth-library' ; import { google } from 'googleapis' ; import { getDefaultResource } from './common-utils' ; import { createMetricDescriptorData , createTimeSeriesList , } from './stackdriver-monitoring-utils' ; import { MonitoredResource , StackdriverExporterOptions , TimeSeries , } from './types' ; const OC_USER_AGENT = { product : 'opencensus-node' , version , } ; const OC_HEADER = { 'x-opencensus-outgoing-request' : 0x1 , } ; google . options ( { headers : OC_HEADER } ) ; const monitoring = google . monitoring ( 'v3' ) ; let auth = globalAuth ; /** Format and sends Stats to Stackdriver */ export class StackdriverStatsExporter implements StatsEventListener { private period : number ; private projectId : string ; private metricPrefix : string ; private displayNamePrefix : string ; private onMetricUploadError ? : ( err : Error ) => void ; private timer ! : NodeJS . Timer ; static readonly DEFAULT_DISPLAY_NAME_PREFIX : string = 'OpenCensus' ; static readonly CUSTOM_OPENCENSUS_DOMAIN : string = 'custom.googleapis.com/opencensus' ; static readonly PERIOD : number = 60000 ; private registeredMetricDescriptors : Map < string , OCMetricDescriptor > = new Map ( ) ; private DEFAULT_RESOURCE : Promise < MonitoredResource > ; logger : Logger ; constructor ( options ) { this . period = options . period !== undefined ? options . period : StackdriverStatsExporter . PERIOD ; this . projectId = options . projectId ; this . metricPrefix = options . prefix || StackdriverStatsExporter . CUSTOM_OPENCENSUS_DOMAIN ; this . displayNamePrefix = options . prefix || StackdriverStatsExporter . DEFAULT_DISPLAY_NAME_PREFIX ; this . logger = options . logger || logger . logger ( ) ; if ( options . onMetricUploadError ) { this . onMetricUploadError = options . onMetricUploadError ; } this . DEFAULT_RESOURCE = getDefaultResource ( this . projectId ) ; if ( options . credentials ) { auth = new GoogleAuth ( { credentials : options . credentials , scopes : [ 'https://www.googleapis.com/auth/cloud-platform' ] , } ) ; } } /**
     * Creates a Stackdriver Stats exporter with a StackdriverExporterOptions.
     */ start ( ) { this . timer = setInterval ( async ( ) => { try { await this . export ( ) ; } catch ( err ) { if ( typeof this . onMetricUploadError === 'function' ) { this . onMetricUploadError ( err ) ; } } } , this . period ) ; } /**
     * Polls MetricProducerManager from Metrics library for all registered
     * MetricDescriptors, and upload them as TimeSeries to StackDriver.
     */ async export ( ) { const metricsList = [ ] ; const metricProducerManager = Metrics . getMetricProducerManager ( ) ; for ( const metricProducer of metricProducerManager . getAllMetricProducer ( ) ) { for ( const metric of metricProducer . getMetrics ( ) ) { const isRegistered = await this . registerMetricDescriptor ( metric . descriptor ) ; if ( metric && isRegistered ) { metricsList . push ( metric ) ; } } } this . createTimeSeries ( metricsList ) ; } param metricDescriptor private async registerMetricDescriptor ( metricDescriptor ) { const existingMetricDescriptor = this . registeredMetricDescriptors . get ( metricDescriptor . name ) ; if ( existingMetricDescriptor ) { if ( existingMetricDescriptor === metricDescriptor ) { 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 ; } param metricsList private async createTimeSeries ( metricsList ) { const timeSeries = [ ] ; const monitoredResource = await this . DEFAULT_RESOURCE ; for ( const metric of metricsList ) { timeSeries . push ( ... createTimeSeriesList ( metric , monitoredResource , this . metricPrefix ) ) ; } 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 ) => { monitoring . projects . timeSeries . create ( request , { headers : OC_HEADER , userAgentDirectives : [ OC_USER_AGENT ] } , ( err ) => { this . logger . debug ( 'sent time series' , request . resource . timeSeries ) ; err ? reject ( err ) : resolve ( ) ; } ) ; } ) ; } ) ; } param metricDescriptor private createMetricDescriptor ( metricDescriptor ) { return this . authorize ( ) . then ( authClient => { const request = { name : `projects/${ this . projectId }` , resource : createMetricDescriptorData ( metricDescriptor , this . metricPrefix , this . displayNamePrefix ) , auth : authClient , } ; return new Promise ( ( resolve , reject ) => { monitoring . projects . metricDescriptors . create ( request , { headers : OC_HEADER , userAgentDirectives : [ OC_USER_AGENT ] } , ( err ) => { this . logger . debug ( 'sent metric descriptor' , request . resource ) ; err ? reject ( err ) : resolve ( ) ; } ) ; } ) . catch ( err => { this . logger . error ( `StackdriverStatsExporter: Failed to write data: ${ err . message }` ) ; this . stop ( ) ; } ) ; } ) ; } /**
     * Clear the interval timer to stop uploading metrics. It should be called
     * whenever the exporter is not needed anymore.
     */ stop ( ) { clearInterval ( this . timer ) ; } /**
     * Gets the Google Application Credentials from the environment variables
     * and authenticates the client.
     */ private async authorize ( ) { const client = await auth . getClient ( ) ; return client as JWT ; } param view onRegisterView ( view ) { } param views param measurement param tags onRecord ( views , measurement , tags ) { } }