github.com/minio/console@v1.4.1/web-app/src/screens/Console/Logs/ErrorLogs/LogLine.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2022 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 import React, { Fragment, useState } from "react"; 17 import { DateTime } from "luxon"; 18 import { LogMessage } from "../types"; 19 import { 20 Box, 21 BoxArrowDown, 22 BoxArrowUp, 23 TableCell, 24 TableRow, 25 WarnFilledIcon, 26 } from "mds"; 27 28 import getByKey from "lodash/get"; 29 30 const timestampDisplayFmt = "HH:mm:ss ZZZZ MM/dd/yyyy"; //make this same as server logs format. 31 const messageForConsoleMsg = (log: LogMessage) => { 32 // regex for terminal colors like e.g. `[31;4m ` 33 const tColorRegex = /((\[[0-9;]+m))/g; 34 35 let fullMessage = log.ConsoleMsg; 36 // remove the 0x1B character 37 /* eslint-disable no-control-regex */ 38 fullMessage = fullMessage.replace(/\x1B/g, " "); 39 /* eslint-enable no-control-regex */ 40 // get substring if there was a match for to split what 41 // is going to be colored and what not, here we add color 42 // only to the first match. 43 fullMessage = fullMessage.replace(tColorRegex, ""); 44 return ( 45 <div 46 style={{ 47 display: "table", 48 tableLayout: "fixed", 49 width: "100%", 50 paddingLeft: 10, 51 paddingRight: 10, 52 }} 53 > 54 <div 55 style={{ 56 display: "table-cell", 57 whiteSpace: "nowrap", 58 textOverflow: "ellipsis", 59 overflowX: "auto", 60 }} 61 > 62 <pre>{fullMessage}</pre> 63 </div> 64 </div> 65 ); 66 }; 67 const messageForError = (log: LogMessage) => { 68 const dataStyle = { 69 color: "#C83B51", 70 fontWeight: 400, 71 fontFamily: "monospace", 72 fontSize: "12px", 73 }; 74 const labelStyle = { 75 fontFamily: "monospace", 76 fontSize: "12px", 77 }; 78 79 const getLogEntryKey = (keyPath: string) => { 80 return getByKey(log, keyPath, ""); 81 }; 82 83 const logTime = DateTime.fromFormat( 84 log.time.toString(), 85 "HH:mm:ss z MM/dd/yyyy", 86 { 87 zone: "UTC", 88 }, 89 ); 90 return ( 91 <Fragment> 92 <div> 93 <b style={labelStyle}>API: </b> 94 <span style={dataStyle}>{getLogEntryKey("api.name")}</span> 95 </div> 96 <div> 97 <b style={labelStyle}>Time: </b> 98 <span style={dataStyle}>{logTime.toFormat(timestampDisplayFmt)}</span> 99 </div> 100 <div> 101 <b style={labelStyle}>DeploymentID: </b> 102 <span style={dataStyle}>{getLogEntryKey("deploymentid")}</span> 103 </div> 104 <div> 105 <b style={labelStyle}>RequestID: </b> 106 <span style={dataStyle}>{getLogEntryKey("requestID")}</span> 107 </div> 108 <div> 109 <b style={labelStyle}>RemoteHost: </b> 110 <span style={dataStyle}>{getLogEntryKey("remotehost")}</span> 111 </div> 112 <div> 113 <b style={labelStyle}>UserAgent: </b> 114 <span style={dataStyle}>{getLogEntryKey("userAgent")}</span> 115 </div> 116 <div> 117 <b style={labelStyle}>Error: </b> 118 <span style={dataStyle}>{getLogEntryKey("error.message")}</span> 119 </div> 120 <br /> 121 <div> 122 <b style={labelStyle}>Backtrace: </b> 123 </div> 124 125 {(log.error.source || []).map((e: any, i: number) => { 126 return ( 127 <div> 128 <b style={labelStyle}>{i}: </b> 129 <span style={dataStyle}>{e}</span> 130 </div> 131 ); 132 })} 133 </Fragment> 134 ); 135 }; 136 137 const LogLine = (props: { log: LogMessage }) => { 138 const { log } = props; 139 const [open, setOpen] = useState<boolean>(false); 140 141 const getLogLineKey = (keyPath: string) => { 142 return getByKey(log, keyPath, ""); 143 }; 144 145 let logMessage = ""; 146 let consoleMsg = getLogLineKey("ConsoleMsg"); 147 let errMsg = getLogLineKey("error.message"); 148 if (consoleMsg !== "") { 149 logMessage = consoleMsg; 150 } else if (errMsg !== "") { 151 logMessage = errMsg; 152 } 153 // remove any non ascii characters, exclude any control codes 154 let titleLogMessage = (logMessage || "").replace(/━|┏|┓|┃|┗|┛/g, ""); 155 // remove any non ascii characters, exclude any control codes 156 titleLogMessage = titleLogMessage.replace(/([^\x20-\x7F])/g, ""); 157 158 // regex for terminal colors like e.g. `[31;4m ` 159 const tColorRegex = /((\[[0-9;]+m))/g; 160 161 let fullMessage = <Fragment />; 162 if (consoleMsg !== "") { 163 fullMessage = messageForConsoleMsg(log); 164 } else if (errMsg !== "") { 165 fullMessage = messageForError(log); 166 } 167 168 titleLogMessage = (titleLogMessage || "").replace(tColorRegex, ""); 169 170 const logTime = DateTime.fromFormat( 171 log.time.toString(), 172 "HH:mm:ss z MM/dd/yyyy", 173 { 174 zone: "UTC", 175 }, 176 ); 177 const dateOfLine = logTime.toJSDate(); //DateTime.fromJSDate(log.time); 178 179 let dateStr = <Fragment>{logTime.toFormat(timestampDisplayFmt)}</Fragment>; 180 181 if (dateOfLine.getFullYear() === 1) { 182 dateStr = <Fragment>n/a</Fragment>; 183 } 184 185 return ( 186 <React.Fragment key={logTime.toString()}> 187 <TableRow 188 sx={{ 189 cursor: "pointer", 190 borderLeft: "0", 191 borderRight: "0", 192 }} 193 > 194 <TableCell 195 onClick={() => setOpen(!open)} 196 sx={{ width: 280, color: "#989898", fontSize: 12 }} 197 > 198 <Box 199 sx={{ 200 display: "flex", 201 gap: 1, 202 alignItems: "center", 203 204 "& .min-icon": { width: 12, marginRight: 1 }, 205 fontWeight: "bold", 206 lineHeight: 1, 207 }} 208 > 209 <WarnFilledIcon /> 210 <div>{dateStr}</div> 211 </Box> 212 </TableCell> 213 <TableCell 214 onClick={() => setOpen(!open)} 215 sx={{ width: 200, color: "#989898", fontSize: 12 }} 216 > 217 <Box 218 sx={{ 219 "& .min-icon": { width: 12, marginRight: 1 }, 220 fontWeight: "bold", 221 lineHeight: 1, 222 }} 223 > 224 {log.errKind} 225 </Box> 226 </TableCell> 227 <TableCell onClick={() => setOpen(!open)}> 228 <Box 229 sx={{ 230 display: "table", 231 tableLayout: "fixed", 232 width: "100%", 233 paddingLeft: 10, 234 paddingRight: 10, 235 }} 236 > 237 <Box 238 sx={{ 239 display: "table-cell", 240 whiteSpace: "nowrap", 241 textOverflow: "ellipsis", 242 overflow: "hidden", 243 }} 244 > 245 {titleLogMessage} 246 </Box> 247 </Box> 248 </TableCell> 249 <TableCell onClick={() => setOpen(!open)} sx={{ width: 40 }}> 250 <Box 251 sx={{ 252 "& .min-icon": { 253 display: "flex", 254 alignItems: "center", 255 justifyContent: "center", 256 borderRadius: "2px", 257 }, 258 "&:hover .min-icon": { 259 fill: "#eaeaea", 260 }, 261 }} 262 > 263 {open ? <BoxArrowUp /> : <BoxArrowDown />} 264 </Box> 265 </TableCell> 266 </TableRow> 267 {open ? ( 268 <TableRow> 269 <TableCell 270 sx={{ 271 paddingBottom: 0, 272 paddingTop: 0, 273 width: 200, 274 textTransform: "uppercase", 275 verticalAlign: "top", 276 textAlign: "right", 277 color: "#8399AB", 278 fontWeight: "bold", 279 }} 280 > 281 <Box sx={{ marginTop: 10 }}>Log Details</Box> 282 </TableCell> 283 <TableCell sx={{ paddingBottom: 0, paddingTop: 0 }} colSpan={2}> 284 <Box 285 sx={{ 286 margin: 1, 287 padding: 4, 288 fontSize: 14, 289 }} 290 withBorders 291 useBackground 292 > 293 {fullMessage} 294 </Box> 295 </TableCell> 296 <TableCell sx={{ paddingBottom: 0, paddingTop: 0, width: 40 }} /> 297 </TableRow> 298 ) : null} 299 </React.Fragment> 300 ); 301 }; 302 303 export default LogLine;