Skip to content

Commit 7cc7cea

Browse files
committed
feat: write combined server and client logs to the console and a file
Signed-off-by: Nick Hale <[email protected]>
1 parent 56cadbd commit 7cc7cea

File tree

9 files changed

+131
-58
lines changed

9 files changed

+131
-58
lines changed

app/error.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default function Error({
66
error,
77
reset,
88
}: {
9-
error: Error;
9+
error: Error & { digest?: string };
1010
reset: () => void;
1111
}) {
1212
useEffect(() => {

contexts/script.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ const ScriptContextProvider: React.FC<ScriptContextProps> = ({
221221
};
222222

223223
const restartScript = useCallback(
224-
// This is debonced as allowing the user to spam the restart button can cause race
224+
// This is debounced as allowing the user to spam the restart button can cause race
225225
// conditions. In particular, the restart may not be processed correctly and can
226226
// get the user into a state where no run has been sent to the server.
227227
debounce(async () => {

electron/config.mjs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { app } from 'electron';
2+
import { getPort } from 'get-port-please';
3+
import { join, dirname, parse } from 'path';
4+
import log from 'electron-log/main.js';
5+
import util from 'util';
6+
import { renameSync } from 'fs';
7+
8+
// App config
9+
const dev = !app.isPackaged;
10+
const appName = 'Acorn';
11+
const appDir = app.getAppPath();
12+
const logsDir = app.getPath('logs');
13+
const resourcesDir = dirname(appDir);
14+
const dataDir = join(app.getPath('userData'), appName);
15+
const threadsDir = process.env.THREADS_DIR || join(dataDir, 'threads');
16+
const workspaceDir = process.env.WORKSPACE_DIR || join(dataDir, 'workspace');
17+
const port =
18+
process.env.PORT || !dev
19+
? await getPort({ portRange: [30000, 40000] })
20+
: 3000;
21+
const gptscriptBin =
22+
process.env.GPTSCRIPT_BIN ||
23+
join(
24+
dev ? join(resourcesDir, 'app.asar.unpacked') : '',
25+
'node_modules',
26+
'@gptscript-ai',
27+
'gptscript',
28+
'bin',
29+
`gptscript${process.platform === 'win32' ? '.exe' : ''}`
30+
);
31+
const gatewayUrl =
32+
process.env.GPTSCRIPT_GATEWAY_URL || 'https://gateway-api.gptscript.ai';
33+
34+
// Logging config
35+
const logFormat = ({ data, level, message }) => [
36+
message.date.toISOString(),
37+
`[${message.variables.processType === 'main' ? 'server' : 'client'}]`,
38+
`[${level.toUpperCase()}]`,
39+
util.format(...data),
40+
];
41+
42+
log.transports.console.format = logFormat;
43+
44+
Object.assign(log.transports.file, {
45+
format: logFormat,
46+
resolvePathFn: (variables) => {
47+
return join(logsDir, `${variables.appName}.log`);
48+
},
49+
archiveLogFn: (file) => {
50+
// Get the current Unix timestamp
51+
const info = parse(file.toString());
52+
const timestamp = Math.floor(Date.now() / 1000);
53+
54+
try {
55+
renameSync(file, join(info.dir, `${info.name}.${timestamp}.${info.ext}`));
56+
} catch (e) {
57+
console.warn('failed to rotate log file', e);
58+
}
59+
},
60+
});
61+
62+
log.initialize({
63+
// Include logs gathered from clients via IPC
64+
spyRendererConsole: true,
65+
includeFutureSessions: true,
66+
});
67+
68+
// Forward default console logging to electron-log
69+
Object.assign(console, log.functions);
70+
71+
export const config = {
72+
dev,
73+
appName,
74+
logsDir,
75+
appDir,
76+
resourcesDir,
77+
dataDir,
78+
threadsDir,
79+
workspaceDir,
80+
port,
81+
gptscriptBin,
82+
gatewayUrl,
83+
};

electron/main.mjs

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,43 @@
11
import { app, shell, BrowserWindow } from 'electron';
2-
import { getPort } from 'get-port-please';
32
import { startAppServer } from '../server/app.mjs';
43
import { join, dirname } from 'path';
54
import { existsSync, mkdirSync, writeFileSync } from 'fs';
65
import fixPath from 'fix-path';
76
import os from 'os';
7+
import { config } from './config.mjs';
88

9-
const appName = 'Acorn';
10-
const gatewayUrl =
11-
process.env.GPTSCRIPT_GATEWAY_URL || 'https://gateway-api.gptscript.ai';
12-
const resourcesDir = dirname(app.getAppPath());
13-
const dataDir = getDataDir(appName);
14-
15-
function getDataDir(appName) {
16-
const userDataPath = app.getPath('userData');
17-
return join(userDataPath, appName);
18-
}
19-
20-
function ensureDirExists(dir) {
21-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
22-
}
9+
app.on('window-all-closed', () => app.quit());
10+
app.on('ready', () => {
11+
startServer(app.isPackaged);
12+
});
2313

2414
async function startServer(isPackaged) {
25-
const port = isPackaged
26-
? await getPort({ portRange: [30000, 40000] })
27-
: process.env.PORT || 3000;
28-
const gptscriptBin = join(
29-
isPackaged ? join(resourcesDir, 'app.asar.unpacked') : '',
30-
'node_modules',
31-
'@gptscript-ai',
32-
'gptscript',
33-
'bin',
34-
`gptscript${process.platform === 'win32' ? '.exe' : ''}`
35-
);
36-
37-
process.env.GPTSCRIPT_BIN = process.env.GPTSCRIPT_BIN || gptscriptBin;
38-
process.env.THREADS_DIR = process.env.THREADS_DIR || join(dataDir, 'threads');
39-
process.env.WORKSPACE_DIR =
40-
process.env.WORKSPACE_DIR || join(dataDir, 'workspace');
41-
process.env.GPTSCRIPT_GATEWAY_URL =
42-
process.env.GPTSCRIPT_GATEWAY_URL || gatewayUrl;
43-
process.env.GPTSCRIPT_OPENAPI_REVAMP = 'true';
15+
// Fix path so that tools can find binaries installed on the system.
16+
fixPath();
4417

45-
console.log(
46-
`Starting app server with GPTSCRIPT_BIN="${process.env.GPTSCRIPT_BIN}"`
47-
);
18+
// Ensure the app's data directory exists
19+
ensureDirExists(config.dataDir);
4820

4921
// Set up the browser tool to run in headless mode.
50-
ensureDirExists(process.env.WORKSPACE_DIR);
22+
ensureDirExists(config.workspaceDir);
5123
writeFileSync(
52-
`${process.env.WORKSPACE_DIR}/browsersettings.json`,
24+
`${config.workspaceDir}/browsersettings.json`,
5325
JSON.stringify({ headless: true })
5426
);
5527

28+
// Project config onto environment variables to configure GPTScript/sdk-server and the Next.js app.
29+
process.env.GPTSCRIPT_BIN = config.gptscriptBin;
30+
process.env.THREADS_DIR = config.threadsDir;
31+
process.env.WORKSPACE_DIR = config.workspaceDir;
32+
process.env.GPTSCRIPT_GATEWAY_URL = config.gatewayUrl;
33+
process.env.GPTSCRIPT_OPENAPI_REVAMP = 'true';
34+
5635
try {
5736
const url = await startAppServer({
58-
dev: !isPackaged,
37+
dev: config.dev,
5938
hostname: 'localhost',
60-
port,
61-
dir: app.getAppPath(),
39+
port: config.port,
40+
appDir: config.appDir,
6241
});
6342
console.log(`> ${isPackaged ? '' : 'Dev '}Electron app started at ${url}`);
6443
createWindow(url);
@@ -73,9 +52,9 @@ function createWindow(url) {
7352
const win = new BrowserWindow({
7453
width: 1024,
7554
height: 720,
76-
frame: isMac ? false : true, // Use frame: true for Windows and Linux
55+
frame: !isMac,
7756
webPreferences: {
78-
preload: join(app.getAppPath(), 'electron/preload.js'),
57+
preload: join(config.appDir, 'electron/preload.mjs'),
7958
nodeIntegration: true,
8059
allowRunningInsecureContent: true,
8160
webSecurity: false,
@@ -88,7 +67,7 @@ function createWindow(url) {
8867
win.setWindowButtonVisibility(true);
8968
}
9069

91-
// Open the NextJS app
70+
// Open the Next.js app
9271
win.loadURL(url);
9372

9473
win.webContents.on('did-fail-load', () =>
@@ -108,10 +87,6 @@ function createWindow(url) {
10887
});
10988
}
11089

111-
app.on('ready', () => {
112-
fixPath();
113-
ensureDirExists(dataDir);
114-
startServer(app.isPackaged);
115-
});
116-
117-
app.on('window-all-closed', () => app.quit());
90+
function ensureDirExists(dir) {
91+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
92+
}

electron/preload.js renamed to electron/preload.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { contextBridge, ipcRenderer } from 'electron';
22

3+
// Import electron-log preload injection to enable log forwarding from clients (i.e. the renderer process) to the server
4+
// (i.e. the main process).
5+
import 'electron-log/preload.js';
6+
37
contextBridge.exposeInMainWorld('electronAPI', {
48
on: (channel, callback) => {
59
ipcRenderer.on(channel, callback);

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"autoprefixer": "10.4.19",
4545
"clsx": "^2.0.0",
4646
"dotenv": "^16.4.5",
47+
"electron-log": "^5.1.7",
4748
"eslint-config-next": "14.2.1",
4849
"fix-path": "^4.0.0",
4950
"framer-motion": "^11.1.1",

server.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ dotenv.config({ path: ['.env', '.env.local'] });
99
const dev = process.env.NODE_ENV !== 'production';
1010
const hostname = 'localhost';
1111
const port = parseInt(process.env.GPTSCRIPT_PORT ?? '3000');
12-
const dir = dirname(fileURLToPath(import.meta.url));
12+
const appDir = dirname(fileURLToPath(import.meta.url));
1313
const runFile = process.env.UI_RUN_FILE;
14-
startAppServer({ dev, hostname, port, dir })
14+
startAppServer({ dev, hostname, port, appDir })
1515
.then((address) => {
1616
let landingPage = address;
1717
if (runFile) {

server/app.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ let serverRunning = false;
1818
let gptscriptInitialized = false;
1919
let gptscriptInitPromise = null;
2020

21-
export const startAppServer = ({ dev, hostname, port, dir }) => {
21+
export const startAppServer = ({ dev, hostname, port, appDir }) => {
2222
const address = `http://${hostname}:${port ?? 3000}`;
2323

2424
return new Promise((resolve, reject) => {
@@ -32,7 +32,7 @@ export const startAppServer = ({ dev, hostname, port, dir }) => {
3232
hostname: hostname,
3333
port: port,
3434
conf: nextConfig,
35-
dir: dir,
35+
dir: appDir,
3636
customServer: true,
3737
});
3838
const handler = app.getRequestHandler();

0 commit comments

Comments
 (0)