ラボ
この手順は、New Relic ブラウザーを使用して Web アプリをトラブルシューティングする方法を説明するラボの一部です。
ラボの各手順は前回の手順に基づいているため、この手順を開始する前に、最後の手順で あるブラウザ エージェントを使用してアプリケーションをインストルメント化するを完了していることを確認してください。
これまで、アプリケーションは正常に動作していました。ユーザーは注文を行うことができ、サービスに満足しました。しかし、アプリケーションでいくつかの洞察を得たので、いくつかの JavaScript エラーが表示されていることに気付きました。
この手順では、New Relic ブラウザーを使用してこれらのエラーの原因を突き止め、アプリケーションをタイムリーにデバッグします。
フロントエンドエラーをデバッグする
悪いニュースは、アプリケーションにいくつかのエラーがあることを確認したことです。良いニュースは、最近、アプリケーションに当社のブラウザー エージェントを組み込んだことです。アカウントにまだサインインしていない場合は、New Relic に移動してサインインします。
ヒント
データが表示されませんか?ブラウザーの監視が有効になっており、Load Generator が実行されていることを確認してください。
上記のエラーの詳細を見ると、サービスに影響を与える特定のエラーがわかります。ただし、ここに示すスタック トレースは縮小されているため、このエラーの原因を理解するのは困難です。これを理解するには、ソース マップをアップロードしてエラーの縮小を解除する必要があります。
ソース マップをアップロードして 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
Load Generator も再起動します。
$python3 simulator.py
重要
これらのコマンドを正しいターミナル ウィンドウで実行していることを確認してください。これらのウィンドウが表示されなくなった場合は、 セットアップ手順の手順に従ってください。
Load Generator が New Relic へのデータ送信を開始すると、アプリケーションが JavaScript エラーを報告しなくなったことに注意してください。
概要
要約すると、アプリケーションでエラーを確認し、New Relic ブラウザーを使用して次のことを行いました。
- エラー率を確認する
- アプリケーションの JS エラーを分析する
- エラーインスタンスを理解する
- ソース マップをアップロードして JS エラーをデバッグする
ラボ
この手順は、New Relic ブラウザーを使用して Web アプリのトラブルシューティングを行う方法を説明するラボの一部です。次に、 アプリケーションのフロントエンドの遅さをデバッグしてみてください。