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:
Michel Weststrate
2021-10-12 15:59:44 -07:00
committed by Facebook GitHub Bot
parent 51bfc8f05d
commit 3e7a6b1b4b
35 changed files with 102 additions and 40 deletions

View 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;
}
}
}

View 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);
}