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:&nbsp;</b>
    94          <span style={dataStyle}>{getLogEntryKey("api.name")}</span>
    95        </div>
    96        <div>
    97          <b style={labelStyle}>Time:&nbsp;</b>
    98          <span style={dataStyle}>{logTime.toFormat(timestampDisplayFmt)}</span>
    99        </div>
   100        <div>
   101          <b style={labelStyle}>DeploymentID:&nbsp;</b>
   102          <span style={dataStyle}>{getLogEntryKey("deploymentid")}</span>
   103        </div>
   104        <div>
   105          <b style={labelStyle}>RequestID:&nbsp;</b>
   106          <span style={dataStyle}>{getLogEntryKey("requestID")}</span>
   107        </div>
   108        <div>
   109          <b style={labelStyle}>RemoteHost:&nbsp;</b>
   110          <span style={dataStyle}>{getLogEntryKey("remotehost")}</span>
   111        </div>
   112        <div>
   113          <b style={labelStyle}>UserAgent:&nbsp;</b>
   114          <span style={dataStyle}>{getLogEntryKey("userAgent")}</span>
   115        </div>
   116        <div>
   117          <b style={labelStyle}>Error:&nbsp;</b>
   118          <span style={dataStyle}>{getLogEntryKey("error.message")}</span>
   119        </div>
   120        <br />
   121        <div>
   122          <b style={labelStyle}>Backtrace:&nbsp;</b>
   123        </div>
   124  
   125        {(log.error.source || []).map((e: any, i: number) => {
   126          return (
   127            <div>
   128              <b style={labelStyle}>{i}:&nbsp;</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;