Skip to content

Commit

Permalink
Attach feature flags and envs to Sentry reports
Browse files Browse the repository at this point in the history
  • Loading branch information
gre committed Jul 5, 2022
1 parent cf80564 commit bc86070
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 20 deletions.
6 changes: 6 additions & 0 deletions .changeset/shiny-swans-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"ledger-live-desktop": patch
"live-mobile": patch
---

Log experimental and feature flags in Sentry error reports.
13 changes: 12 additions & 1 deletion apps/ledger-live-desktop/src/internal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import logger from "~/logger";
import LoggerTransport from "~/logger/logger-transport-internal";

import { executeCommand, unsubscribeCommand, unsubscribeAllCommands } from "./commandHandler";
import sentry from "~/sentry/internal";
import sentry, { setTags } from "~/sentry/internal";

process.on("exit", () => {
logger.debug("exiting process, unsubscribing all...");
Expand Down Expand Up @@ -42,6 +42,12 @@ let sentryEnabled = process.env.INITIAL_SENTRY_ENABLED !== "false";
const userId = process.env.SENTRY_USER_ID || "";
sentry(() => Boolean(userId) && sentryEnabled, userId);

const { INITIAL_SENTRY_TAGS } = process.env;
if (INITIAL_SENTRY_TAGS) {
const parsed = JSON.parse(INITIAL_SENTRY_TAGS);
if (parsed) setTags(parsed);
}

process.on("message", m => {
switch (m.type) {
case "command":
Expand Down Expand Up @@ -74,6 +80,11 @@ process.on("message", m => {
break;
}

case "set-sentry-tags": {
setTags(JSON.parse(m.tagsJSON));
break;
}

case "internalCrashTest": {
logger.critical(new Error("CrashTestInternal"));
break;
Expand Down
13 changes: 13 additions & 0 deletions apps/ledger-live-desktop/src/main/internal-lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { app, ipcMain } from "electron";
import path from "path";
import { setEnvUnsafe, getAllEnvs } from "@ledgerhq/live-common/env";
import { isRestartNeeded } from "~/helpers/env";
import { setTags } from "~/sentry/main";
import logger from "~/logger";
import { getMainWindow } from "./window-lifecycle";
import InternalProcess from "./InternalProcess";
Expand All @@ -20,6 +21,7 @@ const internal = new InternalProcess({ timeout: 3000 });

let sentryEnabled = null;
let userId = null;
let sentryTags = null;

export function getSentryEnabled(): boolean | null {
return sentryEnabled;
Expand All @@ -29,6 +31,16 @@ export function setUserId(id: string) {
userId = id;
}

ipcMain.handle("set-sentry-tags", (event, tags) => {
setTags(tags);
const tagsJSON = JSON.stringify(tags);
sentryTags = tagsJSON;
internal.send({
type: "set-sentry-tags",
tagsJSON,
});
});

const spawnCoreProcess = () => {
const env = {
...getAllEnvs(),
Expand All @@ -37,6 +49,7 @@ const spawnCoreProcess = () => {
IS_INTERNAL_PROCESS: 1,
LEDGER_CONFIG_DIRECTORY,
HOME_DIRECTORY,
INITIAL_SENTRY_TAGS: sentryTags,
INITIAL_SENTRY_ENABLED: String(!!sentryEnabled),
SENTRY_USER_ID: userId,
};
Expand Down
3 changes: 3 additions & 0 deletions apps/ledger-live-desktop/src/renderer/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { ToastProvider } from "@ledgerhq/live-common/notifications/ToastProvider
import { themeSelector } from "./actions/general";
// $FlowFixMe
import MarketDataProvider from "~/renderer/screens/market/MarketDataProviderWrapper";
// $FlowFixMe
import { ConnectEnvsToSentry } from "~/renderer/components/ConnectEnvsToSentry";

const reloadApp = event => {
if ((event.ctrlKey || event.metaKey) && event.key === "r") {
Expand Down Expand Up @@ -73,6 +75,7 @@ const InnerApp = ({ initialCountervalues }: { initialCountervalues: * }) => {
<RemoteConfigProvider>
<FirebaseRemoteConfigProvider>
<FirebaseFeatureFlagsProvider>
<ConnectEnvsToSentry />
<UpdaterProvider>
<CountervaluesProvider initialState={initialCountervalues}>
<ToastProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useEffect } from "react";
import { ipcRenderer } from "electron";
import { EnvName, getEnv } from "@ledgerhq/live-common/lib/env";
import { defaultFeatures, useFeatureFlags } from "@ledgerhq/live-common/lib/featureFlags";
import { FeatureId } from "@ledgerhq/live-common/lib/types";
import { enabledExperimentalFeatures } from "../experimental";
import { setTags } from "../../sentry/renderer";

function setSentryTagsEverywhere(tags: { [_: string]: any }) {
ipcRenderer.invoke("set-sentry-tags", tags);
setTags(tags);
}

const MAX_KEYLEN = 32;
function safekey(k: string) {
if (k.length > MAX_KEYLEN) {
const sep = "..";
const max = MAX_KEYLEN - sep.length;
const split1 = Math.floor(max / 2);
return k.slice(0, split1) + ".." + k.slice(k.length - (max - split1));
}
return k;
}

export const ConnectEnvsToSentry = () => {
const featureFlags = useFeatureFlags();
useEffect(() => {
// This sync the Sentry tags to include the extra information in context of events
const syncTheTags = () => {
const tags: { [_: string]: any } = {};
// if there are experimental on, we will add them in tags
enabledExperimentalFeatures().forEach((key: EnvName) => {
tags[safekey(key)] = getEnv(key);
});
// if there are features on, we will add them in tags
const features: { [key in FeatureId]: boolean } = {};
Object.keys(defaultFeatures).forEach(key => {
const value = featureFlags.getFeature(key);
if (value && value.enabled !== defaultFeatures[key].enabled) {
features[key] = value.enabled;
}
});

Object.keys(features).forEach(key => {
tags[safekey(`f_${key}`)] = features[key];
});

setSentryTagsEverywhere(tags);
};
// We need to wait firebase to load the data and then we set once for all the tags
setTimeout(syncTheTags, 5000);
// We also try to regularly update them so we are sure to get the correct tags (as these are dynamic)
setInterval(syncTheTags, 60000);
}, []);
return null;
};
4 changes: 4 additions & 0 deletions apps/ledger-live-desktop/src/sentry/internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ export const captureException = (e: Error) => {
export const captureBreadcrumb = (o: *) => {
Sentry.addBreadcrumb(o);
};

export const setTags = (tags: *) => {
Sentry.setTags(tags);
};
4 changes: 4 additions & 0 deletions apps/ledger-live-desktop/src/sentry/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ export const captureException = (e: Error) => {
export const captureBreadcrumb = (o: *) => {
Sentry.addBreadcrumb(o);
};

export const setTags = (tags: *) => {
Sentry.setTags(tags);
};
4 changes: 4 additions & 0 deletions apps/ledger-live-desktop/src/sentry/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export const captureException = (e: Error) => {
export const captureBreadcrumb = (o: *) => {
Sentry.addBreadcrumb(o);
};

export const setTags = (tags: *) => {
Sentry.setTags(tags);
};
35 changes: 34 additions & 1 deletion apps/ledger-live-mobile/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ import * as Sentry from "@sentry/react-native";
import Config from "react-native-config";
import VersionNumber from "react-native-version-number";

import { getEnv } from "@ledgerhq/live-common/lib/env";
import BackgroundRunnerService from "./services/BackgroundRunnerService";
import App, { routingInstrumentation } from "./src";
import { getEnabled } from "./src/components/HookSentry";
import logReport from "./src/log-report";
import pkg from "./package.json";
import { getAllDivergedFlags } from "./src/components/FirebaseFeatureFlags";
import { enabledExperimentalFeatures } from "./src/experimental";

// we exclude errors related to user's environment, not fixable by us
const excludedErrorName = [
Expand Down Expand Up @@ -73,7 +76,7 @@ const excludedErrorDescription = [
"Unable to open URL",
"Received an invalid JSON-RPC message",
];
if (Config.SENTRY_DSN && !__DEV__ && !Config.MOCK) {
if (Config.SENTRY_DSN && (!__DEV__ || Config.FORCE_SENTRY) && !Config.MOCK) {
Sentry.init({
dsn: Config.SENTRY_DSN,
environment: Config.SENTRY_ENVIRONMENT,
Expand Down Expand Up @@ -116,6 +119,36 @@ if (Config.SENTRY_DSN && !__DEV__ && !Config.MOCK) {
return event;
},
});

const MAX_KEYLEN = 32;
const safekey = (k: string) => {
if (k.length > MAX_KEYLEN) {
const sep = "..";
const max = MAX_KEYLEN - sep.length;
const split1 = Math.floor(max / 2);
return k.slice(0, split1) + ".." + k.slice(k.length - (max - split1));
}
return k;
};

// This sync the Sentry tags to include the extra information in context of events
const syncTheTags = () => {
const tags = {};
// if there are experimental on, we will add them in tags
enabledExperimentalFeatures().forEach(key => {
tags[safekey(key)] = getEnv(key);
});
// if there are features on, we will add them in tags
const features = getAllDivergedFlags();
Object.keys(features).forEach(key => {
tags[safekey(`f_${key}`)] = features[key];
});
Sentry.setTags(tags);
};
// We need to wait firebase to load the data and then we set once for all the tags
setTimeout(syncTheTags, 5000);
// We also try to regularly update them so we are sure to get the correct tags (as these are dynamic)
setInterval(syncTheTags, 60000);
}

if (Config.DISABLE_YELLOW_BOX) {
Expand Down
49 changes: 32 additions & 17 deletions apps/ledger-live-mobile/src/components/FirebaseFeatureFlags.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { ReactNode } from "react";
import remoteConfig from "@react-native-firebase/remote-config";
import { FeatureFlagsProvider } from "@ledgerhq/live-common/featureFlags/index";
import {
FeatureFlagsProvider,
defaultFeatures,
} from "@ledgerhq/live-common/featureFlags/index";
import { FeatureId } from "@ledgerhq/live-common/types/index";

import { formatFeatureId } from "./FirebaseRemoteConfig";
Expand All @@ -9,22 +12,34 @@ type Props = {
children?: ReactNode;
};

export const FirebaseFeatureFlagsProvider = ({ children }: Props) => {
const getFeature = (key: FeatureId) => {
try {
const value = remoteConfig().getValue(formatFeatureId(key));
const feature = JSON.parse(value.asString());
const getFeature = (key: FeatureId) => {
try {
const value = remoteConfig().getValue(formatFeatureId(key));
const feature = JSON.parse(value.asString());

return feature;
} catch (error) {
console.error(`Failed to retrieve feature "${key}"`);
return null;
}
};
return feature;
} catch (error) {
console.error(`Failed to retrieve feature "${key}"`);
return null;
}
};

return (
<FeatureFlagsProvider getFeature={getFeature}>
{children}
</FeatureFlagsProvider>
);
/**
* @returns all flags that have different defaults are exported as a key value map
*/
export const getAllDivergedFlags = (): { [key in FeatureId]: boolean } => {
const res: { [key in FeatureId]: boolean } = {};
Object.keys(defaultFeatures).forEach(key => {
const value = getFeature(key);
if (value && value.enabled !== defaultFeatures[key].enabled) {
res[key] = value.enabled;
}
});
return res;
};

export const FirebaseFeatureFlagsProvider = ({ children }: Props) => (
<FeatureFlagsProvider getFeature={getFeature}>
{children}
</FeatureFlagsProvider>
);
1 change: 0 additions & 1 deletion apps/ledger-live-mobile/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { checkLibs } from "@ledgerhq/live-common/sanityChecks";
import { FeatureToggle } from "@ledgerhq/live-common/featureFlags/index";
import { useCountervaluesExport } from "@ledgerhq/live-common/countervalues/react";
import { pairId } from "@ledgerhq/live-common/countervalues/helpers";

import { NftMetadataProvider } from "@ledgerhq/live-common/nft/index";
import { ToastProvider } from "@ledgerhq/live-common/notifications/ToastProvider/index";
import { GlobalCatalogProvider } from "@ledgerhq/live-common/platform/providers/GlobalCatalogProvider/index";
Expand Down

0 comments on commit bc86070

Please sign in to comment.