بازگشت به بلاگ
۲۲ مرداد ۱۴۰۵Sergei Solod17 دقیقه مطالعه

برای همه‌چیز هشدار می‌گرفتم: چگونه نویز خطاهای مرورگر را به پایش مفید محیط تولید تبدیل کردم

گزارشگر فرانت‌اند من مسدود شدن تبلیغات، خرابی GTM، خطای AbortError، پیام مبهم Script error و خرابی واقعی چانک‌های Next.js را یکسان می‌دید. پایش را بر اساس مالکیت خطا، اثر بر کاربر، کیفیت شواهد، هم‌بستگی رویدادها و بازیابی بازطراحی کردم.

پایش خطاهای مرورگرمشاهده‌پذیری فرانت‌اندخطاهای JavaScriptپایش محیط تولیدNext.jsکارایی وب

گزارش خطای سمت کاربر را ساختم چون می‌خواستم مشکلات کاربران واقعی را ببینم؛ مخصوصاً خطاهایی که بازتولیدشان در محیط محلی آسان نبود. گزارشگر دقیقاً همان کاری را می‌کرد که از آن خواسته بودم: خطاها را می‌گرفت و برای من می‌فرستاد.

مشکل این بود که تقریباً همه‌چیز به یک اندازه مهم به نظر می‌رسید.

اسکریپت تحلیل‌گری یک سرویس خارجی لود نشد؟ هشدار قرمز. اسکریپت تبلیغاتی مسدود شد؟ هشدار قرمز. یک خزشگر نتوانست Google Analytics را لود کند؟ هشدار قرمز. پیش‌نمایش ویدئو play() را صدا زد و قبل از کامل شدن Promise متوقف شد؟ هشدار قرمز. یک Script error. مبهم بدون منبع یا پشتهٔ مفید رسید؟ باز هم هشدار قرمز.

در همان جریان، خطاهایی هم بودند که واقعاً ارزش رسیدگی داشتند: URL متعلق به خود برنامه اشتباهی به https://example.comhttps://example.com/... تبدیل شده بود و مرورگر نتوانسته بود یک فایل Next.js متعلق به برنامه را زیر /_next/static/chunks/... لود کند.

جمع‌آوری داده کار می‌کرد. مانیتورینگ کار نمی‌کرد.

این تفاوت نگاه من به مشاهده‌پذیری فرانت‌اند را عوض کرد. رویداد خطا فقط مدرکی است که نشان می‌دهد چیزی رخ داده. هنوز نه تشخیص است، نه سطح شدت و نه یک رخداد عملیاتی.

اولین اشتباه این بود که «error» را معادل «فوری» می‌دانستم

مدل اولیه تقریباً این بود:

مرورگر خطا گزارش می‌کند
        ↓
CLIENT ERROR ارسال کن
        ↓
توسعه‌دهنده باید واکنش نشان دهد

این مدل چند سؤال متفاوت را با هم قاطی می‌کرد. آیا خطا در کد خود من است؟ آیا مسیر فعال واقعاً خراب شده؟ آیا لغو مورد انتظار بوده؟ آیا مرورگر اطلاعات کافی برای تشخیص منبع دارد؟ آیا برنامه بازیابی شده؟ ده پیام یعنی ده رخداد یا ده نشانه از یک رخداد؟

تا وقتی پاسخ این سؤال‌ها روشن نیست، یک رویداد نباید خودکار به هشدار تبدیل شود.

در یکی از بررسی‌های اولیه حدود هجده پیام داشتم. بیشترشان نویز سرویس‌های خارجی یا رفتار عادی چرخهٔ عمر بودند. دو مورد واقعاً فرق داشتند: URL معیوب متعلق به برنامه قطعاً باگ بود و لود نشدن یک چانک JavaScript متعلق به برنامه می‌توانست کد ضروری صفحه را از دسترس خارج کند. با این حال، گزارشگر تقریباً همه را با همان فوریتی نشان می‌داد که یک تبلیغ مسدودشده را.

همان‌جا فهمیدم «جمع کردن همهٔ خطاهای مرورگر» با «ساختن پایش محیط تولید» یک مسئله نیست. جمع‌آوری باید شواهد را نگه دارد؛ پایش باید شواهد را به تصمیم تبدیل کند.

مرورگر یک کانال واحد با معنای یکسان برای همهٔ خطاها ندارد

انواع مختلف خرابی در سمت کاربر با معنای یکسان گزارش نمی‌شوند.

رویداد error روی window برای خطاهای هم‌زمان اسکریپت استفاده می‌شود و در خطاهای بارگذاری منابع هم نقش دارد. Promise ردشده‌ای که مدیریت نشده مسیر دیگری دارد و مرورگر unhandledrejection را ایجاد می‌کند. عناصری که اسکریپت، تصویر یا رسانه بارگذاری می‌کنند هم می‌توانند رویداد error خودشان را تولید کنند. React و Next.js نیز در سطح فریم‌ورک سیگنال‌های مرز خطا را اضافه می‌کنند.

window.error
→ شاید یک خطای هم‌زمان script بیرون زده باشد

unhandledrejection
→ یک Promise ردشده در آن لحظه handler نداشته

element error
→ یک resource لود یا قابل استفاده نشده

framework boundary
→ rendering یا execution به مرز خطای framework رسیده

هوک سراسری یک نشانه را در مرز سیستم می‌بیند؛ لزوماً کل زنجیرهٔ علت را نمی‌داند.

وقتی این تفاوت را پذیرفتم، دیگر همهٔ رویدادها را فوراً به یک Error عمومی با یک سطح شدت یکسان تبدیل نکردم.

مالکیت منبع اولین فیلتر واقعاً مفید است

اولین تفکیک واقعاً مفید این بود که بفهمم کد یا منبع خراب متعلق به چه کسی است.

خرابی /_next/static/chunks/app/... با شکست یک SDK تبلیغاتی روی مبدأ دیگر یکی نیست. URL معیوبی که سازندهٔ URL خودم تولید کرده با درخواست تحلیل‌گری مسدودشده فرق دارد. خطای افزونهٔ مرورگر هم دستهٔ دیگری است.

  • خود برنامه: JavaScript، CSS، API، رسانه‌ها و URLهایی که کد خودم تولید می‌کند؛
  • فریم‌ورک و زمان اجرا: Next.js یا React وقتی در زنجیرهٔ اجرای برنامه قرار دارند؛
  • یکپارچه‌سازی‌های خارجی: تحلیل‌گری، تبلیغات، ویجت‌ها و SDKهای خارجی؛
  • محیط: افزونه‌های مرورگر، خزشگرها، وضعیت شبکه، ابزارهای حریم خصوصی و رفتارهای خاص مرورگر.

این به معنی بی‌اهمیت بودن سرویس‌های خارجی نیست. ارائه‌دهندهٔ پرداخت یا ورود ممکن است حیاتی باشد و خرابی تبلیغات می‌تواند روی درآمد اثر بگذارد. اما ناسالم بودن یکپارچه‌سازی به‌طور خودکار به معنی از کار افتادن برنامه نیست.

اگر همهٔ این موارد را به یک کانال فوری بفرستم، خود آن کانال دیگر معنای مشخصی ندارد.

دو خرابی متعلق به خود برنامه به من یاد دادند «قابل اقدام» یعنی چه

URL خراب مورد ساده بود:

https://example.comhttps://example.com/resource

در این مورد لازم نبود AdBlock، VPN یا سیاست‌های مرورگر را مقصر بدانم. خود URL نامعتبر بود. جایی از کد، مبدأ را به مقداری اضافه کرده بود که از قبل یک URL مطلق بود.

این رویداد قابل اقدام بود چون شواهد مشخص بودند، منبع متعلق به خودم بود و خرابی به مسیری از کد اشاره می‌کرد که کنترلش دست من بود.

خرابی چانک Next.js داستان دیگری داشت:

Failed to load script:
/_next/static/chunks/9253.647385b4be0958e4.js

آن هم متعلق به برنامه بود و می‌توانست صفحه را خراب کند، اما خود رویداد علت را ثابت نمی‌کرد. یک کلاینت قدیمی ممکن بود فایلی از استقرار قبلی بخواهد؛ درخواست ممکن بود به مهلت زمانی برسد؛ پراکسی معکوس یا CDN ممکن بود مشکل داشته باشد؛ اتصال ممکن بود قطع شود؛ یا فایل واقعاً وجود نداشته باشد.

پس واکنش درست «علت را پیدا کردم» نبود؛ بلکه «این کلاس خطا اولویت بالا دارد و شواهد بیشتری لازم است» بود.

شدت می‌تواند بالا باشد حتی وقتی اطمینان ما به علت پایین است.

Script error. سرنخ است، نه رد پشته

Error: Script error.
filename: unknown
line: 0
column: 0

ترسناک به نظر می‌رسد اما تقریباً هیچ اطلاعاتی نمی‌دهد.

مرورگرها جزئیات خطاهای اسکریپت میان‌مبدأیی را عمداً محدود می‌کنند. MDN توضیح می‌دهد که بدون تنظیم درست CORS، window.onerror اطلاعات محدودی دریافت می‌کند؛ همچنین رفتار crossorigin در <script> مستقیماً روی مقدار اطلاعاتی که قابل نمایش است اثر دارد.

پس یک Script error. مبهم را خودکار به «برنامهٔ من از کار افتاد» تعبیر نمی‌کنم. منبع می‌تواند کد خودم، کد یک سرویس خارجی، کد تزریق‌شده یا خطایی باشد که مرورگر به‌خاطر محدودیت میان‌مبدأیی اجازه ندارد جزئیاتش را نشان دهد.

رویداد را نگه می‌دارم و آن را با صفحه، بیلد، مرورگر و رویدادهای نزدیک مرتبط می‌کنم؛ اما یک مورد منفرد در 0:0 به‌تنهایی هشدار فوری نمی‌سازد. اگر همان الگو دور یک انتشار یا مسیر مشخص جمع شود، اولویت تغییر می‌کند.

ناشناخته مساوی بی‌خطر نیست، همان‌طور که مساوی بحرانی هم نیست.

AbortError می‌تواند خطایی واقعی و در عین حال بخشی طبیعی از چرخهٔ عمر باشد

پیش‌نمایش ویدئو واضح‌ترین مثال را به من داد:

AbortError:
The play() request was interrupted by a call to pause()

HTMLMediaElement.play() یک Promise برمی‌گرداند و آن Promise ممکن است رد شود. عملیات چرخهٔ عمر رسانه می‌توانند پخش معلق را عمداً متوقف کنند؛ MDN نیز صریحاً مستند کرده که load()، Promiseهای معلق play() را با AbortError متوقف می‌کند.

در شبکه‌ای از پیش‌نمایش‌ها، این وضعیت می‌تواند بدون هیچ خرابی قابل مشاهده‌ای رخ دهد. آیتم وارد viewport می‌شود و کد play() را صدا می‌زند؛ کاربر اسکرول می‌کند، آیتم از viewport خارج می‌شود و قبل از اینکه شروع پخش کامل شود، کد رسانه را متوقف یا جایگزین می‌کند.

رد شدن Promise واقعی است؛ اما ممکن است هیچ رخدادی برای کاربر وجود نداشته باشد.

راه‌حل درست معمولاً کنار همان فراخوانی است: Promise را همان‌جا مدیریت کنم و لغو مورد انتظار را از خرابی واقعی پخش جدا کنم. unhandledrejection شبکهٔ ایمنی خوبی است، اما نباید اولین جایی باشد که چرخهٔ عمر عادی رسانه را معنا می‌کند.

خرابی سرویس‌های خارجی به مدل سلامت جداگانه نیاز دارد

در لاگ‌های اولیه خرابی‌های زیادی از دامنه‌های تحلیل‌گری و تبلیغات می‌دیدم. بعضی از مرورگرهای متمرکز بر حریم خصوصی می‌آمدند و بعضی از خزشگرها. یکی از بی‌فایده‌ترین هشدارهای فوری این بود که یک خزشگر نتوانسته Google Analytics را لود کند.

این فقط نشان می‌دهد یک درخواست شبکه شکست خورده؛ تقریباً هیچ چیز دربارهٔ اینکه کاربر انسانی می‌توانسته از برنامه استفاده کند یا نه نمی‌گوید.

مشکل ذخیره کردن آن رویداد نبود؛ مشکل این بود که آن را در همان جریان رخدادهای یک چانک JavaScript متعلق به برنامه و لودنشده می‌گذاشتم.

  • آیا برنامه برای کاربر شکسته؟
  • آیا یکپارچه‌سازی خارجی سالم است؟

اسکریپت تبلیغاتی مسدودشده می‌تواند وارد سنجهٔ تحویل تبلیغ شود و خرابی تحلیل‌گری می‌تواند پوشش تحلیل‌گری را اندازه بگیرد. هیچ‌کدام نباید با عنوان «فرانت‌اند از کار افتاد» هشدار فوری بدهند مگر اینکه شواهدی باشد که قابلیت اصلی برنامه به آن‌ها وابسته است.

این جداسازی حتی مشکلات سرویس‌های خارجی را واضح‌تر می‌کند؛ چون می‌توانم آن‌ها را بر اساس ارائه‌دهنده، مرورگر و منطقه تجمیع کنم، نه اینکه به‌صورت نویز قرمز تصادفی ببینم.

navigator.onLine زمینه است، نه اثبات دسترسی

بعداً ثبت کردم که مرورگر خودش را آنلاین می‌داند یا نه. این اطلاعات مفید است، اما فقط به‌عنوان سرنخ.

رویدادهایی داشتم که منطق اضافه آن‌ها را خرابی شبکه تشخیص می‌داد، در حالی که لاگ هم‌زمان می‌گفت:

Online: true

تناقضی وجود ندارد. MDN صریحاً هشدار می‌دهد که navigator.onLine بر پایهٔ استدلال‌های مرورگر و سیستم‌عامل است. دستگاه می‌تواند به شبکهٔ محلی وصل باشد اما به مبدأ من دسترسی نداشته باشد. VPN، فایروال، DNS و قطعی‌های جزئی شبکه هم تصویر را پیچیده‌تر می‌کنند.

online === false
→ نشانه قوی از مشکل محیطی

online === true
→ ثابت نمی‌کند origin یا resource قابل دسترسی بوده‌اند

همین تفاوت کوچک جلوی این را می‌گیرد که سامانهٔ پایش یک سرنخ را به تشخیص قطعی اما اشتباه تبدیل کند.

یک خرابی ریشه‌ای می‌تواند چند رویداد مرورگر تولید کند

با بهتر شدن جمع‌آوری، نوع دیگری از نویز روشن شد: یک رخداد می‌تواند چند پیام تولید کند.

یک چانک JavaScript ممکن است ابتدا resource.error ایجاد کند، سپس بارگذار ماژول ChunkLoadError بدهد، React یا Next.js وارد مرز خطا شوند و منطق بازیابی بازبارگذاری را زمان‌بندی کند. اگر هر لایه هشدار مستقلی بفرستد، یک عمل کاربر شبیه چند خرابی جدا در تولید دیده می‌شود.

پنج پیام از نظر ذهنی شبیه پنج کاربر آسیب‌دیده‌اند، در حالی که ممکن است همه از یک نشست و یک منبع آمده باشند.

حذف تکرار بر اساس متن پیام کافی نیست. باید رویدادها را در سطح رخداد به هم مرتبط کرد:

session
+ بازه زمانی کوتاه
+ error class نرمال‌شده
+ first-party resource
+ client build
+ route

رویدادهای خام را نگه می‌دارم، اما آنچه برای انسان ارسال می‌شود باید پرمعناترین نمایش رخداد باشد. اگر مرز خطا از قبل رد پشتهٔ کد خودم و URL دقیق چانک را دارد، خطای عمومی منبع که قبل از آن آمده لازم نیست هشدار فوری دوم بسازد.

برای رخداد هشدار بده؛ رویدادها را ذخیره کن.

اطلاعات اطراف خطا از خود رشتهٔ خطا ارزشمندتر شد

تله‌متری بعدی من ساختاریافته‌تر شد:

clientBuildId
resource URL
resourceResponseStatus
resourceTransferSize
resourceDurationMs
serviceWorkerVersion
serviceWorkerController
serviceWorkerState
chunkRecoveryScheduled
online
stack / component stack

برای رسانه، کد خطای رسانه، وضعیت HTTP در صورت مشاهدهٔ مستقل، Content-Type واقعی و اینکه خرابی بیشتر از نوع HTTP بود یا تحویل شبکه را هم ثبت می‌کردم.

این فیلدها سؤال‌هایی را ممکن کردند که یک رشتهٔ استثنا جوابشان را نمی‌دهد: خرابی‌ها از یک بیلد خاص شروع شدند؟ مرورگر پاسخ HTTP گرفت؟ Service Worker صفحه را کنترل می‌کرد؟ منطق بازیابی اجرا شده بود؟ چند رویداد به یک منبع اشاره می‌کردند؟ مسیر فعلی واقعاً خراب شده بود؟

Resource Timing API می‌تواند مدت منبع، اطلاعات انتقال و در محیط‌های پشتیبانی‌شده و مجاز، وضعیت پاسخ را بدهد. این فیلدها محدودیت دارند: زمان‌بندی میان‌مبدأیی محدود است، منبع کش‌شده می‌تواند transferSize: 0 داشته باشد و responseStatus همه‌جا در دسترس نیست. به همین دلیل null و 0 باید حالت‌های معنادار باقی بمانند، نه اینکه به قطعیت ساختگی تبدیل شوند.

ChunkLoadError نشانه است، نه آشکارساز 404

یک رخداد بعدی نحوهٔ تفسیر خرابی چانک‌ها را برای من تغییر داد.

مرورگر برای یک چانک layout در Next.js، ChunkLoadError گزارش کرد و تله‌متری غنی‌شده هم‌زمان شامل این موارد بود:

resourceResponseStatus: 200
resourceDurationMs: 170523
serviceWorkerState: activated
chunkRecoveryScheduled: true

زمان ثبت‌شده حدود 170 ثانیه بود. علت دقیق آن رخداد هرچه بود، همین داده برای رد کردن یک قاعدهٔ بیش از حد ساده کافی بود:

ChunkLoadError === سرور 404 برگرداند

خرابی‌های دیگر چانک هیچ وضعیت پاسخ قابل مشاهده‌ای نداشتند. بعضی به‌شکل پایان مهلت دیده شدند و بعضی اطلاعات انتقال داشتند. ردهٔ خطا یکی بود، اما شواهد اطرافش نه.

این موضوع در Next.js مهم‌تر است چون فایل‌های زیر /_next/static/ معمولاً هش محتوا دارند و برای کش تغییرناپذیر طراحی شده‌اند. مستندات فعلی میزبانی شخصی Next.js نیز هدرهای کش طولانی‌مدت را برای این فایل‌ها توضیح می‌دهد. بنابراین ChunkLoadError می‌تواند به اختلاف نسخه هنگام استقرار، کلاینت قدیمی، شبکه، پراکسی معکوس، CDN، کش، Service Worker یا واقعاً فایل ساخت مفقود مربوط باشد.

نمی‌خواهم لایهٔ هشدار علت را حدس بزند. می‌خواهم شواهد لازم برای بررسی را حفظ کند.

خطاهای رسانه همان درس را از زاویه‌ای دیگر دادند

در بعضی رخدادها، عنصر رسانه گزارش می‌کرد:

MEDIA_ELEMENT_ERROR: Format error

اگر فقط این پیام را ببینیم، تقریباً شبیه ناسازگاری کدک است.

اما بررسی اضافهٔ تحویل برای بعضی از آن رخدادها نشان داد:

HTTP status: 410
Content-Type: text/html; charset=utf-8
failure kind: http

مرورگر ویدئو خواسته بود اما پاسخ خطای HTTP حاوی HTML گرفته بود. عنصر رسانه نمی‌توانست HTML را به‌عنوان ویدئو رمزگشایی کند، پس نشانهٔ بیرونی “Format error” شد. تشخیص مفید در لایهٔ تحویل بود.

لایه‌ای که اولین بار خرابی را می‌بیند لزوماً همان لایه‌ای نیست که آن را ایجاد کرده است.

«خرابی کدک»، «قطعی شبکه»، «اشکال کش» و «چانک مفقود» نتیجه‌گیری‌اند. تله‌متری باید اول مشاهدات را ثبت کند.

خرابی‌های مرورگر را با پنج بُعد ارزیابی می‌کنم

1. مالکیت

منبع متعلق به خود برنامه است، فریم‌ورک/زمان اجراست، سرویس خارجی است یا از محیط می‌آید؟

2. اثر روی کاربر

آیا مسیر فعال، رندر، ورود، چت، پرداخت یا جریان اصلی دیگری شکسته؟ یا فقط تبلیغ اختیاری، تحلیل‌گری، پیش‌بارگذاری یا پیش‌نمایش شکست خورده و صفحه هنوز قابل استفاده است؟

3. کیفیت شواهد

آیا رد پشتهٔ متعلق به کد خودم، URL منبع، وضعیت HTTP، شناسهٔ بیلد و پشتهٔ مؤلفه را دارم؟ یا فقط Script error. در 0:0؟

4. تکرار و گستره

یک رویداد از یک نشست است یا همان الگو بعد از یک انتشار در میان کاربران، مسیرها و مرورگرهای مختلف دیده می‌شود؟

5. بازیابی

آیا برنامه خودکار بازیابی شد؟ بازبارگذاری چانک زمان‌بندی شد؟ راه جایگزین کار کرد؟ کاربر هنوز گیر کرده است؟

first-party روشن
+ اثر زیاد روی کاربر
+ شواهد قوی
+ چند session
+ بدون recovery
= incident فوری

third-party
+ قابلیت اختیاری
+ شواهد ضعیف
+ منفرد
+ بدون اثر روی کاربر
= metric یا اولویت پایین

فیلتر کردن باید محافظه‌کارانه باشد

وقتی نویز زیاد می‌شود، وسوسه‌انگیز است که ده‌ها عبارت منظم بنویسم و هر چیز آزاردهنده را حذف کنم. این کار خطرناک است.

اگر همهٔ AbortErrorها را خاموش کنم، ممکن است درخواست‌های واقعی API را که قطع شده‌اند پنهان کنم. اگر هر Script error. را حذف کنم، ممکن است خوشه‌ای مخصوص یک مرورگر را که فقط بعد از تجمیع معنا پیدا می‌کند از دست بدهم. اگر همهٔ خرابی‌های سرویس‌های خارجی را نادیده بگیرم، ممکن است خرابی پرداخت، احراز هویت یا مدیریت رضایت را پنهان کنم.

ALERT
→ incident قوی و نیازمند اقدام

RETAIN / AGGREGATE
→ نگه‌دار و بشمار؛ هنگام شکل‌گیری cluster alert کن

METRIC / SAMPLE
→ نویز مورد انتظار یا کم‌اثر؛ trend و نمونه‌ها را حفظ کن

سیستم می‌تواند ساکت‌تر شود بدون اینکه کور شود.

یک طبقه‌بند خوب در اصل سیاستی است که به کد تبدیل شده

کد زیر از پروژهٔ تولیدی من نیست؛ فقط نمونه‌ای فشرده از سیاستی است که کاش از ابتدا داشتم:

function classifyClientEvent(event) {
  const owner = classifyOwner(event);

  if (isExpectedMediaCancellation(event)) {
    return { severity: "metric", reason: "expected-cancellation" };
  }

  if (owner === "first-party" && breaksActiveRoute(event)) {
    return { severity: "alert", reason: "first-party-user-impact" };
  }

  if (isActiveFirstPartyChunkFailure(event)) {
    return { severity: "alert", reason: "application-chunk" };
  }

  if (owner === "third-party") {
    return { severity: "aggregate", reason: "integration-health" };
  }

  if (isOpaqueScriptError(event)) {
    return { severity: "aggregate", reason: "insufficient-evidence" };
  }

  return { severity: "aggregate", reason: "needs-correlation" };
}

کار سخت داخل تابع‌هایی مثل breaksActiveRoute() پنهان است. نام استثنا به‌تنهایی کافی نیست؛ به زمینهٔ مسیر، مالکیت منبع، دادهٔ مرز خطا و گاهی شناخت مخصوص محصول نیاز داریم.

اثر انگشت باید رخداد را دنبال کند، نه متن پیام را

برابری کامل متن پیام راه ضعیفی برای حذف تکرار است. آفست‌های پشتهٔ کوچک‌سازی‌شده با بیلد تغییر می‌کنند، هش چانک‌ها عوض می‌شود، URLها شناسهٔ پویا دارند و متن خطای مرورگرها متفاوت است.

{
  errorClass,
  normalizedFirstPartyResource,
  routeFamily,
  clientBuild,
  sessionId,
  shortTimeBucket
}

برای از کار افتادن سراسری برنامه، بالاترین فریم متعلق به کد خودم در پشته ممکن است مفیدتر باشد. برای خرابی چانک، منبع نرمال‌شدهٔ چانک مهم‌تر است. برای خرابی تحویل رسانه، ردهٔ خطای HTTP و مسیر رسانه ممکن است از پیام بیرونی مرورگر ارزشمندتر باشند.

resource.error
→ ChunkLoadError
→ framework boundary
→ recovery scheduled

در نتیجه یک زنجیرهٔ رویداد مرتبط می‌تواند به یک رخداد با چهار مشاهدهٔ پیوست‌شده تبدیل شود، نه چهار خرابی فوری جداگانه.

کانال هشدار فوری باید وظیفه‌ای بسیار محدودتر داشته باشد

هشدار فوری را برای مواردی مثل این نگه می‌دارم: خطای زمان اجرای متعلق به برنامه با رد پشتهٔ مفید که مسیر فعال را می‌شکند؛ مرز خطای React/Next.js که واقعاً تعامل را خراب می‌کند؛ لود نشدن JavaScript یا CSS متعلق به برنامه و مورد نیاز مسیر؛ ChunkLoadError تکرارشونده میان چند نشست یا بیلد؛ خرابی API یا دادهٔ اصلی که کاربر را متوقف می‌کند؛ یا نقض روشن یک قاعدهٔ داخلی مثل تولید URL معیوب.

یک Script error. مبهم و منفرد، خرابی یک منبع متعلق به برنامه با بازیابی موفق، خطای رسانه با لایهٔ ریشهٔ نامشخص، یا ناهنجاری مخصوص مرورگر که هنوز به خوشه‌بندی نیاز دارد را نگه می‌دارم اما فوراً هشدار نمی‌دهم.

و معمولاً این موارد را به سنجه‌ها یا تشخیص نمونه‌برداری‌شده می‌فرستم: خرابی شناخته‌شدهٔ منابع تبلیغات یا تحلیل‌گری؛ لغو مورد انتظار رسانه با AbortError؛ خرابی سرویس خارجی فقط برای خزشگرها؛ خرابی همراه با نشانهٔ قوی آفلاین؛ و منابع اختیاری پیش‌بینانه که مسیر فعال را تحت تأثیر قرار نمی‌دهند.

هشدار فوری باید نشان‌دهندهٔ اثر قابل اقدام روی کاربر باشد، نه حجم خام شکایت‌های مرورگر.

ترجیح می‌دهم رخدادها را اندازه بگیرم، نه یک «تعداد خطای» کلی را

  • رخدادهای متعلق به برنامه در هر 1,000 نشست؛
  • نشست‌های تحت تأثیر بر اساس شناسهٔ بیلد؛
  • رخدادهای مرز خطا بر اساس مسیر؛
  • خرابی چانک‌ها بر اساس منبع و استقرار؛
  • نرخ خرابی یکپارچه‌سازی خارجی بر اساس ارائه‌دهنده؛
  • حجم لغوهای مورد انتظار، تا افزایش ناگهانی همچنان دیده شود؛
  • نرخ موفقیت بازیابی؛
  • تعداد رخدادهای یکتا جدا از تعداد رویدادهای خام.

«یک رویداد رخ داد» به‌ندرت آستانهٔ خوبی برای هشدار تولید است. «همان رخداد متعلق به برنامه اکنون چند نشست مستقل را روی بیلد جدید درگیر کرده و بازیابی هم شکست می‌خورد» به چیزی که انسان باید روی آن اقدام کند نزدیک‌تر است.

پایش سمت کاربر به‌تنهایی علت ریشه‌ای را ثابت نمی‌کند

تله‌متری مرورگر محدودیت‌های جدی دارد.

نبود وضعیت HTTP می‌تواند یعنی API آن را در اختیار نگذاشته، مرورگر فیلد را پشتیبانی نمی‌کند، محدودیت میان‌مبدأیی دخالت کرده، درخواست لغو شده یا شکاف دیگری در مشاهده‌پذیری وجود دارد. فعال بودن Service Worker ثابت نمی‌کند پاسخ قدیمی از آن آمده. ظاهر شدن ChunkLoadError بعد از استقرار، اختلاف نسخه را ثابت نمی‌کند. online: true نیز دسترسی‌پذیر بودن مبدأ را ثابت نمی‌کند.

پایش سمت کاربر فرضیه‌ها را محدود می‌کند. برای اثبات علت هنوز ممکن است به لاگ سرور، لاگ پراکسی معکوس، مانیفست استقرار، وضعیت کش و بازتولید واقعی نیاز باشد.

همچنین نمی‌خواهم مشاهده‌پذیری به جمع‌آوری بی‌حد دادهٔ کاربر تبدیل شود. هر فیلد باید به این دلیل وجود داشته باشد که واقعاً میان حالت‌های خرابی تفاوت ایجاد می‌کند.

تله‌متری بهتر لزوماً بیشتر نیست؛ تمایزدهنده‌تر است.

قاعدهٔ من حالا این است: رویدادها را جمع کن، رخدادها را بررسی کن، روی اثر هشدار بده

ابتدا می‌خواستم گزارشگر فقط به یک سؤال پاسخ دهد: «چیزی خراب شد؟». در محیط تولید این سؤال بیش از حد گسترده است. همیشه خزشگری هست که به تحلیل‌گری نمی‌رسد، ابزار حریم خصوصی‌ای که تبلیغ را می‌بندد، Promise رسانه‌ای که عمداً لغو می‌شود، کاربری که اتصالش را از دست می‌دهد یا SDK خارجی‌ای که بد رفتار می‌کند.

آیا مال خودمان است؟
آیا کاربر قابلیتی را از دست داده؟
قدرت شواهد چقدر است؟
آیا تکرار می‌شود؟
آیا برنامه بازیابی شد؟
این‌ها چند event هستند یا یک incident؟

وقتی پایش را حول سؤال‌های دقیق‌تر ساختم، سیل پیام‌های قرمز از نویز به ابزار مهندسی تبدیل شد.

خطای مرورگر یک مشاهده است. رخداد، توضیحی مرتبط از اثر بر کاربر است. هشدار، تصمیمی است که می‌گوید حالا زمان اقدام انسان است.

دیگر نمی‌خواهم این سه مفهوم را مترادف بدانم.