chore: Centralize clientside logging
This commit is contained in:
@@ -5,6 +5,7 @@ import * as React from "react";
|
|||||||
import Collection from "~/models/Collection";
|
import Collection from "~/models/Collection";
|
||||||
import { icons } from "~/components/IconPicker";
|
import { icons } from "~/components/IconPicker";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
|
import Logger from "~/utils/logger";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
@@ -36,7 +37,9 @@ function ResolvedCollectionIcon({
|
|||||||
const Component = icons[collection.icon].component;
|
const Component = icons[collection.icon].component;
|
||||||
return <Component color={color} size={size} />;
|
return <Component color={color} size={size} />;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Failed to render custom icon " + collection.icon);
|
Logger.warn("Failed to render custom icon", {
|
||||||
|
icon: collection.icon,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from "@sentry/react";
|
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@@ -11,6 +10,7 @@ import PageTitle from "~/components/PageTitle";
|
|||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import isHosted from "~/utils/isHosted";
|
import isHosted from "~/utils/isHosted";
|
||||||
|
import Logger from "~/utils/logger";
|
||||||
|
|
||||||
type Props = WithTranslation & {
|
type Props = WithTranslation & {
|
||||||
reloadOnChunkMissing?: boolean;
|
reloadOnChunkMissing?: boolean;
|
||||||
@@ -26,7 +26,6 @@ class ErrorBoundary extends React.Component<Props> {
|
|||||||
|
|
||||||
componentDidCatch(error: Error) {
|
componentDidCatch(error: Error) {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.props.reloadOnChunkMissing &&
|
this.props.reloadOnChunkMissing &&
|
||||||
@@ -40,9 +39,7 @@ class ErrorBoundary extends React.Component<Props> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.SENTRY_DSN) {
|
Logger.error("ErrorBoundary", error);
|
||||||
Sentry.captureException(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReload = () => {
|
handleReload = () => {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { EmbedDescriptor, EventType } from "@shared/editor/types";
|
|||||||
import EventEmitter from "@shared/utils/events";
|
import EventEmitter from "@shared/utils/events";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import { Dictionary } from "~/hooks/useDictionary";
|
import { Dictionary } from "~/hooks/useDictionary";
|
||||||
|
import Logger from "~/utils/logger";
|
||||||
import BlockMenu from "./components/BlockMenu";
|
import BlockMenu from "./components/BlockMenu";
|
||||||
import ComponentView from "./components/ComponentView";
|
import ComponentView from "./components/ComponentView";
|
||||||
import EditorContext from "./components/EditorContext";
|
import EditorContext from "./components/EditorContext";
|
||||||
@@ -476,7 +477,7 @@ export class Editor extends React.PureComponent<
|
|||||||
// querySelector will throw an error if the hash begins with a number
|
// querySelector will throw an error if the hash begins with a number
|
||||||
// or contains a period. This is protected against now by safeSlugify
|
// or contains a period. This is protected against now by safeSlugify
|
||||||
// however previous links may be in the wild.
|
// however previous links may be in the wild.
|
||||||
console.warn(`Attempted to scroll to invalid hash: ${hash}`, err);
|
Logger.debug("editor", `Attempted to scroll to invalid hash: ${hash}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Primitive } from "utility-types";
|
import { Primitive } from "utility-types";
|
||||||
import Storage from "~/utils/Storage";
|
import Storage from "~/utils/Storage";
|
||||||
|
import Logger from "~/utils/logger";
|
||||||
import useEventListener from "./useEventListener";
|
import useEventListener from "./useEventListener";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +33,7 @@ export default function usePersistedState(
|
|||||||
Storage.set(key, valueToStore);
|
Storage.set(key, valueToStore);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// A more advanced implementation would handle the error case
|
// A more advanced implementation would handle the error case
|
||||||
console.log(error);
|
Logger.debug("misc", "Failed to persist state", { error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Toasts from "~/components/Toasts";
|
|||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import Routes from "./routes";
|
import Routes from "./routes";
|
||||||
import history from "./utils/history";
|
import history from "./utils/history";
|
||||||
|
import Logger from "./utils/logger";
|
||||||
import { initSentry } from "./utils/sentry";
|
import { initSentry } from "./utils/sentry";
|
||||||
|
|
||||||
initI18n();
|
initI18n();
|
||||||
@@ -40,10 +41,14 @@ if ("serviceWorker" in window.navigator) {
|
|||||||
if (maybePromise?.then) {
|
if (maybePromise?.then) {
|
||||||
maybePromise
|
maybePromise
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
console.log("SW registered: ", registration);
|
Logger.debug("lifecycle", "SW registered: ", registration);
|
||||||
})
|
})
|
||||||
.catch((registrationError) => {
|
.catch((registrationError) => {
|
||||||
console.log("SW registration failed: ", registrationError);
|
Logger.debug(
|
||||||
|
"lifecycle",
|
||||||
|
"SW registration failed: ",
|
||||||
|
registrationError
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import useStores from "~/hooks/useStores";
|
|||||||
import useToasts from "~/hooks/useToasts";
|
import useToasts from "~/hooks/useToasts";
|
||||||
import MultiplayerExtension from "~/multiplayer/MultiplayerExtension";
|
import MultiplayerExtension from "~/multiplayer/MultiplayerExtension";
|
||||||
import { supportsPassiveListener } from "~/utils/browser";
|
import { supportsPassiveListener } from "~/utils/browser";
|
||||||
|
import Logger from "~/utils/logger";
|
||||||
import { homePath } from "~/utils/routeHelpers";
|
import { homePath } from "~/utils/routeHelpers";
|
||||||
|
|
||||||
type Props = EditorProps & {
|
type Props = EditorProps & {
|
||||||
@@ -139,15 +140,21 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
|||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
provider.on("status", (ev: ConnectionStatusEvent) =>
|
provider.on("status", (ev: ConnectionStatusEvent) =>
|
||||||
console.log("status", ev.status)
|
Logger.debug("collaboration", "status", ev)
|
||||||
);
|
);
|
||||||
provider.on("message", (ev: MessageEvent) =>
|
provider.on("message", (ev: MessageEvent) =>
|
||||||
console.log("incoming", ev.message)
|
Logger.debug("collaboration", "incoming", {
|
||||||
|
message: ev.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
provider.on("outgoingMessage", (ev: MessageEvent) =>
|
provider.on("outgoingMessage", (ev: MessageEvent) =>
|
||||||
console.log("outgoing", ev.message)
|
Logger.debug("collaboration", "outgoing", {
|
||||||
|
message: ev.message,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
localProvider.on("synced", () =>
|
||||||
|
Logger.debug("collaboration", "local synced")
|
||||||
);
|
);
|
||||||
localProvider.on("synced", () => console.log("local synced"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.on("status", (ev: ConnectionStatusEvent) =>
|
provider.on("status", (ev: ConnectionStatusEvent) =>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import RegisterKeyDown from "~/components/RegisterKeyDown";
|
|||||||
import Scene from "~/components/Scene";
|
import Scene from "~/components/Scene";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import withStores from "~/components/withStores";
|
import withStores from "~/components/withStores";
|
||||||
|
import Logger from "~/utils/logger";
|
||||||
import { searchPath } from "~/utils/routeHelpers";
|
import { searchPath } from "~/utils/routeHelpers";
|
||||||
import { decodeURIComponentSafe } from "~/utils/urls";
|
import { decodeURIComponentSafe } from "~/utils/urls";
|
||||||
import CollectionFilter from "./components/CollectionFilter";
|
import CollectionFilter from "./components/CollectionFilter";
|
||||||
@@ -257,9 +258,9 @@ class Search extends React.Component<Props> {
|
|||||||
} else {
|
} else {
|
||||||
this.offset += DEFAULT_PAGINATION_LIMIT;
|
this.offset += DEFAULT_PAGINATION_LIMIT;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
|
Logger.error("Search query failed", error);
|
||||||
this.lastQuery = "";
|
this.lastQuery = "";
|
||||||
throw err;
|
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as Sentry from "@sentry/react";
|
|
||||||
import invariant from "invariant";
|
import invariant from "invariant";
|
||||||
import { client } from "./ApiClient";
|
import { client } from "./ApiClient";
|
||||||
|
import Logger from "./logger";
|
||||||
|
|
||||||
type UploadOptions = {
|
type UploadOptions = {
|
||||||
/** The user facing name of the file */
|
/** The user facing name of the file */
|
||||||
@@ -45,7 +45,6 @@ export const uploadFile = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Using XMLHttpRequest instead of fetch because fetch doesn't support progress
|
// Using XMLHttpRequest instead of fetch because fetch doesn't support progress
|
||||||
let error;
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
const success = await new Promise((resolve) => {
|
const success = await new Promise((resolve) => {
|
||||||
xhr.upload.addEventListener("progress", (event) => {
|
xhr.upload.addEventListener("progress", (event) => {
|
||||||
@@ -53,7 +52,12 @@ export const uploadFile = async (
|
|||||||
options.onProgress(event.loaded / event.total);
|
options.onProgress(event.loaded / event.total);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
xhr.addEventListener("error", (err) => (error = err));
|
xhr.addEventListener("error", () => {
|
||||||
|
Logger.error(
|
||||||
|
"File upload failed",
|
||||||
|
new Error(`${xhr.status} ${xhr.statusText}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
xhr.addEventListener("loadend", () => {
|
xhr.addEventListener("loadend", () => {
|
||||||
resolve(xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 400);
|
resolve(xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 400);
|
||||||
});
|
});
|
||||||
@@ -62,7 +66,6 @@ export const uploadFile = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Sentry.captureException(error);
|
|
||||||
throw new Error("Upload failed");
|
throw new Error("Upload failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
86
app/utils/logger.ts
Normal file
86
app/utils/logger.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
import env from "~/env";
|
||||||
|
|
||||||
|
type LogCategory =
|
||||||
|
| "lifecycle"
|
||||||
|
| "http"
|
||||||
|
| "editor"
|
||||||
|
| "router"
|
||||||
|
| "collaboration"
|
||||||
|
| "misc";
|
||||||
|
|
||||||
|
type Extra = Record<string, any>;
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
/**
|
||||||
|
* Log information
|
||||||
|
*
|
||||||
|
* @param category A log message category that will be prepended
|
||||||
|
* @param extra Arbitrary data to be logged that will appear in prod logs
|
||||||
|
*/
|
||||||
|
info(label: LogCategory, message: string, extra?: Extra) {
|
||||||
|
console.info(`[${label}] ${message}`, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug information
|
||||||
|
*
|
||||||
|
* @param category A log message category that will be prepended
|
||||||
|
* @param extra Arbitrary data to be logged
|
||||||
|
*/
|
||||||
|
debug(label: LogCategory, message: string, extra?: Extra) {
|
||||||
|
if (env.ENVIRONMENT === "development") {
|
||||||
|
console.debug(`[${label}] ${message}`, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a warning
|
||||||
|
*
|
||||||
|
* @param message A warning message
|
||||||
|
* @param extra Arbitrary data to be logged that will appear in prod logs
|
||||||
|
*/
|
||||||
|
warn(message: string, extra?: Extra) {
|
||||||
|
if (env.SENTRY_DSN) {
|
||||||
|
Sentry.withScope(function (scope) {
|
||||||
|
scope.setLevel(Sentry.Severity.Warning);
|
||||||
|
|
||||||
|
for (const key in extra) {
|
||||||
|
scope.setExtra(key, extra[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sentry.captureMessage(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(message, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report a runtime error
|
||||||
|
*
|
||||||
|
* @param message A description of the error
|
||||||
|
* @param error The error that occurred
|
||||||
|
* @param extra Arbitrary data to be logged that will appear in prod logs
|
||||||
|
*/
|
||||||
|
error(message: string, error: Error, extra?: Extra) {
|
||||||
|
if (env.SENTRY_DSN) {
|
||||||
|
Sentry.withScope(function (scope) {
|
||||||
|
scope.setLevel(Sentry.Severity.Error);
|
||||||
|
|
||||||
|
for (const key in extra) {
|
||||||
|
scope.setExtra(key, extra[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sentry.captureException(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(message, {
|
||||||
|
error,
|
||||||
|
extra,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Logger();
|
||||||
@@ -74,9 +74,10 @@ class Logger {
|
|||||||
|
|
||||||
if (process.env.SENTRY_DSN) {
|
if (process.env.SENTRY_DSN) {
|
||||||
Sentry.withScope(function (scope) {
|
Sentry.withScope(function (scope) {
|
||||||
|
scope.setLevel(Sentry.Severity.Warning);
|
||||||
|
|
||||||
for (const key in extra) {
|
for (const key in extra) {
|
||||||
scope.setExtra(key, extra[key]);
|
scope.setExtra(key, extra[key]);
|
||||||
scope.setLevel(Sentry.Severity.Warning);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Sentry.captureMessage(message);
|
Sentry.captureMessage(message);
|
||||||
@@ -105,9 +106,10 @@ class Logger {
|
|||||||
|
|
||||||
if (process.env.SENTRY_DSN) {
|
if (process.env.SENTRY_DSN) {
|
||||||
Sentry.withScope(function (scope) {
|
Sentry.withScope(function (scope) {
|
||||||
|
scope.setLevel(Sentry.Severity.Error);
|
||||||
|
|
||||||
for (const key in extra) {
|
for (const key in extra) {
|
||||||
scope.setExtra(key, extra[key]);
|
scope.setExtra(key, extra[key]);
|
||||||
scope.setLevel(Sentry.Severity.Error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Sentry.captureException(error);
|
Sentry.captureException(error);
|
||||||
|
|||||||
Reference in New Issue
Block a user