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 4, 2022
1 parent 582a22d commit ff48eae
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 26 deletions.
7 changes: 1 addition & 6 deletions apps/ledger-live-desktop/release-notes.json
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
[
{
"tag_name": "2.43.0",
"body": "\nWe’re constantly adding new integrations and working on performance improvements to make Ledger Live a world-class experience. Here is what’s new in this release.\n\n### 🚀 Features\nWe’re excited to announce that Ledger Live is launching support for Cardano, one of the biggest cryptocurrencies based on its total market value. Now you can send and receive Cardano’s native token, ADA. \n\n### 🐛 Fixes\nWe’ve done some bug fixes and made a few small but important changes behind the curtain.\n"
}
]
[]
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/lib/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 @@ -19,6 +20,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 @@ -28,13 +30,24 @@ 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(),
// $FlowFixMe
...process.env,
IS_INTERNAL_PROCESS: 1,
LEDGER_CONFIG_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.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { ToastProvider } from "@ledgerhq/live-common/lib/notifications/ToastProv
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/lib/featureFlags";
import {
FeatureFlagsProvider,
defaultFeatures,
} from "@ledgerhq/live-common/lib/featureFlags";
import { FeatureId } from "@ledgerhq/live-common/lib/types";

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/lib/sanityChecks";
import { FeatureToggle } from "@ledgerhq/live-common/lib/featureFlags";
import { useCountervaluesExport } from "@ledgerhq/live-common/lib/countervalues/react";
import { pairId } from "@ledgerhq/live-common/lib/countervalues/helpers";

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

0 comments on commit ff48eae

Please sign in to comment.