Move metrics / tracking / error analysis to flipper-common package
Summary: what else can I say move_complexity Reviewed By: passy Differential Revision: D31483414 fbshipit-source-id: 1692c792121a3aae0843eb238040cae0445cdf54
This commit is contained in:
committed by
Facebook GitHub Bot
parent
51bfc8f05d
commit
3e7a6b1b4b
105
desktop/flipper-common/src/utils/errors.tsx
Normal file
105
desktop/flipper-common/src/utils/errors.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
export function isAuthError(
|
||||
err: any,
|
||||
): err is UserNotSignedInError | UserUnauthorizedError {
|
||||
return (
|
||||
err instanceof UserNotSignedInError || err instanceof UserUnauthorizedError
|
||||
);
|
||||
}
|
||||
|
||||
export function isConnectivityOrAuthError(
|
||||
err: any,
|
||||
): err is ConnectivityError | UserNotSignedInError | UserUnauthorizedError {
|
||||
return (
|
||||
err instanceof ConnectivityError ||
|
||||
isAuthError(err) ||
|
||||
String(err).startsWith('Failed to fetch')
|
||||
);
|
||||
}
|
||||
|
||||
export class CancelledPromiseError extends Error {
|
||||
constructor(msg: string) {
|
||||
super(msg);
|
||||
this.name = 'CancelledPromiseError';
|
||||
}
|
||||
name: 'CancelledPromiseError';
|
||||
}
|
||||
|
||||
export class ConnectivityError extends Error {
|
||||
constructor(msg: string) {
|
||||
super(msg);
|
||||
this.name = 'ConnectivityError';
|
||||
}
|
||||
name: 'ConnectivityError';
|
||||
}
|
||||
|
||||
export class UserUnauthorizedError extends Error {
|
||||
constructor(msg: string = 'User unauthorized.') {
|
||||
super(msg);
|
||||
this.name = 'UserUnauthorizedError';
|
||||
}
|
||||
name: 'UserUnauthorizedError';
|
||||
}
|
||||
|
||||
export class UserNotSignedInError extends Error {
|
||||
constructor(msg: string = 'User not signed in.') {
|
||||
super(msg);
|
||||
this.name = 'UserNotSignedInError';
|
||||
}
|
||||
name: 'UserNotSignedInError';
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Error {
|
||||
interaction?: unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export function isError(obj: any): obj is Error {
|
||||
return (
|
||||
obj instanceof Error ||
|
||||
(obj &&
|
||||
obj.name &&
|
||||
typeof obj.name === 'string' &&
|
||||
obj.message &&
|
||||
typeof obj.message === 'string' &&
|
||||
obj.stack &&
|
||||
typeof obj.stack === 'string')
|
||||
);
|
||||
}
|
||||
|
||||
export function getErrorFromErrorLike(e: any): Error | undefined {
|
||||
if (Array.isArray(e)) {
|
||||
return e.map(getErrorFromErrorLike).find((x) => x);
|
||||
} else if (isError(e)) {
|
||||
return e;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getStringFromErrorLike(e: any): string {
|
||||
if (Array.isArray(e)) {
|
||||
return e.map(getStringFromErrorLike).join(' ');
|
||||
} else if (typeof e == 'string') {
|
||||
return e;
|
||||
} else if (isError(e)) {
|
||||
return e.message || e.toString();
|
||||
} else {
|
||||
try {
|
||||
return JSON.stringify(e);
|
||||
} catch (e) {
|
||||
// Stringify might fail on arbitrary structures
|
||||
// Last resort: toString it.
|
||||
return '' + e;
|
||||
}
|
||||
}
|
||||
}
|
||||
188
desktop/flipper-common/src/utils/metrics.tsx
Normal file
188
desktop/flipper-common/src/utils/metrics.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {CancelledPromiseError, isError} from './errors';
|
||||
import {getLogger} from './Logger';
|
||||
|
||||
type Result =
|
||||
| {kind: 'success'}
|
||||
| {kind: 'cancelled'}
|
||||
| {kind: 'failure'; supportedOperation: boolean; error: any};
|
||||
|
||||
export class UnsupportedError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wraps a Promise, preserving it's functionality but logging the success or
|
||||
failure state of it, with a given name, based on whether it's fulfilled or
|
||||
rejected.
|
||||
|
||||
Use this variant to report failures in core platform (Flipper) code.
|
||||
*/
|
||||
export function reportPlatformFailures<T>(
|
||||
promise: Promise<T>,
|
||||
name: string,
|
||||
): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
promise
|
||||
.then((fulfilledValue) => {
|
||||
logPlatformSuccessRate(name, {kind: 'success'});
|
||||
resolve(fulfilledValue);
|
||||
})
|
||||
.catch((rejectionReason) => {
|
||||
if (rejectionReason instanceof CancelledPromiseError) {
|
||||
logPlatformSuccessRate(name, {
|
||||
kind: 'cancelled',
|
||||
});
|
||||
} else {
|
||||
logPlatformSuccessRate(name, {
|
||||
kind: 'failure',
|
||||
supportedOperation: !(rejectionReason instanceof UnsupportedError),
|
||||
error: rejectionReason,
|
||||
});
|
||||
}
|
||||
reject(rejectionReason);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Wraps a Promise, preserving it's functionality but logging the success or
|
||||
failure state of it, with a given name, based on whether it's fulfilled or
|
||||
rejected.
|
||||
|
||||
Use this variant to report failures in plugin code.
|
||||
*/
|
||||
export function reportPluginFailures<T>(
|
||||
promise: Promise<T>,
|
||||
name: string,
|
||||
plugin: string,
|
||||
): Promise<T> {
|
||||
return promise.then(
|
||||
(fulfilledValue) => {
|
||||
logPluginSuccessRate(name, plugin, {kind: 'success'});
|
||||
return fulfilledValue;
|
||||
},
|
||||
(rejectionReason) => {
|
||||
if (rejectionReason instanceof CancelledPromiseError) {
|
||||
logPluginSuccessRate(name, plugin, {
|
||||
kind: 'cancelled',
|
||||
});
|
||||
} else {
|
||||
logPluginSuccessRate(name, plugin, {
|
||||
kind: 'failure',
|
||||
supportedOperation: !(rejectionReason instanceof UnsupportedError),
|
||||
error: rejectionReason,
|
||||
});
|
||||
}
|
||||
return Promise.reject(rejectionReason);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wraps a closure, preserving it's functionality but logging the success or
|
||||
failure state of it.
|
||||
*/
|
||||
export function tryCatchReportPlatformFailures<T>(
|
||||
closure: () => T,
|
||||
name: string,
|
||||
): T {
|
||||
try {
|
||||
const result = closure();
|
||||
logPlatformSuccessRate(name, {kind: 'success'});
|
||||
return result;
|
||||
} catch (e) {
|
||||
logPlatformSuccessRate(name, {
|
||||
kind: 'failure',
|
||||
supportedOperation: !(e instanceof UnsupportedError),
|
||||
error: e,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wraps a closure, preserving it's functionality but logging the success or
|
||||
failure state of it.
|
||||
*/
|
||||
export function tryCatchReportPluginFailures<T>(
|
||||
closure: () => T,
|
||||
name: string,
|
||||
plugin: string,
|
||||
): T {
|
||||
try {
|
||||
const result = closure();
|
||||
logPluginSuccessRate(name, plugin, {kind: 'success'});
|
||||
return result;
|
||||
} catch (e) {
|
||||
logPluginSuccessRate(name, plugin, {
|
||||
kind: 'failure',
|
||||
supportedOperation: !(e instanceof UnsupportedError),
|
||||
error: e,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track usage of a feature.
|
||||
* @param action Unique name for the action performed. E.g. captureScreenshot
|
||||
* @param data Optional additional metadata attached to the event.
|
||||
*/
|
||||
export function reportUsage(
|
||||
action: string,
|
||||
data?: {[key: string]: string},
|
||||
plugin?: string,
|
||||
) {
|
||||
getLogger().track('usage', action, data, plugin);
|
||||
}
|
||||
|
||||
export function logPlatformSuccessRate(name: string, result: Result) {
|
||||
if (result.kind === 'success') {
|
||||
getLogger().track('success-rate', name, {value: 1});
|
||||
} else if (result.kind === 'cancelled') {
|
||||
getLogger().track('operation-cancelled', name);
|
||||
} else {
|
||||
getLogger().track('success-rate', name, {
|
||||
value: 0,
|
||||
supportedOperation: result.supportedOperation ? 1 : 0,
|
||||
error: extractMessage(result.error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function logPluginSuccessRate(name: string, plugin: string, result: Result) {
|
||||
if (result.kind === 'success') {
|
||||
getLogger().track('success-rate', name, {value: 1}, plugin);
|
||||
} else if (result.kind === 'cancelled') {
|
||||
getLogger().track('operation-cancelled', name, undefined, plugin);
|
||||
} else {
|
||||
getLogger().track(
|
||||
'success-rate',
|
||||
name,
|
||||
{
|
||||
value: 0,
|
||||
supportedOperation: result.supportedOperation ? 1 : 0,
|
||||
error: extractMessage(result.error),
|
||||
},
|
||||
plugin,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function extractMessage(error: any) {
|
||||
if (isError(error)) {
|
||||
return error.message;
|
||||
}
|
||||
return JSON.stringify(error);
|
||||
}
|
||||
Reference in New Issue
Block a user