Skip to content

Commit

Permalink
retrieve app logs from dev admin's memory storage (electrode-io#1713)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip authored and ImgBotApp committed Aug 20, 2020
1 parent 5e76e01 commit 4ba0888
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 191 deletions.
4 changes: 2 additions & 2 deletions packages/xarc-app-dev/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -505,8 +505,8 @@ sauce_connect*.txt
dist
tmp
.etmp
config
lib
/lib
/config
typedef.js

###############################################################################
Expand Down
5 changes: 3 additions & 2 deletions packages/xarc-app-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"config",
"dist",
"lib",
"src",
"require.js",
"scripts",
"typedef.js"
Expand All @@ -49,7 +50,7 @@
"@babel/preset-env": "^7.1.6",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"@jchip/redbird": "^1.1.0",
"@jchip/redbird": "^1.1.2",
"@loadable/babel-plugin": "^5.10.0",
"@xarc/webpack": "8.1.0",
"ansi-to-html": "^0.6.8",
Expand Down Expand Up @@ -94,7 +95,7 @@
"webpack-hot-middleware": "^2.25.0",
"winston": "^2.4.4",
"xaa": "^1.6.0",
"xclap": "^0.2.51",
"xclap": "^0.2.53",
"xenv-config": "^1.3.1",
"xsh": "^0.4.5"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/xarc-app-dev/src/config/archetype.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-var-requires, max-statements */

const Path = require("path");
const { merge } = require("lodash");
const { getXarcOptions, getMyPkg } = require("../lib/utils");
Expand Down
3 changes: 2 additions & 1 deletion packages/xarc-app-dev/src/config/get-dev-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ module.exports = function createDevProxy() {
webpackDevHost,
protocol,
elevated,
useDevProxy
useDevProxy,
devAdminPort: parseInt(process.env.ELECTRODE_ADMIN_PORT || "8991")
};

const adminPath = `/__proxy_admin`;
Expand Down
115 changes: 115 additions & 0 deletions packages/xarc-app-dev/src/lib/dev-admin/admin-http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* eslint-disable max-statements */

import * as http from "http";
import * as Path from "path";
import * as Fs from "fs";
import * as Url from "url";
import * as AnsiConvert from "ansi-to-html";
import { AdminServer } from "./admin-server";
import { getLogEventAsHtml } from "./log-parser";
import * as QS from "querystring";

export type AdminHttpOptions = {
port?: number;
admin: any;
};

export class AdminHttp {
_server: http.Server;
_admin: AdminServer;
_logHtml: string;
_adminHtml: string;
_port: number;
_instanceId: number;

constructor(options: AdminHttpOptions) {
this._server = http.createServer(this.requestListener.bind(this));
this._admin = options.admin;
this._port = options.port || 9001;
this._server.listen(this._port);
this._instanceId = Date.now();
}

_readAsset(filename: string, memoize: string): string {
if (!this[memoize]) {
this[memoize] = Fs.readFileSync(filename).toString();
}

return this[memoize];
}

_serveHtml(res: http.ServerResponse, content: string) {
res.setHeader("content-type", "text/html; charset=UTF-8");
res.writeHead(200);
res.end(content);
}

_serveLogs(url: Url.UrlWithStringQuery, res: http.ServerResponse) {
const logs = this._admin.getLogs("app");
const query: any = QS.parse(url.query);
const id = parseInt(query.id, 10);

let start = 0;

if (id === this._instanceId) {
start = parseInt(query.start, 10) || 0;
}

const htmlLogs = [];
for (let ix = start; ix < logs.length; ix++) {
const event: any = logs[ix];
const message = getLogEventAsHtml(event);
const record: any = {
level: event.level,
message
};
if (event.jsonData) {
record.json = true;
}
htmlLogs.push(record);
}

const data = {
start,
logs: htmlLogs,
total: logs.length,
instanceId: this._instanceId
};

res.setHeader("content-type", "application/json");
res.writeHead(200);
res.end(JSON.stringify(data));
}

requestListener(req: http.IncomingMessage, res: http.ServerResponse) {
const url = Url.parse(req.url);
switch (url.pathname) {
case "/":
case "/__electrode_dev":
return this._serveHtml(
res,
this._readAsset(Path.join(__dirname, "admin.html"), "_adminHtml")
);
case "/log-events":
case "/__electrode_dev/log-events":
return this._serveLogs(url, res);
case "/log":
case "/__electrode_dev/log":
return this._serveHtml(res, this._readAsset(Path.join(__dirname, "log.html"), "_logHtml"));
default:
res.writeHead(404);
return res.end("dev admin http 404 " + req.url);
}
}

/**
* Shutdown the http server
*/
shutdown() {
const server = this._server;
this._server = null;
if (server) {
server.close();
}
}
}
63 changes: 46 additions & 17 deletions packages/xarc-app-dev/src/lib/dev-admin/admin-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@ const boxen = require("boxen");
const ck = require("chalker");
const chokidar = require("chokidar");
const readline = require("readline");
const { parse: parseLog } = require("./log-parser");
import { parse as parseLog } from "./log-parser";
const WebpackDevRelay = require("./webpack-dev-relay");
const { displayLogs } = require("./log-reader");
const { fork } = require("child_process");
const ConsoleIO = require("./console-io");
const AutomationIO = require("./automation-io");
const winstonLogger = require("../winston-logger");
const winston = require("winston");
const logger = winstonLogger(winston, false);
const isCI = require("is-ci");
const { doCleanup } = require("./cleanup");
const xaa = require("xaa");
Expand All @@ -29,6 +25,8 @@ const {
controlPaths
} = require("../../config/dev-proxy");

import { AdminHttp } from "./admin-http";

const ADMIN_LOG_LEVEL = parseInt(adminLogLevel) || 0;

const APP_SERVER_NAME = "your app server";
Expand All @@ -51,7 +49,7 @@ const SERVER_ENVS = {
[PROXY_SERVER_NAME]: {}
};

class AdminServer {
export class AdminServer {
_opts: any;
_passThru: any;
_messageId: any;
Expand All @@ -73,6 +71,7 @@ class AdminServer {
_hideMenuTimer: any;
_appWatcher: any;
_startDefer: any;
_adminHttp: AdminHttp;

constructor(args, options) {
this._opts = args.opts;
Expand All @@ -95,6 +94,7 @@ class AdminServer {

this._shutdown = false;
this._fullAppLogUrl = formUrl({ ...fullDevServer, path: controlPaths.appLog });
this._adminHttp = new AdminHttp({ admin: this, port: this._opts.port });
}

async start() {
Expand Down Expand Up @@ -245,7 +245,9 @@ ${proxyItem}<magenta>M</> - Show this menu <magenta>Q</> - Shutdown
]);

this._io.shutdown();
const httpShutdown = this._adminHttp.shutdown();
await doCleanup();
await httpShutdown;
this._io.exit();
}

Expand Down Expand Up @@ -314,6 +316,7 @@ ${proxyItem}<magenta>M</> - Show this menu <magenta>Q</> - Shutdown
if (!this._servers[name]) this._servers[name] = { time: Date.now() };

const info = this._servers[name];

info.options = options;
info.name = name;
if (info._starting) {
Expand All @@ -327,7 +330,7 @@ ${proxyItem}<magenta>M</> - Show this menu <magenta>Q</> - Shutdown

// show Restarting or Starting message
const re = info._child ? "Res" : "S";
this._io.show(ck`<orange>${re}tarting ${name}${debugMsg}</orange>`);
this._io.show(ck`<orange>${re}tarting ${name}${debugMsg} - log tag:</orange> ${options.tag}`);
if (info._child) {
await this.kill(name, "SIGINT");
}
Expand All @@ -347,6 +350,7 @@ ${proxyItem}<magenta>M</> - Show this menu <magenta>Q</> - Shutdown
const forkOpts: any = {
env: Object.assign({}, process.env, {
ELECTRODE_ADMIN_SERVER: true,
ELECTRODE_ADMIN_PORT: this._adminHttp._port,
...SERVER_ENVS[name]
}),
silent: true
Expand Down Expand Up @@ -450,6 +454,7 @@ ${proxyItem}<magenta>M</> - Show this menu <magenta>Q</> - Shutdown

await this.startServer({
name: DEV_SERVER_NAME,
tag: this._wds,
killKey: "X",
exec: Path.join(__dirname, "dev-server.js"),
debug: debug || false,
Expand Down Expand Up @@ -559,22 +564,29 @@ ${instruction}`
const timeDiff = Date.now() - context._deferTimestamp;
// if an error line has been detected, then only consider other lines following it
// within 30 milliseconds as part of it.
if (context._deferTimer && timeDiff > 30) {
const continuation = timeDiff < 30;
if (context._deferTimer && !continuation) {
store.push(false);
}

const str = data.toString();
context.checkLine && context.checkLine(str);
if (!str.trim()) {
store.push("");
logger.info("");
store.push({ level: "info", message: "" });
} else {
const entry = parseLog(str.trimRight());
store.push(entry.json || entry.message);
// consider lines with at least two leading white spaces to be potential
// continuation of previous error/warning messages.
if (continuation && str.startsWith(" ")) {
const last = store[store.length - 1];
if (last && (last.level === "warn" || last.level === "error")) {
entry.level = last.level;
}
}
store.push(entry);
if (entry.show || this._appLogLevel === "all") {
this.deferLogsOutput(context, entry.show > 1);
}
logger[entry.level](str);
}
};

Expand Down Expand Up @@ -610,6 +622,7 @@ ${instruction}`

await this.startServer({
name: APP_SERVER_NAME,
tag: this._app,
debug: debug || false,
killKey: "K",
exec: this._opts.exec,
Expand All @@ -627,6 +640,26 @@ ${instruction}`
});
}

/**
* Get the logs of a server the admin manages.
* name could be:
*
* - "app" - app server
* - "wds" - webpack dev server
*
* @param name
* @returns logs in an array of strings
*/
getLogs(name: string): string[] {
const info = this.getServer({ app: APP_SERVER_NAME, wds: DEV_SERVER_NAME }[name]);
if (!info || !info.options) {
return [`server ${name} doesn't exist`];
}
const { logSaver } = info.options;

return logSaver.store;
}

passThruLineOutput(tag, input, output) {
const reader = readline.createInterface({ input });
let deferWrites;
Expand All @@ -645,6 +678,7 @@ ${instruction}`
async startProxyServer(debug = undefined) {
await this.startServer({
name: PROXY_SERVER_NAME,
tag: this._proxy,
killKey: "O",
debug,
exec: Path.join(__dirname, "redbird-spawn"),
Expand Down Expand Up @@ -727,10 +761,6 @@ ${info.name} - assuming it started.</>`);
return defer.promise;
}

displayLogs(maxLevel = 6) {
displayLogs(maxLevel);
}

//
// watches files change and restart a server
//
Expand All @@ -756,5 +786,4 @@ ${info.name} - assuming it started.</>`);
}
}

module.exports = AdminServer;
//
8 changes: 8 additions & 0 deletions packages/xarc-app-dev/src/lib/dev-admin/admin.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>xarc dev admin</title>
</head>
<body>
<h1>xarc dev admin</h1>
</body>
</html>
2 changes: 1 addition & 1 deletion packages/xarc-app-dev/src/lib/dev-admin/dev-express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function setup(app, protocol, port) {
.status(200)
.send(`<!DOCTYPE html>${html}`);
},
replyNotFound: () => res.status(404).send("Not Found"),
replyNotFound: () => res.status(404).send("dev server express Not Found"),
replyError: err => res.status(500).send(err),
replyStaticData: data => {
const type = mime.getType(req.url);
Expand Down
2 changes: 1 addition & 1 deletion packages/xarc-app-dev/src/lib/dev-admin/dev-koa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function setup(app, protocol, port) {
},
replyNotFound: () => {
res.status = 404;
res.body = "Not Found";
res.body = "dev server koa Not Found";
return res;
},
replyError: err => {
Expand Down
Loading

0 comments on commit 4ba0888

Please sign in to comment.