New Relic Vue.js統合ガイド - Vue.jsアプリケーションの効果的な監視
Vue.jsは、そのリアクティブデータシステムと柔軟なアーキテクチャにより、開発者に優れた体験を提供しますが、その独特なライフサイクルとデータフローは監視において特別な考慮が必要です。New Relic Browser AgentとVue.jsの統合により、リアクティブシステムの動作、コンポーネントのパフォーマンス、ユーザーインタラクションを詳細に追跡できます。
Vue.js監視の特徴
Vue.jsアプリケーションの監視では、そのアーキテクチャ固有の要素を理解することが重要です。
リアクティブシステムの監視
Vue.jsのリアクティブシステムは、データの変更を検知して自動的にDOMを更新しますが、この過程でのパフォーマンス影響を監視することが重要です。
データ変更の追跡により、プロパティの更新頻度と再レンダリングの関係を分析できます。ウォッチャーの効率性では、watch
やcomputed
プロパティの実行時間を測定して最適化の機会を特定します。
Vue Routerとの統合
Vue Routerを使用するアプリケーションでは、SPA特有のルーティング監視が必要です。
// Vue Router 4との統合
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/products', component: Products },
{ path: '/dashboard', component: Dashboard }
]
});
// ルート変更の監視
router.afterEach((to, from) => {
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vue_route_change', {
toPath: to.path,
toName: to.name,
fromPath: from.path,
fromName: from.name,
params: JSON.stringify(to.params),
query: JSON.stringify(to.query),
timestamp: Date.now()
});
}
});
export default router;
Vue.js プラグインとしての実装
New RelicをVue.jsプラグインとして実装することで、アプリケーション全体で一貫した監視を実現できます。
基本的なプラグイン実装
// new-relic-vue-plugin.js
const NewRelicVuePlugin = {
install(app, options = {}) {
// グローバルエラーハンドラー
app.config.errorHandler = (err, instance, info) => {
if (typeof newrelic !== 'undefined') {
newrelic.noticeError(err, {
vueComponent: instance?.$options.name || 'Anonymous',
vueInfo: info,
instanceData: JSON.stringify(instance?.$data || {}),
propsData: JSON.stringify(instance?.$props || {}),
vueVersion: app.version,
errorInfo: err.stack
});
}
// 開発環境でのログ出力
if (process.env.NODE_ENV === 'development') {
console.error('Vue Error Handler:', err);
console.error('Component Info:', info);
}
};
// グローバルプロパティの追加
app.config.globalProperties.$newrelic = {
trackEvent: (eventName, attributes = {}) => {
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction(eventName, {
...attributes,
timestamp: Date.now(),
userAgent: navigator.userAgent
});
}
},
trackError: (error, context = {}) => {
if (typeof newrelic !== 'undefined') {
newrelic.noticeError(error, context);
}
}
};
// Vue Router統合
if (options.router) {
options.router.afterEach((to, from) => {
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vue_navigation', {
to: to.path,
from: from.path,
name: to.name,
timestamp: Date.now()
});
}
});
}
// Vuex統合
if (options.store) {
options.store.subscribe((mutation, state) => {
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vuex_mutation', {
type: mutation.type,
payload: JSON.stringify(mutation.payload).slice(0, 200),
timestamp: Date.now()
});
}
});
}
}
};
export default NewRelicVuePlugin;
プラグインの使用
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import NewRelicVuePlugin from './plugins/new-relic-vue-plugin';
const app = createApp(App);
app.use(router);
app.use(store);
app.use(NewRelicVuePlugin, { router, store });
app.mount('#app');
Composition APIとの統合
Vue 3のComposition APIを活用して、コンポーネントレベルでの詳細な監視を実装します。
パフォーマンス追跡のComposable
// composables/useNewRelicTracking.js
import { onMounted, onUpdated, onUnmounted, ref, nextTick } from 'vue';
export function usePerformanceTracking(componentName) {
const renderCount = ref(0);
const mountTime = ref(null);
onMounted(() => {
mountTime.value = performance.now();
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vue_component_mounted', {
component: componentName,
mountTime: mountTime.value,
timestamp: Date.now()
});
}
});
onUpdated(() => {
renderCount.value++;
nextTick(() => {
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vue_component_updated', {
component: componentName,
renderCount: renderCount.value,
timeSinceMount: performance.now() - (mountTime.value || 0),
timestamp: Date.now()
});
}
});
});
onUnmounted(() => {
const lifeTime = performance.now() - (mountTime.value || 0);
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vue_component_unmounted', {
component: componentName,
lifeTime: lifeTime,
totalRenders: renderCount.value,
timestamp: Date.now()
});
}
});
return {
renderCount: renderCount.value
};
}
// API追跡のComposable
export function useApiTracking() {
const trackApiCall = async (apiName, apiFunction, context = {}) => {
const startTime = performance.now();
try {
const result = await apiFunction();
const duration = performance.now() - startTime;
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vue_api_success', {
apiName,
duration,
context: JSON.stringify(context),
resultSize: JSON.stringify(result).length,
timestamp: Date.now()
});
}
return result;
} catch (error) {
const duration = performance.now() - startTime;
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vue_api_error', {
apiName,
duration,
error: error.message,
context: JSON.stringify(context),
timestamp: Date.now()
});
newrelic.noticeError(error, {
apiCall: true,
apiName,
context
});
}
throw error;
}
};
return { trackApiCall };
}
// リアクティブデータの変更追跡
export function useReactivityTracking(watchTargets, componentName) {
const changeCount = ref(0);
watchEffect(() => {
changeCount.value++;
if (typeof newrelic !== 'undefined' && changeCount.value > 1) {
newrelic.addPageAction('vue_reactivity_change', {
component: componentName,
changeCount: changeCount.value,
watchTargetsCount: Object.keys(watchTargets).length,
timestamp: Date.now()
});
}
});
return { changeCount };
}
使用例
// ProductList.vue
<template>
<div>
<h2>商品一覧 (更新回数: {{ renderCount }})</h2>
<div v-if="loading">読み込み中...</div>
<div v-else>
<product-card
v-for="product in products"
:key="product.id"
:product="product"
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { usePerformanceTracking, useApiTracking, useReactivityTracking } from '@/composables/useNewRelicTracking';
const props = defineProps(['category']);
const products = ref([]);
const loading = ref(false);
// パフォーマンス追跡
const { renderCount } = usePerformanceTracking('ProductList');
// API追跡
const { trackApiCall } = useApiTracking();
// リアクティビティ追跡
const { changeCount } = useReactivityTracking(
{ products, loading, category: props.category },
'ProductList'
);
// データ取得
onMounted(async () => {
loading.value = true;
try {
const result = await trackApiCall(
'fetchProducts',
() => fetch(`/api/products?category=${props.category}`).then(r => r.json()),
{ category: props.category }
);
products.value = result;
} catch (error) {
console.error('商品取得エラー:', error);
} finally {
loading.value = false;
}
});
</script>
Vuex/Pinia状態管理との統合
Vue.jsの状態管理ライブラリとの統合により、アプリケーションの状態変更を詳細に監視できます。
Vuex統合
// store/index.js
import { createStore } from 'vuex';
const store = createStore({
state: {
user: null,
products: [],
cart: []
},
mutations: {
SET_USER(state, user) {
state.user = user;
},
ADD_TO_CART(state, product) {
state.cart.push(product);
}
},
actions: {
async fetchProducts({ commit }, category) {
const startTime = performance.now();
try {
const response = await fetch(`/api/products?category=${category}`);
const products = await response.json();
commit('SET_PRODUCTS', products);
const duration = performance.now() - startTime;
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vuex_action_success', {
action: 'fetchProducts',
duration,
category,
productCount: products.length,
timestamp: Date.now()
});
}
return products;
} catch (error) {
const duration = performance.now() - startTime;
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vuex_action_error', {
action: 'fetchProducts',
duration,
error: error.message,
category,
timestamp: Date.now()
});
}
throw error;
}
}
}
});
// Vuex プラグインでの監視
const newRelicPlugin = (store) => {
store.subscribe((mutation, state) => {
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vuex_mutation', {
type: mutation.type,
payload: JSON.stringify(mutation.payload).slice(0, 100),
stateSize: JSON.stringify(state).length,
timestamp: Date.now()
});
}
});
store.subscribeAction({
before: (action, state) => {
// アクション開始の記録
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vuex_action_start', {
type: action.type,
payload: JSON.stringify(action.payload).slice(0, 100),
timestamp: Date.now()
});
}
},
after: (action, state) => {
// アクション完了の記録
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('vuex_action_complete', {
type: action.type,
timestamp: Date.now()
});
}
}
});
};
store.plugins = [newRelicPlugin];
export default store;
Pinia統合
// stores/products.js
import { defineStore } from 'pinia';
export const useProductsStore = defineStore('products', {
state: () => ({
products: [],
loading: false,
error: null
}),
actions: {
async fetchProducts(category) {
const startTime = performance.now();
this.loading = true;
this.error = null;
try {
const response = await fetch(`/api/products?category=${category}`);
const products = await response.json();
this.products = products;
const duration = performance.now() - startTime;
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('pinia_action_success', {
store: 'products',
action: 'fetchProducts',
duration,
category,
productCount: products.length,
timestamp: Date.now()
});
}
} catch (error) {
this.error = error.message;
const duration = performance.now() - startTime;
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction('pinia_action_error', {
store: 'products',
action: 'fetchProducts',
duration,
error: error.message,
category,
timestamp: Date.now()
});
}
throw error;
} finally {
this.loading = false;
}
}
}
});
カスタムディレクティブでの監視
Vue.jsのカスタムディレクティブを使用して、DOM要素レベルでの監視を実装できます。
// directives/track.js
export const trackDirective = {
mounted(el, binding) {
const { value, arg, modifiers } = binding;
const trackEvent = (event) => {
if (typeof newrelic !== 'undefined') {
newrelic.addPageAction(`vue_directive_${arg || 'interaction'}`, {
element: el.tagName.toLowerCase(),
eventType: event.type,
value: typeof value === 'string' ? value : JSON.stringify(value),
modifiers: Object.keys(modifiers),
timestamp: Date.now()
});
}
};
// イベントタイプに応じたリスナー追加
if (modifiers.click) {
el.addEventListener('click', trackEvent);
}
if (modifiers.scroll) {
el.addEventListener('scroll', trackEvent);
}
if (modifiers.input) {
el.addEventListener('input', trackEvent);
}
// デフォルトはクリックイベント
if (!modifiers.click && !modifiers.scroll && !modifiers.input) {
el.addEventListener('click', trackEvent);
}
}
};
// 使用例
// <button v-track:button-click.click="{ action: 'purchase', product: product.id }">
// 購入する
// </button>
実装のベストプラクティス
Vue.js環境でのNew Relic監視を最適化するための推奨事項をまとめます。
パフォーマンス最適化
リアクティブシステムとの調和により、Vue.jsのリアクティブシステムに過度な負荷をかけないよう、監視データの収集と送信を適切に最適化します。
非同期処理の活用では、nextTick
を使用してDOM更新後のタイミングで監視データを収集し、レンダリングをブロックしないよう配慮します。
開発体験の向上
開発用と本番用の設定分離により、開発環境では詳細な監視を行い、本番環境では必要最小限のデータのみを収集します。
TypeScript統合では、Composition APIとTypeScriptを組み合わせて、型安全な監視コードを作成します。
まとめ
New RelicとVue.jsの統合により、リアクティブシステムの動作から状態管理、ユーザーインタラクションまで、Vue.jsアプリケーションの全体像を詳細に監視できるようになります。Composition APIとプラグインシステムを活用することで、保守性が高く効率的な監視システムを構築できます。
次のステップでは、Angularアプリケーションでの統合方法について詳しく解説します。Angular特有のアーキテクチャと監視手法を学んでいきましょう。
関連記事: Angular統合ガイド関連記事: React統合ガイド