提交 b53be6ef 编写于 作者: R Rob Lourens

Refactor to support ErrorTelemetry in non-browser processes

上级 06dd5823
......@@ -3,69 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { binarySearch } from 'vs/base/common/arrays';
import { toDisposable } from 'vs/base/common/lifecycle';
import { globals } from 'vs/base/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import * as Errors from 'vs/base/common/errors';
import { safeStringify } from 'vs/base/common/objects';
import BaseErrorTelemetry, { ErrorEvent } from '../common/errorTelemetry';
/* __GDPR__FRAGMENT__
"ErrorEvent" : {
"stack": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"message" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"filename" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"callstack": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"msg" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"file" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"line": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"column": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"uncaught_error_name": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"uncaught_error_msg": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"count": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
interface ErrorEvent {
callstack: string;
msg?: string;
file?: string;
line?: number;
column?: number;
uncaught_error_name?: string;
uncaught_error_msg?: string;
count?: number;
}
namespace ErrorEvent {
export function compare(a: ErrorEvent, b: ErrorEvent) {
if (a.callstack < b.callstack) {
return -1;
} else if (a.callstack > b.callstack) {
return 1;
}
return 0;
}
}
export default class ErrorTelemetry {
public static ERROR_FLUSH_TIMEOUT: number = 5 * 1000;
private _telemetryService: ITelemetryService;
private _flushDelay: number;
private _flushHandle: any = -1;
private _buffer: ErrorEvent[] = [];
private _disposables: IDisposable[] = [];
constructor(telemetryService: ITelemetryService, flushDelay = ErrorTelemetry.ERROR_FLUSH_TIMEOUT) {
this._telemetryService = telemetryService;
this._flushDelay = flushDelay;
// (1) check for unexpected but handled errors
const unbind = Errors.errorHandler.addListener((err) => this._onErrorEvent(err));
this._disposables.push(toDisposable(unbind));
// (2) check for uncaught global errors
export default class ErrorTelemetry extends BaseErrorTelemetry {
protected installErrorListeners(): void {
let oldOnError: Function;
let that = this;
if (typeof globals.onerror === 'function') {
......@@ -84,37 +27,7 @@ export default class ErrorTelemetry {
}));
}
dispose() {
clearTimeout(this._flushHandle);
this._flushBuffer();
this._disposables = dispose(this._disposables);
}
private _onErrorEvent(err: any): void {
if (!err) {
return;
}
// unwrap nested errors from loader
if (err.detail && err.detail.stack) {
err = err.detail;
}
// work around behavior in workerServer.ts that breaks up Error.stack
let callstack = Array.isArray(err.stack) ? err.stack.join('\n') : err.stack;
let msg = err.message ? err.message : safeStringify(err);
// errors without a stack are not useful telemetry
if (!callstack) {
return;
}
this._enqueue({ msg, callstack });
}
private _onUncaughtError(msg: string, file: string, line: number, column?: number, err?: any): void {
let data: ErrorEvent = {
callstack: msg,
msg,
......@@ -138,37 +51,4 @@ export default class ErrorTelemetry {
this._enqueue(data);
}
private _enqueue(e: ErrorEvent): void {
const idx = binarySearch(this._buffer, e, ErrorEvent.compare);
if (idx < 0) {
e.count = 1;
this._buffer.splice(~idx, 0, e);
} else {
if (!this._buffer[idx].count) {
this._buffer[idx].count = 0;
}
this._buffer[idx].count! += 1;
}
if (this._flushHandle === -1) {
this._flushHandle = setTimeout(() => {
this._flushBuffer();
this._flushHandle = -1;
}, this._flushDelay);
}
}
private _flushBuffer(): void {
for (let error of this._buffer) {
/* __GDPR__
"UnhandledError" : {
"${include}": [ "${ErrorEvent}" ]
}
*/
this._telemetryService.publicLog('UnhandledError', error, true);
}
this._buffer.length = 0;
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { binarySearch } from 'vs/base/common/arrays';
import * as Errors from 'vs/base/common/errors';
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { safeStringify } from 'vs/base/common/objects';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
/* __GDPR__FRAGMENT__
"ErrorEvent" : {
"stack": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"message" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"filename" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"callstack": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"msg" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"file" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"line": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"column": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"uncaught_error_name": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"uncaught_error_msg": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"count": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
export interface ErrorEvent {
callstack: string;
msg?: string;
file?: string;
line?: number;
column?: number;
uncaught_error_name?: string;
uncaught_error_msg?: string;
count?: number;
}
export namespace ErrorEvent {
export function compare(a: ErrorEvent, b: ErrorEvent) {
if (a.callstack < b.callstack) {
return -1;
} else if (a.callstack > b.callstack) {
return 1;
}
return 0;
}
}
export default abstract class BaseErrorTelemetry {
public static ERROR_FLUSH_TIMEOUT: number = 5 * 1000;
private _telemetryService: ITelemetryService;
private _flushDelay: number;
private _flushHandle: any = -1;
private _buffer: ErrorEvent[] = [];
protected _disposables: IDisposable[] = [];
constructor(telemetryService: ITelemetryService, flushDelay = BaseErrorTelemetry.ERROR_FLUSH_TIMEOUT) {
this._telemetryService = telemetryService;
this._flushDelay = flushDelay;
// (1) check for unexpected but handled errors
const unbind = Errors.errorHandler.addListener((err) => this._onErrorEvent(err));
this._disposables.push(toDisposable(unbind));
// (2) install implementation-specific error listeners
this.installErrorListeners();
}
dispose() {
clearTimeout(this._flushHandle);
this._flushBuffer();
this._disposables = dispose(this._disposables);
}
protected installErrorListeners(): void {
// to override
}
private _onErrorEvent(err: any): void {
if (!err) {
return;
}
// unwrap nested errors from loader
if (err.detail && err.detail.stack) {
err = err.detail;
}
// work around behavior in workerServer.ts that breaks up Error.stack
let callstack = Array.isArray(err.stack) ? err.stack.join('\n') : err.stack;
let msg = err.message ? err.message : safeStringify(err);
// errors without a stack are not useful telemetry
if (!callstack) {
return;
}
this._enqueue({ msg, callstack });
}
protected _enqueue(e: ErrorEvent): void {
const idx = binarySearch(this._buffer, e, ErrorEvent.compare);
if (idx < 0) {
e.count = 1;
this._buffer.splice(~idx, 0, e);
} else {
if (!this._buffer[idx].count) {
this._buffer[idx].count = 0;
}
this._buffer[idx].count! += 1;
}
if (this._flushHandle === -1) {
this._flushHandle = setTimeout(() => {
this._flushBuffer();
this._flushHandle = -1;
}, this._flushDelay);
}
}
private _flushBuffer(): void {
for (let error of this._buffer) {
/* __GDPR__
"UnhandledError" : {
"${include}": [ "${ErrorEvent}" ]
}
*/
this._telemetryService.publicLog('UnhandledError', error, true);
}
this._buffer.length = 0;
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import BaseErrorTelemetry from '../common/errorTelemetry';
export default class ErrorTelemetry extends BaseErrorTelemetry {
protected installErrorListeners(): void {
// Print a console message when rejection isn't handled within N seconds. For details:
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
const unhandledPromises: Promise<any>[] = [];
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
unhandledPromises.push(promise);
setTimeout(() => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
promise.catch(e => {
unhandledPromises.splice(idx, 1);
console.warn(`rejected promise not handled within 1 second: ${e}`);
if (e.stack) {
console.warn(`stack trace: ${e.stack}`);
}
onUnexpectedError(reason);
});
}
}, 1000);
});
process.on('rejectionHandled', (promise: Promise<any>) => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
}
});
// Print a console message when an exception isn't handled.
process.on('uncaughtException', (err: Error) => {
onUnexpectedError(err);
});
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册