mirror of
https://github.com/jely2002/youtube-dl-gui.git
synced 2021-11-01 22:46:21 +03:00
chore: add Sentry error reporting to project
This commit is contained in:
10
main.js
10
main.js
@@ -8,14 +8,19 @@ const AppUpdater = require("./modules/AppUpdater");
|
||||
const TaskList = require("./modules/persistence/TaskList");
|
||||
const DoneAction = require("./modules/DoneAction");
|
||||
const ClipboardWatcher = require("./modules/ClipboardWatcher");
|
||||
const Analytics = require("./modules/Analytics");
|
||||
|
||||
let win
|
||||
let env
|
||||
let queryManager
|
||||
let clipboardWatcher
|
||||
let taskList
|
||||
let analytics;
|
||||
let appStarting = true;
|
||||
|
||||
analytics = new Analytics(app);
|
||||
analytics.initSentry().then(() => console.log("Sentry initialized"));
|
||||
|
||||
function sendLogToRenderer(log, isErr) {
|
||||
if(win == null) return;
|
||||
win.webContents.send("log", {log: log, isErr: isErr});
|
||||
@@ -196,12 +201,9 @@ function createWindow(env) {
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
env = new Environment(app);
|
||||
env = new Environment(app, analytics);
|
||||
await env.initialize();
|
||||
createWindow(env);
|
||||
if(app.isPackaged && process.argv[2] !== '--dev') {
|
||||
env.analytics.sendDownload();
|
||||
}
|
||||
globalShortcut.register('Control+Shift+I', () => { win.webContents.openDevTools(); })
|
||||
})
|
||||
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
const axios = require("axios");
|
||||
const querystring = require("querystring");
|
||||
const Utils = require("./Utils");
|
||||
const Sentry = require("@sentry/electron");
|
||||
const path = require("path");
|
||||
|
||||
class Analytics {
|
||||
constructor(version, paths, settings) {
|
||||
this.paths = paths;
|
||||
this.version = version;
|
||||
this.settings = settings;
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
async sendDownload() {
|
||||
if(!this.settings.statSend) {
|
||||
await axios.post('http://backend.jelleglebbeek.com/youtubedl/download.php/', querystring.stringify({ version: this.version }));
|
||||
this.settings.statSend = true;
|
||||
this.settings.save();
|
||||
}
|
||||
initSentry() {
|
||||
return new Promise(resolve => {
|
||||
require('dotenv').config({path: this.app.isPackaged ? path.join(process.cwd(), "/resources/app.asar/.env") : path.resolve(process.cwd(), '.env')});
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
release: "youtube-dl-gui@" + this.app.getVersion(),
|
||||
sendDefaultPii: true,
|
||||
environment: process.argv[2] === '--dev' ? "development" : "production"
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
async sendReport(err) {
|
||||
const id = Utils.getRandomID(8);
|
||||
await axios.post('http://backend.jelleglebbeek.com/youtubedl/errorreport.php/', querystring.stringify({ id: id, version: this.version, code: err.error.code, description: err.error.description, platform: process.platform, url: err.url, type: err.type, quality: err.quality}));
|
||||
async sendReport(id) {
|
||||
//Legacy code, no longer used.
|
||||
//Await axios.post('http://backend.jelleglebbeek.com/youtubedl/errorreport.php/', querystring.stringify({ id: id, version: this.version, code: err.error.code, description: err.error.description, platform: process.platform, url: err.url, type: err.type, quality: err.quality}));
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const Bottleneck = require("bottleneck");
|
||||
const Filepaths = require("./Filepaths");
|
||||
const Settings = require("./persistence/Settings");
|
||||
const Analytics = require("./Analytics");
|
||||
const DetectPython = require("./DetectPython");
|
||||
const fs = require("fs").promises;
|
||||
|
||||
class Environment {
|
||||
constructor(app) {
|
||||
constructor(app, analytics) {
|
||||
this.app = app;
|
||||
this.analytics = analytics;
|
||||
this.version = app.getVersion();
|
||||
this.cookiePath = null;
|
||||
this.mainAudioOnly = false;
|
||||
@@ -45,7 +45,6 @@ class Environment {
|
||||
} else {
|
||||
this.pythonCommand = "python";
|
||||
}
|
||||
this.analytics = new Analytics(this.app.getVersion(), this.paths, this.settings);
|
||||
}
|
||||
|
||||
changeMaxConcurrent(max) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
const Sentry = require("@sentry/electron");
|
||||
const Utils = require("./Utils");
|
||||
|
||||
class ErrorHandler {
|
||||
constructor(win, queryManager, env) {
|
||||
this.env = env;
|
||||
@@ -188,12 +191,25 @@ class ErrorHandler {
|
||||
if(video.type === "playlist") return;
|
||||
let errorDef = {
|
||||
identifier: identifier,
|
||||
error_id: Utils.getRandomID(8),
|
||||
unexpected: true,
|
||||
error: {
|
||||
code: "Unhandled exception",
|
||||
description: error,
|
||||
}
|
||||
};
|
||||
Sentry.captureMessage(error, scope => {
|
||||
scope.setLevel(Sentry.Severity.Error);
|
||||
scope.setTag("url", video.url);
|
||||
scope.setTag("error_id", errorDef.error_id);
|
||||
if(video.formats != null) {
|
||||
scope.setData("formats", video.formats);
|
||||
}
|
||||
if(video.selected_format_index != null) {
|
||||
scope.setData("selected_format", video.formats[video.selected_format_index].serialize())
|
||||
}
|
||||
scope.setData("settings", this.env.settings);
|
||||
});
|
||||
this.win.webContents.send("error", errorDef);
|
||||
this.unhandledErrors.push(errorDef);
|
||||
this.queryManager.onError(identifier);
|
||||
@@ -206,6 +222,7 @@ class ErrorHandler {
|
||||
console.error(errorDef.code + " - " + errorDef.description);
|
||||
this.win.webContents.send("error", { error: errorDef, identifier: identifier, unexpected: false, url: video.url });
|
||||
this.queryManager.onError(identifier);
|
||||
Sentry.captureMessage(errorDef.code, Sentry.Severity.Warning);
|
||||
}
|
||||
|
||||
async reportError(args) {
|
||||
@@ -215,7 +232,7 @@ class ErrorHandler {
|
||||
err.url = video.url;
|
||||
err.type = args.type;
|
||||
err.quality = args.quality;
|
||||
return await this.env.analytics.sendReport(err);
|
||||
return await this.env.analytics.sendReport(err.error_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
const Sentry = require("@sentry/electron");
|
||||
const version = require('./package.json').version;
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
release: "youtube-dl-gui@" + version,
|
||||
sendDefaultPii: true,
|
||||
environment: process.argv[2] === '--dev' ? "development" : "production"
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
"main",
|
||||
{
|
||||
|
||||
@@ -1,50 +1,66 @@
|
||||
const Analytics = require('../modules/Analytics');
|
||||
const axios = require("axios");
|
||||
const Utils = require("../modules/Utils");
|
||||
const dotenv = require("dotenv");
|
||||
const Sentry = require("@sentry/electron");
|
||||
const path = require("path");
|
||||
|
||||
jest.mock('axios');
|
||||
jest.mock('dotenv');
|
||||
|
||||
jest.mock('@sentry/electron', () => ({
|
||||
init: jest.fn()
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(axios, 'post').mockResolvedValue("");
|
||||
});
|
||||
|
||||
describe('sendDownload', () => {
|
||||
it('posts data to the backend when not done already', () => {
|
||||
const instance = new Analytics("v2.0.0-test1", null, {statSend: false, save: jest.fn()});
|
||||
instance.sendDownload().then(() => {
|
||||
expect(axios.post).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('init sentry', () => {
|
||||
it("loads dotenv with packaged path", () => {
|
||||
const dotenvMock = jest.spyOn(dotenv, 'config').mockImplementation(() => {});
|
||||
const instance = instanceBuiler(true);
|
||||
instance.initSentry();
|
||||
expect(dotenvMock).toBeCalledTimes(1);
|
||||
expect(dotenvMock).toBeCalledWith({path: path.join(process.cwd(), "/resources/app.asar/.env")});
|
||||
});
|
||||
it("loads dotenv with test path", () => {
|
||||
const dotenvMock = jest.spyOn(dotenv, 'config').mockImplementation(() => {});
|
||||
const instance = instanceBuiler(false);
|
||||
instance.initSentry();
|
||||
expect(dotenvMock).toBeCalledTimes(1);
|
||||
expect(dotenvMock).toBeCalledWith({path: path.resolve(process.cwd(), '.env')})
|
||||
});
|
||||
it('does not post data to backend when done already', () => {
|
||||
const instance = new Analytics("v2.0.0-test1", null, {statSend: true, save: jest.fn()});
|
||||
instance.sendDownload().then(() => {
|
||||
expect(axios.post).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
it('updates the statSend setting after sending data', () => {
|
||||
const instance = new Analytics("v2.0.0-test1", null, {statSend: false, save: jest.fn()});
|
||||
instance.sendDownload().then(() => {
|
||||
expect(instance.settings.statSend).toBe(true);
|
||||
expect(instance.settings.save).toBeCalledTimes(1);
|
||||
});
|
||||
it("inits sentry in dev mode", () => {
|
||||
process.argv = ["", "", "--dev"];
|
||||
jest.spyOn(dotenv, 'config').mockImplementation(() => {});
|
||||
const instance = instanceBuiler();
|
||||
instance.initSentry();
|
||||
expect(Sentry.init).toBeCalledTimes(1);
|
||||
expect(Sentry.init.mock.calls[0][0].environment).toBe("development");
|
||||
});
|
||||
it("inits sentry in prod mode", () => {
|
||||
process.argv = ["", ""];
|
||||
jest.spyOn(dotenv, 'config').mockImplementation(() => {});
|
||||
const instance = instanceBuiler();
|
||||
instance.initSentry();
|
||||
expect(Sentry.init).toBeCalledTimes(1);
|
||||
expect(Sentry.init.mock.calls[0][0].environment).toBe("production");
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendReport', () => {
|
||||
it('posts data to the backend', () => {
|
||||
const instance = new Analytics("v2.0.0-test1", null, {statSend: true, save: jest.fn()});
|
||||
instance.sendReport({error: {}}).then(() => {
|
||||
expect(axios.post).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
it('returns the report id', () => {
|
||||
const testID = "test__id";
|
||||
const randomIDSpy = jest.spyOn(Utils, 'getRandomID').mockReturnValueOnce(testID)
|
||||
const instance = new Analytics("v2.0.0-test1", null, {statSend: true, save: jest.fn()});
|
||||
instance.sendReport({error: {}}).then((data) => {
|
||||
const instance = new Analytics();
|
||||
instance.sendReport(testID).then((data) => {
|
||||
expect(data).toBe(testID);
|
||||
});
|
||||
randomIDSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
function instanceBuiler(packaged) {
|
||||
const app = { isPackaged: packaged, getVersion: jest.fn()}
|
||||
return new Analytics(app);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const ErrorHandler = require("../modules/ErrorHandler");
|
||||
const Utils = require("../modules/Utils");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -54,17 +55,20 @@ describe('raiseUnhandledError', () => {
|
||||
expect(instance.queryManager.onError).not.toBeCalled();
|
||||
});
|
||||
it('adds the error to the unhandled error list', () => {
|
||||
const randomIDSpy = jest.spyOn(Utils, 'getRandomID').mockReturnValueOnce("12345678");
|
||||
const instance = instanceBuilder();
|
||||
instance.queryManager.getVideo.mockReturnValue({type: "single", identifier: "test__identifier"});
|
||||
instance.raiseUnhandledError("test__unhandled", "test__identifier");
|
||||
expect(instance.unhandledErrors).toContainEqual({
|
||||
identifier: "test__identifier",
|
||||
unexpected: true,
|
||||
error_id: "12345678",
|
||||
error: {
|
||||
code: "Unhandled exception",
|
||||
description: "test__unhandled",
|
||||
}
|
||||
});
|
||||
randomIDSpy.mockRestore();
|
||||
});
|
||||
it('sends the error to the renderer process', () => {
|
||||
const instance = instanceBuilder();
|
||||
|
||||
Reference in New Issue
Block a user