diff --git a/packages/gcf-utils/src/gcf-utils.ts b/packages/gcf-utils/src/gcf-utils.ts index 59e573517d3..bff3544e264 100644 --- a/packages/gcf-utils/src/gcf-utils.ts +++ b/packages/gcf-utils/src/gcf-utils.ts @@ -47,6 +47,9 @@ import { export {TriggerType} from './bot-request'; export {GCFLogger} from './logging/gcf-logger'; +export const ERROR_REPORTING_TYPE_NAME = + 'type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent'; + // On Cloud Functions, rawBody is automatically added. // It's not guaranteed on other platform. export interface RequestWithRawBody extends express.Request { @@ -1310,7 +1313,15 @@ function parseRateLimitError(e: Error): RateLimits | undefined { * @param {GCFLogger} logger The logger to log to * @param {Error} e The error to log */ -function logErrors(logger: GCFLogger, e: Error) { +export function logErrors(logger: GCFLogger, e: Error) { + // Add "@type" bindings so that Cloud Error Reporting will capture these logs. + const bindings = logger.getBindings(); + if (bindings['@type'] !== ERROR_REPORTING_TYPE_NAME) { + logger = logger.child({ + '@type': ERROR_REPORTING_TYPE_NAME, + ...bindings, + }); + } if (e instanceof AggregateError) { for (const inner of e) { // AggregateError should not contain an AggregateError, but diff --git a/packages/gcf-utils/test/gcf-bootstrapper.ts b/packages/gcf-utils/test/gcf-bootstrapper.ts index 64407bc736d..a5c08bf2e58 100644 --- a/packages/gcf-utils/test/gcf-bootstrapper.ts +++ b/packages/gcf-utils/test/gcf-bootstrapper.ts @@ -482,7 +482,7 @@ describe('GCFBootstrapper', () => { it('logs errors for single handler errors', async () => { const fakeLogger = new GCFLogger(); sandbox.stub(loggerModule, 'buildRequestLogger').returns(fakeLogger); - const errorStub = sandbox.stub(fakeLogger, 'error'); + const errorStub = sandbox.stub(GCFLogger.prototype, 'error'); await mockBootstrapper(undefined, async app => { app.on('issues', async () => { throw new SyntaxError('Some error message'); @@ -513,7 +513,7 @@ describe('GCFBootstrapper', () => { it('logs errors for multiple handler errors', async () => { const fakeLogger = new GCFLogger(); sandbox.stub(loggerModule, 'buildRequestLogger').returns(fakeLogger); - const errorStub = sandbox.stub(fakeLogger, 'error'); + const errorStub = sandbox.stub(GCFLogger.prototype, 'error'); await mockBootstrapper(undefined, async app => { app.on('issues', async () => { throw new SyntaxError('Some error message'); diff --git a/packages/gcf-utils/test/gcf-utils.ts b/packages/gcf-utils/test/gcf-utils.ts index 3397efefee0..14377b5810a 100644 --- a/packages/gcf-utils/test/gcf-utils.ts +++ b/packages/gcf-utils/test/gcf-utils.ts @@ -17,20 +17,54 @@ import { addOrUpdateIssueComment, getAuthenticatedOctokit, + ERROR_REPORTING_TYPE_NAME, + logErrors, } from '../src/gcf-utils'; +import {GCFLogger} from '../src/logging/gcf-logger'; import * as gcfUtilsModule from '../src/gcf-utils'; import {Octokit} from '@octokit/rest'; import {resolve} from 'path'; import snapshot from 'snap-shot-it'; +import {ObjectWritableMock} from 'stream-mock'; import {Probot, ProbotOctokit} from 'probot'; import {describe, beforeEach, afterEach, it} from 'mocha'; import nock from 'nock'; import * as sinon from 'sinon'; +import * as assert from 'assert'; +import {LogLine} from './test-helpers'; nock.disableNetConnect(); const fixturesPath = resolve(__dirname, '../../test/fixtures'); +describe('logErrors', () => { + let destination: ObjectWritableMock; + let logger: GCFLogger & {[key: string]: Function}; + + function readLogsAsObjects(writeStream: ObjectWritableMock): LogLine[] { + try { + writeStream.end(); + const lines: string[] = writeStream.data; + return lines.map(line => JSON.parse(line)); + } catch (error) { + throw new Error(`Failed to read stream: ${error}`); + } + } + + beforeEach(() => { + destination = new ObjectWritableMock(); + logger = new GCFLogger(destination) as GCFLogger & { + [key: string]: Function; + }; + }); + + it('adds @type property to the log entry', () => { + logErrors(logger, new Error('An error happened')); + const loggedLines: LogLine[] = readLogsAsObjects(destination); + assert.strictEqual(loggedLines[0]['@type'], ERROR_REPORTING_TYPE_NAME); + }); +}); + // Test app const app = (app: Probot) => { app.on('issues', async context => {