랩
이 절차는 New Relic 브라우저로 웹 앱의 문제를 해결하는 방법을 알려주는 랩의 일부입니다.
랩의 각 절차는 마지막 절차를 기반으로 하므로 이 절차를 시작하기 전에 마지막 절차인 브라우저 에이전트로 애플리케이션 계측을완료했는지 확인하십시오.
지금까지 애플리케이션이 제대로 작동했습니다. 사용자가 주문할 수 있었고 귀하의 서비스에 만족했습니다. 그러나 이제 애플리케이션에 약간의 통찰력이 있으므로 일부 JavaScript 오류가 표시되고 있음을 알 수 있습니다.

이 절차에서는 New Relic 브라우저를 사용하여 이러한 오류의 원인을 찾고 적시에 애플리케이션을 디버깅합니다.
프런트엔드 오류 디버그
안타까운 소식은 애플리케이션에 몇 가지 오류가 있음을 확인했다는 것입니다. 좋은 소식은 최근에 브라우저 에이전트로 애플리케이션을 계측했다는 것입니다! 아직 로그인하지 않은 경우 New Relic으로 이동하여 계정에 로그인합니다.
팁
데이터가 보이지 않습니까? 브라우저 모니터링을 활성화했고 부하 생성기가 실행 중인지 확인하십시오.
위의 오류 세부 정보를 보면 이제 서비스에 영향을 미치는 특정 오류를 알 수 있습니다. 그러나 여기에 표시된 스택 추적은 축소되었으며 이 오류의 원인을 이해하기 어렵습니다. 이를 이해하려면 소스 맵을 업로드하여 오류 축소를 해제해야 합니다.
JS 오류를 축소 해제하기 위해 소스 맵 업로드
축소된 JavaScript는 대부분 브라우저의 오류 페이지에서 이해하기 어렵고 쓸모 없는 스택 추적을 생성합니다. 소스 맵을 업로드하면 이러한 오류가 이해할 수 있는 스택 추적으로 변환됩니다. 또한 코드 라인에 대한 유용한 참조를 제공하고 디버깅을 더 쉽게 만듭니다. UI, API 또는 npm 모듈을 통해 소스 맵을 New Relic에 업로드할 수 있습니다.
여기서는 New Relic UI를 사용하여 소스 맵을 업로드하고 JS 오류를 축소 해제합니다.
find file 을 클릭합니다.

이렇게 하면 로컬 저장소에서 소스 맵을 업로드할 수 있는 파일 탐색기 창이 열립니다. 프로젝트의 build/static/js 디렉터리에서 소스 맵을 찾아 업로드합니다.
팁
소스 맵 파일의 파일 확장자는 .js.map 입니다. Relicstaurants는 소스 맵을 생성하도록 설정되어 있으며 build/static/js 디렉토리에서 찾을 수 있습니다. 프로젝트의 소스 맵을 생성하는 데 문제가 있는 경우 설명서에 따라 생성 방법을 알아보세요.
선택한 IDE에서 애플리케이션을 열고 src/components/layouts/app/app-container/header/app-container-header.js 파일로 이동합니다. 표시된 코드를 자세히 살펴보십시오.
import { Button, Drawer, Table } from 'antd';import Text from 'antd/lib/typography/Text';import { orderList } from 'atoms/order-list.atom';import { useState } from 'react';import { Link } from 'react-router-dom';import { useRecoilState } from 'recoil';import { Logo, StyledHeader } from './app-header-styled';import Navi from './navi-items';import { useNavigate } from 'react-router';
const Header = () => {  const [isSidebarVisible, setIsSidebarVisible] = useState(false);  const [orderListState, setOrderList] = useRecoilState(orderList);  const navigate = useNavigate();
  const onClose = () => {    setIsSidebarVisible(false);  };  const handleSidebarOpen = () => {    setIsSidebarVisible(true);  };
  const itemQuantity = (list) => {    let totalItemQuantity = 0;    list.forEach((item) => (totalItemQuantity += item.count));
    return totalItemQuantity;  };
  const handleDeleteItem = (clickedRow) => {    const reducedData = orderListState.filter((item) =>      item.name === clickedRow.name ? false : true    );    setOrderList(reducedData);  };
  const columns = [    {      title: 'Name',      dataIndex: 'name',      key: 'name',    },    {      title: 'Count',      dataIndex: 'count',      key: 'count',    },    {      title: 'Price',      dataIndex: 'price',      key: 'price',    },    {      title: 'Delete',      render: (clickedRow) => (        <Button onClick={() => handleDeleteItem(clickedRow)}>-</Button>      ),    },  ];
  return (    <StyledHeader>      <Link to="/">        <Logo>          <div>Relicstaurants</div>          <p>by New Relic</p>        </Logo>      </Link>      <Navi        sidebarVisible={handleSidebarOpen}        orderListLength={itemQuantity(orderListState)}      />      <Drawer        size="large"        title="Cart"        placement="right"        onClose={onClose}        visible={isSidebarVisible}      >        <Table          dataSource={orderListState}          columns={columns}          pagination={false}          summary={(pageData) => {            let totalPrice = 0;
            pageData.forEach(              ({ price, count }) => (totalPrice += price * count)            );
            return (              <>                <Table.Summary.Row>                  <Table.Summary.Cell colSpan={2}>Total</Table.Summary.Cell>                  <Table.Summary.Cell>                    <Text type="danger">{totalPrice.toFixed(2)}</Text>                  </Table.Summary.Cell>                </Table.Summary.Row>                <Table.Summary.Row>                  <Table.Summary.Cell colSpan={3}>                    <Button                      disabled={totalPrice > 0 ? false : true}                      primary                      onClick={() => {                        setOrderList([]);                        setIsSidebarVisible(false);                      }}                    >                      Clear Cart                    </Button>                  </Table.Summary.Cell>                  <Table.Summary.Cell>                    <Button                       id="pay"                       primary                       onClick={() => {                         if (!(totalPrice > 0)) {                           var err = new Error('Cart cannot be empty!');                           newrelic.noticeError(err);                            alert(err)                           navigate('/')                           setIsSidebarVisible(false);                         } else {                         navigate(`/payment`, { state: totalPrice });                         setIsSidebarVisible(false);                         }                       }}                     >                       PAY                     </Button>                   </Table.Summary.Cell>                </Table.Summary.Row>              </>            );          }}        />      </Drawer>    </StyledHeader>  );};
export default Header;여기서는 사용자가 실수로 빈 카트를 사용하여 결제하려고 할 때마다 Cart cannot be empty! 오류가 발생한다는 점에 유의하세요. 공지 최종 사용자에게 빈 장바구니로는 결제를 진행할 수 없다는 기능이 코딩되어 있습니다. 이제 이 오류가 서비스에 영향을 미치지 않는다는 것을 알게 되었습니다. 그러나 이러한 극단적인 경우를 처리하고 오류를 방지하는 더 나은 방법이 있습니다.
애플리케이션 제공을 중지하려면 애플리케이션을 실행 중인 터미널에서 Ctrl+C 누르세요. 다음과 같이 src/components/layouts/app/app-container/header/app-container-header.js를 업데이트합니다.
import { Button, Drawer, Table } from 'antd';import Text from 'antd/lib/typography/Text';import { orderList } from 'atoms/order-list.atom';import { Message } from 'components/common';import { useState } from 'react';import { Link } from 'react-router-dom';import { useRecoilState } from 'recoil';import { Logo, StyledHeader } from './app-header-styled';import Navi from './navi-items';import { useNavigate } from 'react-router';
const Header = () => {  const [isSidebarVisible, setIsSidebarVisible] = useState(false);  const [orderListState, setOrderList] = useRecoilState(orderList);  const navigate = useNavigate();
  const onClose = () => {    setIsSidebarVisible(false);  };  const handleSidebarOpen = () => {    setIsSidebarVisible(true);  };
  const itemQuantity = (list) => {    let totalItemQuantity = 0;    list.forEach((item) => (totalItemQuantity += item.count));
    return totalItemQuantity;  };
  const handleDeleteItem = (clickedRow) => {    const reducedData = orderListState.filter((item) =>      item.name === clickedRow.name ? false : true    );    setOrderList(reducedData);  };
  const columns = [    {      title: 'Name',      dataIndex: 'name',      key: 'name',    },    {      title: 'Count',      dataIndex: 'count',      key: 'count',    },    {      title: 'Price',      dataIndex: 'price',      key: 'price',    },    {      title: 'Delete',      render: (clickedRow) => (        <Button onClick={() => handleDeleteItem(clickedRow)}>-</Button>      ),    },  ];
  return (    <StyledHeader>      <Link to="/">        <Logo>          <div>Relicstaurants</div>          <p>by New Relic</p>        </Logo>      </Link>      <Navi        sidebarVisible={handleSidebarOpen}        orderListLength={itemQuantity(orderListState)}      />      <Drawer        size="large"        title="Cart"        placement="right"        onClose={onClose}        visible={isSidebarVisible}      >        {orderListState.length > 0 ? (          <Table            dataSource={orderListState}            columns={columns}            pagination={false}            summary={(pageData) => {              let totalPrice = 0;
              pageData.forEach(                ({ price, count }) => (totalPrice += price * count)              );
              return (                <>                  <Table.Summary.Row>                    <Table.Summary.Cell colSpan={2}>Total</Table.Summary.Cell>                    <Table.Summary.Cell>                      <Text type="danger">{totalPrice.toFixed(2)}</Text>                    </Table.Summary.Cell>                  </Table.Summary.Row>                  <Table.Summary.Row>                    <Table.Summary.Cell colSpan={3}>                      <Button                        disabled={totalPrice > 0 ? false : true}                        primary                        onClick={() => {                          setOrderList([]);                          setIsSidebarVisible(false);                        }}                      >                        Clear Cart                      </Button>                    </Table.Summary.Cell>                    <Table.Summary.Cell>                      <Button                        id="pay"                        disabled={totalPrice > 0 ? false : true}                        primary                        onClick={() => {                          navigate(`/payment`, { state: totalPrice });                          setIsSidebarVisible(false);                        }}                      >                        PAY                      </Button>                    </Table.Summary.Cell>                  </Table.Summary.Row>                </>              );            }}          />        ) : (          <Message>Nothing in cart</Message>        )}      </Drawer>    </StyledHeader>  );};
export default Header;여기서는 장바구니가 비어 있을 때 오류 대신 Nothing in cart 메시지를 표시하도록 파일을 수정했습니다. 최종 사용자가 장바구니에 항목을 담을 때까지 PAY 버튼은 비활성화된 상태로 유지됩니다.
애플리케이션 다시 시작
애플리케이션을 수정했으므로 이제 로컬 서버를 다시 시작할 차례입니다.
$npm run build$npm run newstart부하 생성기도 다시 시작하십시오.
$python3 simulator.py중요
올바른 터미널 창에서 이러한 명령을 실행하고 있는지 확인하십시오. 해당 창이 더 이상 없으면 설정 절차의 단계를 따르십시오.
부하 생성기가 New Relic으로 데이터를 전송하기 시작하면 애플리케이션에서 더 이상 JavaScript 오류를 보고하지 않는다는 점에 유의하십시오.

요약
요약하자면, 애플리케이션에서 오류를 발견하고 New Relic 브라우저를 사용하여 다음을 수행했습니다.
- 오류율 검토
 - 애플리케이션의 JS 오류 분석
 - 오류 인스턴스 이해
 - 소스 맵을 업로드하여 JS 오류 디버그
 
랩
이 절차는 New Relic 브라우저로 웹 앱의 문제를 해결하는 방법을 알려주는 랩의 일부입니다. 다음으로 애플리케이션에서 프런트엔드 속도 저하를 디버깅해보십시오.











