diff --git a/src/index.ts b/src/index.ts index e4139f44..3a4e2871 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,10 +28,12 @@ const pumpify = require('pumpify'); import * as streamEvents from 'stream-events'; import * as through from 'through2'; import * as middleware from './middleware'; +import {detectServiceContext} from './metadata'; import {StackdriverHttpRequest as HttpRequest} from './http-request'; export {middleware}; export {HttpRequest}; +export {detectServiceContext}; const PKG = require('../../package.json'); const v2 = require('./v2'); diff --git a/src/metadata.ts b/src/metadata.ts index 69bf3f83..609db4f5 100644 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -19,6 +19,8 @@ import * as gcpMetadata from 'gcp-metadata'; import {GCPEnv, GoogleAuth} from 'google-auth-library'; import * as pify from 'pify'; +import {ServiceContext} from './index'; + const readFile = pify(fs.readFile); function zoneFromQualifiedZone(qualified: string): string|undefined { @@ -153,3 +155,34 @@ export async function getDefaultResource(auth: GoogleAuth) { return getGlobalDescriptor(); } } + +/** + * For logged errors, users can provide a service context. This enables errors + * to be picked up Stackdriver Error Reporting. For more information see + * [this guide]{@link + * https://cloud.google.com/error-reporting/docs/formatting-error-messages} and + * the [official documentation]{@link + * https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext}. + */ +export async function detectServiceContext(auth: GoogleAuth): + Promise { + const env = await auth.getEnv(); + switch (env) { + case GCPEnv.APP_ENGINE: + return { + service: process.env.GAE_SERVICE || process.env.GAE_MODULE_NAME, + version: process.env.GAE_VERSION || process.env.GAE_MODULE_VERSION, + }; + case GCPEnv.CLOUD_FUNCTIONS: + return { + service: process.env.FUNCTION_NAME, + }; + // One Kubernetes we probably need to use the pod-name to describe the + // service. Currently there isn't a universal way to acquire the pod + // name from within the pod. + case GCPEnv.KUBERNETES_ENGINE: + case GCPEnv.COMPUTE_ENGINE: + default: + return null; + } +} diff --git a/test/metadata.ts b/test/metadata.ts index 7bc6ec7a..2d157fd7 100644 --- a/test/metadata.ts +++ b/test/metadata.ts @@ -21,6 +21,7 @@ import {GCPEnv} from 'google-auth-library'; import * as proxyquire from 'proxyquire'; import assertRejects = require('assert-rejects'); +import {detectServiceContext} from '../src/metadata'; let instanceOverride; const fakeGcpMetadata = { @@ -360,4 +361,85 @@ describe('metadata', () => { }); }); }); + + describe('detectServiceContext', () => { + it('should return the correct descriptor for App Engine', async () => { + const GAE_MODULE_NAME = 'gae-module-name'; + const GAE_MODULE_VERSION = 'gae-module-version'; + const GAE_SERVICE = 'gae-service'; + const GAE_VERSION = 'gae-version'; + process.env.GAE_MODULE_NAME = GAE_MODULE_NAME; + process.env.GAE_MODULE_VERSION = GAE_MODULE_VERSION; + process.env.GAE_SERVICE = GAE_SERVICE; + process.env.GAE_VERSION = GAE_VERSION; + const fakeAuth = { + async getEnv() { + return GCPEnv.APP_ENGINE; + } + }; + + const sc1 = await metadata.detectServiceContext(fakeAuth); + assert.deepStrictEqual(sc1, { + service: GAE_SERVICE, + version: GAE_VERSION, + }); + + delete process.env.GAE_SERVICE; + const sc2 = await metadata.detectServiceContext(fakeAuth); + assert.deepStrictEqual(sc2, { + service: GAE_MODULE_NAME, + version: GAE_VERSION, + }); + + delete process.env.GAE_VERSION; + const sc3 = await metadata.detectServiceContext(fakeAuth); + assert.deepStrictEqual(sc3, { + service: GAE_MODULE_NAME, + version: GAE_MODULE_VERSION, + }); + }); + + it('should return the correct descriptor for Cloud Functions', async () => { + const FUNCTION_NAME = process.env.FUNCTION_NAME = 'function-name'; + + const fakeAuth = { + async getEnv() { + return GCPEnv.CLOUD_FUNCTIONS; + } + }; + + const sc1 = await metadata.detectServiceContext(fakeAuth); + assert.deepStrictEqual(sc1, {service: FUNCTION_NAME}); + }); + + it('should return null on GKE', async () => { + const fakeAuth = { + async getEnv() { + return GCPEnv.KUBERNETES_ENGINE; + } + }; + const serviceContext = await metadata.detectServiceContext(fakeAuth); + assert.strictEqual(serviceContext, null); + }); + + it('should return null on GCE', async () => { + const fakeAuth = { + async getEnv() { + return GCPEnv.COMPUTE_ENGINE; + } + }; + const serviceContext = await metadata.detectServiceContext(fakeAuth); + assert.strictEqual(serviceContext, null); + }); + + it('should return null elsewhere', async () => { + const fakeAuth = { + async getEnv() { + return GCPEnv.NONE; + } + }; + const serviceContext = await metadata.detectServiceContext(fakeAuth); + assert.strictEqual(serviceContext, null); + }); + }); });