Uses a similar approach as Trace and Console Logs by using websockets. It also includes the integration with the UI which needs 3 input fields that are sent as query parameters.
273 lines
7.6 KiB
TypeScript
273 lines
7.6 KiB
TypeScript
// This file is part of MinIO Console Server
|
|
// Copyright (c) 2020 MinIO, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
import React, { useEffect } from "react";
|
|
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
|
import { AppState } from "../../../store";
|
|
import { connect } from "react-redux";
|
|
import { logMessageReceived, logResetMessages } from "./actions";
|
|
import { LogMessage } from "./types";
|
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
|
import { timeFromDate } from "../../../common/utils";
|
|
import { isNullOrUndefined } from "util";
|
|
import { wsProtocol } from "../../../utils/wsUtils";
|
|
|
|
const styles = (theme: Theme) =>
|
|
createStyles({
|
|
logList: {
|
|
background: "white",
|
|
maxHeight: "400px",
|
|
overflow: "auto",
|
|
"& ul": {
|
|
margin: "4px",
|
|
padding: "0px"
|
|
},
|
|
"& ul li": {
|
|
listStyle: "none",
|
|
margin: "0px",
|
|
padding: "0px",
|
|
borderBottom: "1px solid #dedede"
|
|
}
|
|
},
|
|
tab: {
|
|
padding: "25px"
|
|
},
|
|
logerror: {
|
|
color: "#A52A2A"
|
|
},
|
|
logerror_tab: {
|
|
color: "#A52A2A",
|
|
padding: "25px"
|
|
},
|
|
ansidefault: {
|
|
color: "black"
|
|
}
|
|
});
|
|
|
|
interface ILogs {
|
|
classes: any;
|
|
logMessageReceived: typeof logMessageReceived;
|
|
logResetMessages: typeof logResetMessages;
|
|
messages: LogMessage[];
|
|
}
|
|
|
|
const Logs = ({
|
|
classes,
|
|
logMessageReceived,
|
|
logResetMessages,
|
|
messages
|
|
}: ILogs) => {
|
|
useEffect(() => {
|
|
logResetMessages();
|
|
const url = new URL(window.location.toString());
|
|
const isDev = process.env.NODE_ENV === "development";
|
|
const port = isDev ? "9090" : url.port;
|
|
|
|
const wsProt = wsProtocol(url.protocol);
|
|
|
|
const c = new W3CWebSocket(
|
|
`${wsProt}://${url.hostname}:${port}/ws/console`
|
|
);
|
|
|
|
let interval: any | null = null;
|
|
if (c !== null) {
|
|
c.onopen = () => {
|
|
console.log("WebSocket Client Connected");
|
|
c.send("ok");
|
|
interval = setInterval(() => {
|
|
c.send("ok");
|
|
}, 10 * 1000);
|
|
};
|
|
c.onmessage = (message: IMessageEvent) => {
|
|
// console.log(message.data.toString())
|
|
let m: LogMessage = JSON.parse(message.data.toString());
|
|
m.time = new Date(m.time.toString());
|
|
m.key = Math.random();
|
|
logMessageReceived(m);
|
|
};
|
|
c.onclose = () => {
|
|
clearInterval(interval);
|
|
console.log("connection closed by server");
|
|
};
|
|
return () => {
|
|
c.close(1000);
|
|
clearInterval(interval);
|
|
console.log("closing websockets");
|
|
};
|
|
}
|
|
}, [logMessageReceived]);
|
|
|
|
// replaces a character of a string with other at a given index
|
|
const replaceWeirdChar = (
|
|
origString: string,
|
|
replaceChar: string,
|
|
index: number
|
|
) => {
|
|
let firstPart = origString.substr(0, index);
|
|
let lastPart = origString.substr(index + 1);
|
|
|
|
let newString = firstPart + replaceChar + lastPart;
|
|
return newString;
|
|
};
|
|
|
|
const renderError = (logElement: LogMessage) => {
|
|
let errorElems = [];
|
|
if (!isNullOrUndefined(logElement.error)) {
|
|
if (logElement.api && logElement.api.name) {
|
|
errorElems.push(
|
|
<li key={`api-${logElement.key}`}>
|
|
<span className={classes.logerror}>API: {logElement.api.name}</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.time) {
|
|
errorElems.push(
|
|
<li key={`time-${logElement.key}`}>
|
|
<span className={classes.logerror}>
|
|
Time: {timeFromDate(logElement.time)}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.deploymentid) {
|
|
errorElems.push(
|
|
<li key={`deploytmentid-${logElement.key}`}>
|
|
<span className={classes.logerror}>
|
|
DeploymentID: {logElement.deploymentid}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.requestID) {
|
|
errorElems.push(
|
|
<li key={`requestid-${logElement.key}`}>
|
|
<span className={classes.logerror}>
|
|
RequestID: {logElement.requestID}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.remotehost) {
|
|
errorElems.push(
|
|
<li key={`remotehost-${logElement.key}`}>
|
|
<span className={classes.logerror}>
|
|
RemoteHost: {logElement.remotehost}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.host) {
|
|
errorElems.push(
|
|
<li key={`host-${logElement.key}`}>
|
|
<span className={classes.logerror}>Host: {logElement.host}</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.userAgent) {
|
|
errorElems.push(
|
|
<li key={`useragent-${logElement.key}`}>
|
|
<span className={classes.logerror}>
|
|
UserAgent: {logElement.userAgent}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.error.message) {
|
|
errorElems.push(
|
|
<li key={`message-${logElement.key}`}>
|
|
<span className={classes.logerror}>
|
|
Error: {logElement.error.message}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
if (logElement.error.source) {
|
|
// for all sources add padding
|
|
for (let s in logElement.error.source) {
|
|
errorElems.push(
|
|
<li key={`source-${logElement.key}-${s}`}>
|
|
<span className={classes.logerror_tab}>
|
|
{logElement.error.source[s]}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return errorElems;
|
|
};
|
|
|
|
const renderLog = (logElement: LogMessage) => {
|
|
let logMessage = logElement.ConsoleMsg;
|
|
// remove any non ascii characters, exclude any control codes
|
|
logMessage = logMessage.replace(/([^\x20-\x7F])/g, "");
|
|
|
|
// regex for terminal colors like e.g. `[31;4m `
|
|
const tColorRegex = /((\[[0-9;]+m))/g;
|
|
|
|
// get substring if there was a match for to split what
|
|
// is going to be colored and what not, here we add color
|
|
// only to the first match.
|
|
let substr = logMessage.replace(tColorRegex, "");
|
|
|
|
// if starts with multiple spaces add padding
|
|
if (substr.startsWith(" ")) {
|
|
return (
|
|
<li key={logElement.key}>
|
|
<span className={classes.tab}>
|
|
{substr}
|
|
</span>
|
|
</li>
|
|
);
|
|
} else if (!isNullOrUndefined(logElement.error)) {
|
|
// list error message and all sources and error elems
|
|
return renderError(logElement);
|
|
} else {
|
|
// for all remaining set default class
|
|
return (
|
|
<li key={logElement.key}>
|
|
<span className={classes.ansidefault}>
|
|
{substr}
|
|
</span>
|
|
</li>
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<h1>Logs</h1>
|
|
<div className={classes.logList}>
|
|
<ul>
|
|
{messages.map(m => {
|
|
return renderLog(m);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const mapState = (state: AppState) => ({
|
|
messages: state.logs.messages
|
|
});
|
|
|
|
const connector = connect(mapState, {
|
|
logMessageReceived: logMessageReceived,
|
|
logResetMessages: logResetMessages
|
|
});
|
|
|
|
export default connector(withStyles(styles)(Logs));
|