Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Merge pull request #195 from ferreira-tb/create-error-log
Browse files Browse the repository at this point in the history
feat: permite exportar o registro de erros
  • Loading branch information
ferreira-tb committed May 7, 2023
2 parents e61f348 + c7b0e27 commit a6a6865
Show file tree
Hide file tree
Showing 32 changed files with 273 additions and 165 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@
"vue/no-lone-template": "error",
"vue/this-in-template": ["error", "never"],

"vue/block-lang": ["error", { "script": { "lang": "ts" } }],
"vue/block-lang": ["error", { "script": { "lang": "ts" }, "style": { "lang": "scss" } }],
"vue/component-api-style": ["error", ["script-setup"]],
"vue/component-name-in-template-casing": ["error", "PascalCase"],
"vue/component-options-name-casing": ["error", "PascalCase"],
Expand Down
28 changes: 20 additions & 8 deletions electron/child-process/error.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { join } from 'node:path';
import { appendFile } from 'node:fs/promises';
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { AresError } from '$global/error';
import { isString } from '$global/guards';
import { ErrorLogFile } from '$global/constants';
import type { ElectronErrorLogType, OmitOptionalErrorLogProps } from '$types/error';

export class ChildProcessError extends AresError {
constructor(message: string) {
Expand All @@ -10,15 +13,24 @@ export class ChildProcessError extends AresError {

public static override async catch(err: unknown) {
if (!process.env.USER_DATA_PATH) return;
process.env.ARES_VERSION ??= 'unknown version';
process.env.ELECTRON_VERSION ??= 'unknown version';

if (err instanceof Error) {
const errorLog: OmitOptionalErrorLogProps<ElectronErrorLogType> = {
name: err.name,
message: err.message,
stack: isString(err.stack) ? err.stack : err.message,
time: Date.now(),
ares: process.env.ARES_VERSION ?? 'unknown',
chrome: process.env.CHROME_VERSION ?? 'unknown',
electron: process.env.ELECTRON_VERSION ??= 'unknown',
tribal: process.env.TRIBAL_WARS_VERSION ?? 'unknown',
locale: process.env.TRIBAL_WARS_LOCALE ?? 'unknown'
};

// Gera um arquivo de log com a data e a pilha de erros.
const date = new Date().toLocaleString('pt-br');
const logPath = join(process.env.USER_DATA_PATH, 'child-process-error.log');
const content = `${date}\nAres: ${process.env.ARES_VERSION} Electron: ${process.env.ELECTRON_VERSION}\n${err.stack}\n\n`;
await appendFile(logPath, content);
const content = this.generateLogContent(errorLog);
const logPath = path.join(process.env.USER_DATA_PATH, ErrorLogFile.ChildProcess);
await fs.appendFile(logPath, content, { encoding: 'utf-8' });
};
};
};
9 changes: 9 additions & 0 deletions electron/env.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { app } from 'electron';
import { storeToRefs, watch } from 'mechanus';
import { useAresStore } from '$electron/interface';

/** Define as variáveis de ambiente. */
export function setEnv() {
process.env.ARES_MODE = 'dev';
process.env.ARES_VERSION = app.getVersion();
process.env.CHROME_VERSION = process.versions.chrome;
process.env.ELECTRON_VERSION = process.versions.electron;
process.env.USER_DATA_PATH = app.getPath('userData');

const aresStore = useAresStore();
const { locale, majorVersion } = storeToRefs(aresStore);

watch(locale, (value) => (process.env.TRIBAL_WARS_LOCALE = value ?? 'unknown'));
watch(majorVersion, (value) => (process.env.TRIBAL_WARS_VERSION = value ?? 'unknown'));
};
23 changes: 19 additions & 4 deletions electron/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { app } from 'electron';
import { AresError } from '$global/error';
import { isString } from '$global/guards';
import { ErrorLogFile } from '$global/constants';
import type { ElectronErrorLogType, OmitOptionalErrorLogProps } from '$types/error';

export class MainProcessError extends AresError {
constructor(message: string) {
Expand All @@ -18,10 +21,22 @@ export class MainProcessError extends AresError {
/** Gera um arquivo de log com a data e a pilha de erros. */
public static async log(err: unknown) {
if (!(err instanceof Error)) return;
const date = new Date().toLocaleString('pt-br');
const logPath = path.join(app.getPath('userData'), 'ares-error.log');
const content = `${date}\nAres: ${app.getVersion()} Electron: ${process.versions.electron}\n${err.stack}\n\n`;
await fs.appendFile(logPath, content);

const errorLog: OmitOptionalErrorLogProps<ElectronErrorLogType> = {
name: err.name,
message: err.message,
stack: isString(err.stack) ? err.stack : null,
time: Date.now(),
ares: app.getVersion(),
chrome: process.versions.chrome,
electron: process.versions.electron,
tribal: process.env.TRIBAL_WARS_VERSION ?? 'unknown',
locale: process.env.TRIBAL_WARS_LOCALE ?? 'unknown'
};

const content = this.generateLogContent(errorLog);
const logPath = path.join(app.getPath('userData'), ErrorLogFile.Uncaught);
await fs.appendFile(logPath, content, { encoding: 'utf-8' });
};
};

Expand Down
77 changes: 57 additions & 20 deletions electron/events/error.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { URL } from 'url';
import { Op } from 'sequelize';
import { app, ipcMain, BrowserWindow } from 'electron';
import { app, dialog, ipcMain, BrowserWindow } from 'electron';
import { MainProcessEventError } from '$electron/error';
import { sequelize } from '$electron/database';
import { getActiveModule } from '$electron/app/modules';
import { ErrorLog, ElectronErrorLog, useAresStore } from '$electron/interface';
import type { ErrorLogBase, ErrorLogType } from '$types/error';
import { getMainWindow } from '$electron/utils/helpers';
import { ErrorLogFile } from '$global/constants';
import type { AllErrorLogTypes, ErrorLogBase, ErrorLogType, OmitOptionalErrorLogProps } from '$types/error';

export function setErrorEvents() {
const mainWindow = getMainWindow();
const aresStore = useAresStore();

ipcMain.on('error:create-log', async (e, error: ErrorLogBase) => {
ipcMain.on('error:create-log', async (e, error: OmitOptionalErrorLogProps<ErrorLogBase>) => {
try {
const errorLog: Omit<ErrorLogType, 'id' | 'pending'> = {
const errorLog: OmitOptionalErrorLogProps<ErrorLogType> = {
name: error.name,
message: error.message,
stack: error.stack,
Expand Down Expand Up @@ -49,7 +54,7 @@ export function setErrorEvents() {
await ErrorLog.destroy({ where: { time: { [Op.lte]: expiration } }, transaction });
});

const errors = await ErrorLog.findAll();
const errors = await ErrorLog.findAll({ where: { pending: true } });
return errors.map((error) => error.toJSON());

} catch (err) {
Expand All @@ -58,17 +63,6 @@ export function setErrorEvents() {
};
});

ipcMain.on('error:delete-log', async (_e, id: number) => {
try {
await sequelize.transaction(async (transaction) => {
await ErrorLog.destroy({ where: { id }, transaction });
});

} catch (err) {
MainProcessEventError.catch(err);
};
});

ipcMain.handle('error:get-electron-log', async () => {
try {
await sequelize.transaction(async (transaction) => {
Expand All @@ -77,7 +71,7 @@ export function setErrorEvents() {
await ElectronErrorLog.destroy({ where: { time: { [Op.lte]: expiration } }, transaction });
});

const errors = await ElectronErrorLog.findAll();
const errors = await ElectronErrorLog.findAll({ where: { pending: true } });
return errors.map((error) => error.toJSON());

} catch (err) {
Expand All @@ -86,14 +80,57 @@ export function setErrorEvents() {
};
});

ipcMain.on('error:delete-electron-log', async (_e, id: number) => {
ipcMain.handle('error:export', async (): Promise<'canceled' | 'error' | 'sucess'> => {
try {
const normalErrors = await ErrorLog.findAll({ where: { pending: true } });
const electronErrors = await ElectronErrorLog.findAll({ where: { pending: true } });
const errors = [...normalErrors, ...electronErrors].sort((a, b) => a.time - b.time);
if (errors.length === 0) return 'canceled';

const asJson = errors.map((err) => err.toJSON()) as AllErrorLogTypes[];
let content = MainProcessEventError.generateLogContent(asJson);

const userData = app.getPath('userData');
const uncaughtLogFilePath = path.join(userData, ErrorLogFile.Uncaught);
content = await consumeLogFile(uncaughtLogFilePath, content);

const childProcessFilePath = path.join(userData, ErrorLogFile.ChildProcess);
content = await consumeLogFile(childProcessFilePath, content);

const mergedLogPath = path.join(userData, ErrorLogFile.All);
await fs.appendFile(mergedLogPath, content, { encoding: 'utf-8' });

const defaultPath = `ares-error-log-${Date.now()}.log`;
const { canceled, filePath: savePath } = await dialog.showSaveDialog(mainWindow, { defaultPath });

if (canceled) {
return 'canceled';
} else if (!savePath) {
throw new MainProcessEventError('Could not export error log.');
};

await fs.writeFile(savePath, content, { encoding: 'utf-8' });

await sequelize.transaction(async (transaction) => {
await ElectronErrorLog.destroy({ where: { id }, transaction });
await ErrorLog.update({ pending: false }, { fields: ['pending'], where: { pending: true }, transaction });
await ElectronErrorLog.update({ pending: false }, { fields: ['pending'], where: { pending: true }, transaction });
});


return 'sucess';

} catch (err) {
MainProcessEventError.catch(err);
return 'error';
};
});
};

async function consumeLogFile(filePath: string, currentContent: string) {
try {
const newContent = await fs.readFile(filePath, { encoding: 'utf-8' });
queueMicrotask(() => fs.rm(filePath).catch(MainProcessEventError.catch));
return currentContent + newContent;
} catch {
return currentContent;
};
};
13 changes: 6 additions & 7 deletions electron/interface/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@ import { sequelize } from '$electron/database';
import { getActiveModule } from '$electron/app/modules';
import { getMainWindow } from '$electron/utils/helpers';
import { MainProcessError } from '$electron/error';
import type { ElectronErrorLogBase } from '$types/error';
import type { useAresStore, useAppNotificationsStore } from '$electron/interface';
import type { ElectronErrorLogType, OmitOptionalErrorLogProps } from '$types/error';
import type { useAppNotificationsStore } from '$electron/interface';
import type { ElectronErrorLog as ElectronErrorLogTable } from '$electron/interface';

export function catchError(
aresStore: ReturnType<typeof useAresStore>,
appNotificationsStore: ReturnType<typeof useAppNotificationsStore>,
ElectronErrorLog: typeof ElectronErrorLogTable
) {
const { notifyOnError } = storeToRefs(appNotificationsStore);
return async function(err: unknown) {
if (!(err instanceof Error)) return;
try {
const errorLog: ElectronErrorLogBase = {
const errorLog: OmitOptionalErrorLogProps<ElectronErrorLogType> = {
name: err.name,
message: err.message,
stack: isString(err.stack) ? err.stack : null,
stack: isString(err.stack) ? err.stack : err.message,
time: Date.now(),
ares: app.getVersion(),
chrome: process.versions.chrome,
electron: process.versions.electron,
tribal: aresStore.majorVersion,
locale: aresStore.locale
tribal: process.env.TRIBAL_WARS_VERSION ?? 'unknown',
locale: process.env.TRIBAL_WARS_LOCALE ?? 'unknown'
};

await sequelize.transaction(async (transaction) => {
Expand Down
2 changes: 1 addition & 1 deletion electron/interface/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const aliasArgs = [
] as const;

// ERROS
MainProcessError.catch = catchError(useAresStore(), useAppNotificationsStore(), ElectronErrorLog);
MainProcessError.catch = catchError(useAppNotificationsStore(), ElectronErrorLog);

// WATCHERS
// Essas funções retornam outras funções, que, por sua vez, são usadas como callbacks.
Expand Down
1 change: 1 addition & 0 deletions electron/utils/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { GameRegion } from '$types/game';
export const getMainViewWebContents = () => {
const id = Number.parseIntStrict(process.env.MAIN_VIEW_WEB_CONTENTS_ID ?? '');
const mainViewWebContents = webContents.fromId(id);
if (!mainViewWebContents) throw new BrowserViewError('Could not get main view web contents.');
return mainViewWebContents;
};

Expand Down
6 changes: 6 additions & 0 deletions global/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export const enum Kronos {
Month = 30 * Day
};

export const enum ErrorLogFile {
All = 'error.log',
ChildProcess = 'child-process-error.log',
Uncaught = 'uncaught-error.log'
};

// Jogo.
export const resources = ['wood', 'stone', 'iron'] as const;
export const farmUnits = ['spear', 'sword', 'axe', 'archer', 'spy', 'light', 'marcher', 'heavy', 'knight'] as const;
Expand Down
16 changes: 16 additions & 0 deletions global/error.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import type { AllErrorLogTypes } from '$types/error';

export class AresError extends Error {
declare public static catch: (err: unknown) => Promise<void>;

constructor(message: string) {
super(message);
this.name = 'AresError';
};

public static generateLogContent(err: AllErrorLogTypes | AllErrorLogTypes[]) {
const errors = Array.isArray(err) ? err : [err];

let content = '';
for (const error of errors) {
const date = new Date(error.time).toLocaleString('pt-br');
content += `${date}\nAres: ${error.ares} Electron: ${error.electron} Chrome: ${error.chrome}\n`;
content += `Tribal Wars: ${error.tribal ?? 'unknown'} Locale: ${error.locale ?? 'unknown'}\n`;
content += `${error.stack ?? error.message}\n\n`;
};

return content;
};
};
21 changes: 19 additions & 2 deletions modules/assets/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ html {
user-select: none;
}

.module-content {
@mixin module-tabbed-view-wrapper($margin-bottom: 0) {
position: absolute;
top: 30px;
bottom: 0;
width: 100%;
padding-left: 0.3em;
padding-right: 0.3em;
margin-top: 0.5em;
overflow: hidden;
user-select: none;

@if $margin-bottom > 0 {
margin-bottom: $margin-bottom;
}
}

.module-view {
Expand All @@ -35,4 +38,18 @@ html {
overflow-x: hidden;
overflow-y: auto;
user-select: none;
}

.config-divider {
margin-top: 1em !important;
margin-bottom: 0.5em !important;
}

.config-divider:first-of-type {
margin-top: 0.3em !important;
}

.error-log {
margin-bottom: 0.5em;
user-select: text;
}
6 changes: 1 addition & 5 deletions modules/components/ConfigPlunderGridAttack.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,4 @@ const blindAttackOptions = [
</NGridItem>
</NGrid>
</div>
</template>

<style scoped>
</style>
</template>
Loading

0 comments on commit a6a6865

Please sign in to comment.