File size: 2,420 Bytes
5c2ed06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
 * Crash logger
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * Logs crashes, sends an e-mail notification if you've set up
 * config.js to do that.
 *
 * @license MIT
 */

import * as fs from 'fs';
import * as path from 'path';

const CRASH_EMAIL_THROTTLE = 5 * 60 * 1000; // 5 minutes

const logPath = path.resolve(
	// not sure why this is necessary, but in Windows testing it was
	__dirname, '../', __dirname.includes(`${path.sep}dist${path.sep}`) ? '..' : '',
	path.join((global as any).Config?.logsdir || 'logs', 'errors.txt')
);
let lastCrashLog = 0;
let transport: any;

/**
 * Logs when a crash happens to console, then e-mails those who are configured
 * to receive them.
 */
export function crashlogger(
	error: unknown,
	description: string,
	data: AnyObject | null = null,
	emailConfig: AnyObject | null = null,
): string | null {
	const datenow = Date.now();

	let stack = (typeof error === 'string' ? error : (error as Error)?.stack) || '';
	if (data) {
		stack += `\n\nAdditional information:\n`;
		for (const k in data) {
			stack += `  ${k} = ${data[k]}\n`;
		}
	}

	console.error(`\nCRASH: ${stack}\n`);
	const out = fs.createWriteStream(logPath, { flags: 'a' });
	out.on('open', () => {
		out.write(`\n${stack}\n`);
		out.end();
	}).on('error', (err: Error) => {
		console.error(`\nSUBCRASH: ${err.stack}\n`);
	});

	const emailOpts = emailConfig || (global as any).Config?.crashguardemail;
	if (emailOpts && ((datenow - lastCrashLog) > CRASH_EMAIL_THROTTLE)) {
		lastCrashLog = datenow;

		if (!transport) {
			try {
				require.resolve('nodemailer');
			} catch {
				throw new Error(
					'nodemailer is not installed, but it is required if Config.crashguardemail is configured! ' +
					'Run npm install --no-save nodemailer and restart the server.'
				);
			}
		}

		let text = `${description} crashed `;
		if (transport) {
			text += `again with this stack trace:\n${stack}`;
		} else {
			try {
				transport = require('nodemailer').createTransport(emailOpts.options);
			} catch {
				throw new Error("Failed to start nodemailer; are you sure you've configured Config.crashguardemail correctly?");
			}

			text += `with this stack trace:\n${stack}`;
		}

		transport.sendMail({
			from: emailOpts.from,
			to: emailOpts.to,
			subject: emailOpts.subject,
			text,
		}, (err: Error | null) => {
			if (err) console.error(`Error sending email: ${err}`);
		});
	}

	return null;
}