تخصيص تقارير الأعطال لمنصات Apple

اختيار المنصة: iOS+ Android Flutter Unity


يمكنك النقر على إحدى المشاكل والحصول على تقرير مفصّل عن الحدث في لوحة بيانات عمليات التطوير والتفاعل > Crashlytics في وحدة تحكّم Firebase. ويمكنك تخصيص هذه التقارير لمساعدتك في فهم ما يحدث في تطبيقك بشكل أفضل والظروف المحيطة بالأحداث التي تم الإبلاغ عنها إلى Crashlytics.

إضافة مفاتيح مخصّصة

تساعدك المفاتيح المخصّصة في الحصول على الحالة المحدّدة لتطبيقك قبل حدوث عُطل. يمكنك ربط أزواج مفتاح/قيمة عشوائية بتقارير الأعطال، ثم استخدام المفاتيح المخصّصة للبحث عن تقارير الأعطال وفلترتها في لوحة بيانات DevOps & Engagement > Crashlytics في Firebase Console.

  • يمكنك البحث عن المشاكل التي تتطابق مع مفتاح مخصّص.

  • عند مراجعة مشكلة معيّنة في وحدة التحكّم، يمكنك الاطّلاع على المفاتيح المخصّصة المرتبطة بكل حدث (علامة التبويب الفرعية المفاتيح) ويمكنك حتى فلترة الأحداث حسب المفاتيح المخصّصة (قائمة الفلتر في أعلى الصفحة).

استخدِم طريقة setCustomValue لضبط أزواج المفتاح/القيمة. على سبيل المثال:

Swift

// Set int_key to 100.
Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set str_key to "hello".
Crashlytics.crashlytics().setCustomValue("hello", forKey: "str_key")

Objective-C

عند ضبط الأعداد الصحيحة أو القيم المنطقية أو الأرقام العشرية، ضع القيمة في المربّع @(value).

// Set int_key to 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set str_key to "hello".
[[FIRCrashlytics crashlytics] setCustomValue:@"hello" forKey:@"str_key"];

يمكنك أيضًا تعديل قيمة مفتاح حالي من خلال استدعاء المفتاح وتعيين قيمة مختلفة له. على سبيل المثال:

Swift

Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set int_key to 50 from 100.
Crashlytics.crashlytics().setCustomValue(50, forKey: "int_key")

Objective-C

[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set int_key to 50 from 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(50) forKey:@"int_key"];

يمكنك إضافة أزواج المفتاح/القيمة بشكل مجمّع باستخدام طريقة setCustomKeysAndValues مع NSDictionary كالمَعلمة الوحيدة:

Swift

let keysAndValues = [
                 "string key" : "string value",
                 "string key 2" : "string value 2",
                 "boolean key" : true,
                 "boolean key 2" : false,
                 "float key" : 1.01,
                 "float key 2" : 2.02
                ] as [String : Any]

Crashlytics.crashlytics().setCustomKeysAndValues(keysAndValues)

Objective-C

NSDictionary *keysAndValues =
    @{@"string key" : @"string value",
      @"string key 2" : @"string value 2",
      @"boolean key" : @(YES),
      @"boolean key 2" : @(NO),
      @"float key" : @(1.01),
      @"float key 2" : @(2.02)};

[[FIRCrashlytics crashlytics] setCustomKeysAndValues: keysAndValues];

إضافة رسائل سجلّ مخصّصة

للحصول على المزيد من السياق حول الأحداث التي أدّت إلى حدوث تعطُّل، يمكنك إضافة سجلّات Crashlytics مخصّصة إلى تطبيقك. تربط Crashlytics السجلات ببيانات الأعطال وتعرضها في علامة التبويب السجلّات عند الاطّلاع على تفاصيل إحدى المشاكل (يمكنك الاطّلاع على جميع المشاكل في لوحة بيانات DevOps ومعدّل الاهتمام بالتطبيق > Crashlytics في وحدة تحكّم Firebase).

Swift

استخدِم log() أو log(format:, arguments:) للمساعدة في تحديد المشاكل. إذا كنت تريد الحصول على ناتج سجلّ مفيد يتضمّن رسائل، يجب أن يتوافق العنصر الذي تمرّره إلى log() مع السمة CustomStringConvertible. تعرض log() سمة الوصف التي تحدّدها للكائن. على سبيل المثال:

Crashlytics.crashlytics().log("Higgs-Boson detected! Bailing out…, \(attributesDict)")

تنسّق .log(format:, arguments:) القيم التي يتم عرضها من خلال استدعاء getVaList(). على سبيل المثال:

Crashlytics.crashlytics().log(format: "%@, %@", arguments: getVaList(["Higgs-Boson detected! Bailing out…", attributesDict]))

لمزيد من التفاصيل حول كيفية استخدام log() أو log(format:, arguments:)، يُرجى الرجوع إلى Crashlytics المستندات المرجعية.

Objective-C

استخدِم log أو logWithFormat للمساعدة في تحديد المشاكل. يُرجى العِلم أنّه إذا أردت الحصول على ناتج سجلّ مفيد يتضمّن رسائل، يجب أن يتجاوز العنصر الذي تمرّره إلى أيّ من الطريقتين سمة مثيل description. على سبيل المثال:

[[FIRCrashlytics crashlytics] log:@"Simple string message"];

[[FIRCrashlytics crashlytics] logWithFormat:@"Higgs-Boson detected! Bailing out... %@", attributesDict];

[[FIRCrashlytics crashlytics] logWithFormat:@"Logging a variable argument list %@" arguments:va_list_arg];

لمزيد من التفاصيل حول كيفية استخدام log وlogWithFormat، يُرجى الرجوع إلى Crashlytics المستندات المرجعية.

ضبط معرّفات المستخدمين

لتشخيص مشكلة، من المفيد غالبًا معرفة المستخدمين الذين واجهوا عُطلاً معيّنًا. تتضمّن Crashlytics طريقة لتحديد هوية المستخدمين بشكل مجهول في تقارير الأعطال.

لإضافة أرقام تعريف المستخدمين إلى تقاريرك، خصِّص معرّفًا فريدًا لكل مستخدم على شكل رقم تعريف أو رمز مميّز أو قيمة مجزّأة:

Swift

Crashlytics.crashlytics().setUserID("123456789")

Objective-C

[[FIRCrashlytics crashlytics] setUserID:@"123456789"];

إذا احتجت في أي وقت إلى محو معرّف مستخدم بعد ضبطه، عليك إعادة ضبط القيمة على سلسلة فارغة. ولا يؤدي محو معرّف المستخدم إلى إزالة السجلات الحالية Crashlytics. إذا كنت بحاجة إلى حذف السجلات المرتبطة بمعرّف مستخدم، يُرجى التواصل مع فريق دعم Firebase.

الإبلاغ عن استثناءات غير خطيرة

بالإضافة إلى إعداد التقارير تلقائيًا عن الأعطال في تطبيقك، تتيح لك Crashlytics تسجيل الاستثناءات غير الخطيرة وإرسالها إليك في المرة التالية التي يتم فيها تشغيل تطبيقك.

يمكنك تسجيل الاستثناءات غير المميتة من خلال تسجيل عناصر NSError باستخدام الطريقة recordError. تلتقط recordError حزمة تنفيذ سلسلة التعليمات من خلال استدعاء [NSThread callStackReturnAddresses].

Swift

Crashlytics.crashlytics().record(error: error)

Objective-C

[[FIRCrashlytics crashlytics] recordError:error];

عند استخدام طريقة recordError، من المهم فهم بنية NSError وكيفية استخدام Crashlytics للبيانات من أجل تجميع الأعطال. قد يؤدي الاستخدام غير الصحيح للطريقة recordError إلى حدوث سلوك غير متوقّع، وقد يتسبّب في أن تحدّ Crashlytics من إعداد التقارير عن الأخطاء المسجّلة في تطبيقك.

يتضمّن عنصر NSError ثلاث وسيطات:

  • domain: String
  • code: Int
  • userInfo: [AnyHashable : Any]? = nil

على عكس الأعطال المميتة التي يتم تجميعها من خلال تحليل تتبُّع تسلسل استدعاء الدوال البرمجية، يتم تجميع الأخطاء المسجّلة حسب domain وcode. وهذا فرق مهم بين الأعطال الفادحة والأخطاء المسجَّلة. على سبيل المثال:

Swift

let userInfo = [
  NSLocalizedDescriptionKey: NSLocalizedString("The request failed.", comment: ""),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString("The response returned a 404.", comment: ""),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString("Does this page exist?", comment: ""),
  "ProductID": "123456",
  "View": "MainView"
]

let error = NSError.init(domain: NSCocoaErrorDomain,
                         code: -1001,
                         userInfo: userInfo)

Objective-C

NSDictionary *userInfo = @{
  NSLocalizedDescriptionKey: NSLocalizedString(@"The request failed.", nil),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The response returned a 404.", nil),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Does this page exist?", nil),
  @"ProductID": @"123456",
  @"View": @"MainView",
};

NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
                                     code:-1001
                                 userInfo:userInfo];

عند تسجيل الخطأ أعلاه، يتم إنشاء مشكلة جديدة يتم تجميعها حسب NSSomeErrorDomain و-1001. يتم تجميع الأخطاء الإضافية المسجّلة التي تستخدم قيم المجال والرمز نفسها ضمن المشكلة نفسها. يتم تحويل البيانات الواردة في الكائن userInfo إلى أزواج مفتاح/قيمة وعرضها في قسم المفاتيح/السجلات ضمن مشكلة فردية.

السجلّات والمفاتيح المخصّصة

وكما هو الحال مع تقارير الأعطال، يمكنك تضمين السجلّات والمفاتيح المخصّصة لإضافة سياق إلى NSError. ومع ذلك، هناك اختلاف في السجلّات المرفقة بالأعطال مقارنةً بالأخطاء المسجّلة. فعند حدوث عُطل وإعادة تشغيل التطبيق، تكون السجلّات التي يستردّها Crashlytics من القرص هي تلك التي تمت كتابتها حتى وقت العُطل. وعند تسجيل NSError، لا يتم إيقاف التطبيق على الفور. وبما أنّ Crashlytics لا يرسل تقرير الخطأ المسجّل إلا عند إعادة تشغيل التطبيق، ويجب أن يحدّ من مقدار المساحة المخصّصة للسجلّات على القرص، من المحتمل تسجيل ما يكفي بعد تسجيل NSError ليتم استبدال جميع السجلّات ذات الصلة بحلول الوقت الذي يرسل فيه Crashlytics التقرير من الجهاز. ضَع هذا التوازن في الاعتبار عند تسجيل NSErrors واستخدام السجلّات والمفاتيح المخصّصة في تطبيقك.

اعتبارات الأداء

يُرجى العِلم أنّ تسجيل NSError قد يكون مكلفًا إلى حدٍّ ما. عند إجراء المكالمة، تسجّل Crashlytics حزمة التنفيذ لسلسلة التعليمات الحالية باستخدام عملية تُعرف باسم "فك سلسلة الاستدعاءات". يمكن أن تكون هذه العملية مكثفة بالنسبة إلى وحدة المعالجة المركزية (CPU) وعمليات الإدخال والإخراج (I/O)، خاصةً في البِنى التي تتيح إيقاف تنفيذ DWARF (مثل arm64 وx86). بعد اكتمال عملية التراجع، تتم كتابة المعلومات على القرص بشكل متزامن. ويمنع ذلك فقدان البيانات في حال تعطُّل السطر التالي.

على الرغم من أنّه من الآمن استدعاء واجهة برمجة التطبيقات هذه في سلسلة تعليمات في الخلفية، تذكَّر أنّ إرسال هذا الاستدعاء إلى صفّ انتظار آخر يؤدي إلى فقدان سياق تتبُّع تسلسل استدعاء الدوال البرمجية الحالي.

ماذا عن NSExceptions؟

لا يوفّر Crashlytics وسيلة لتسجيل NSException الحالات وتتبُّعها مباشرةً. بشكل عام، لا تكون واجهات برمجة التطبيقات Cocoa وCocoa Touch آمنة من الاستثناءات. وهذا يعني أنّ استخدام @catch قد يؤدي إلى آثار جانبية غير مقصودة وخطيرة جدًا في عمليتك، حتى عند استخدامه بحذر شديد. يجب عدم استخدام عبارات @catch في الرمز البرمجي. يُرجى الرجوع إلى مستندات Apple حول هذا الموضوع.

تخصيص تتبُّع تسلسل استدعاء الدوال البرمجية

إذا كان تطبيقك يعمل في بيئة غير أصلية (مثل C++‎ أو Unity)، يمكنك استخدام واجهة برمجة التطبيقات Exception Model API لإرسال البيانات الوصفية للأعطال بتنسيق الاستثناء الأصلي لتطبيقك. يتم وضع علامة "غير خطأ" على الاستثناءات التي تم الإبلاغ عنها.

Swift

var  ex = ExceptionModel(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(symbol:"makeError", file:"handler.js", line:495),
  StackFrame(symbol:"then", file:"routes.js", line:102),
  StackFrame(symbol:"main", file:"app.js", line:12),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithSymbol:@"makeError" file:@"handler.js" line:495],
  [FIRStackFrame stackFrameWithSymbol:@"then" file:@"routes.js" line:102],
  [FIRStackFrame stackFrameWithSymbol:@"main" file:@"app.js" line:12],
];

[[FIRCrashlytics crashlytics] recordExceptionModel:model];

يمكن أيضًا تهيئة إطارات حزمة التعليمات البرمجية المخصّصة باستخدام العناوين فقط:

Swift

var  ex = ExceptionModel.init(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(address:0xfa12123),
  StackFrame(address:12412412),
  StackFrame(address:194129124),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithAddress:0xfa12123],
  [FIRStackFrame stackFrameWithAddress:12412412],
  [FIRStackFrame stackFrameWithAddress:194129124],
];


[[FIRCrashlytics crashlytics] recordExceptionModel:model];

الحصول على سجلّات أشرطة التنقّل

تمنحك سجلّات مسار التنفيذ فهمًا أفضل للتفاعلات التي أجراها المستخدم مع تطبيقك قبل وقوع حدث تعطُّل أو خطأ غير فادح أو خطأ ANR. ويمكن أن تكون هذه السجلّات مفيدة عند محاولة إعادة إظهار مشكلة وتصحيحها.

تستند سجلّات مسار التنفيذ إلى "إحصاءات Google"، لذا للحصول على سجلّات مسار التنفيذ، عليك تفعيل "إحصاءات Google" لمشروع Firebase وإضافة Firebase SDK لـ Google Analytics إلى تطبيقك. بعد استيفاء هذه المتطلبات، يتم تلقائيًا تضمين سجلّات مسار التنفيذ مع بيانات الحدث ضمن علامة التبويب السجلّات عند عرض تفاصيل المشكلة (يمكنك الاطّلاع على جميع المشاكل في لوحة بيانات DevOps & Engagement > Crashlytics في وحدة تحكّم Firebase).

يسجّل حزمة تطوير البرامج (SDK) الخاصة بـ Analytics تلقائيًا الحدث screen_view، ما يتيح لسجلات مسار التنفيذ عرض قائمة بالشاشات التي تم عرضها قبل وقوع الحدث، سواء كان عطلاً أو خطأ غير فادح أو خطأ ANR. يحتوي سجلّ مسار التنفيذ الخاص بـ screen_view على المَعلمة firebase_screen_class.

يتم أيضًا ملء سجلّات مسار التنقّل بأي أحداث مخصّصة تسجّلها يدويًا ضمن جلسة المستخدم، بما في ذلك بيانات مَعلمات الحدث. يمكن أن تساعد هذه البيانات في عرض سلسلة من إجراءات المستخدمين التي أدّت إلى حدوث عُطل أو خطأ غير قاتل أو خطأ ANR.

يُرجى العِلم أنّه يمكنك التحكّم في جمع بيانات Google Analytics واستخدامها، بما في ذلك البيانات التي يتم إدخالها في سجلّات مسار التنقّل.

تفعيل ميزة إعداد التقارير التي تتطلّب الموافقة

تجمع Crashlytics تلقائيًا تقارير الأعطال لجميع مستخدمي تطبيقك. لمنح المستخدمين المزيد من التحكّم في البيانات التي يرسلونها، يمكنك تفعيل ميزة إعداد التقارير التي تتطلّب موافقة المستخدم من خلال إيقاف إعداد التقارير التلقائي وإرسال البيانات إلى Crashlytics فقط عندما تختار ذلك في الرمز البرمجي.

  1. أوقِف عملية الجمع التلقائي عن طريق إضافة مفتاح جديد إلى ملف Info.plist:

    • المفتاح: FirebaseCrashlyticsCollectionEnabled
    • القيمة: false
  2. يمكنك تفعيل عملية الجمع لمستخدمين محدّدين من خلال استدعاء Crashlytics الإعداد التلقائي لجمع البيانات في وقت التشغيل. تظل قيمة الإلغاء متوفّرة في جميع عمليات تشغيل تطبيقك اللاحقة، وبالتالي يمكن لـ Crashlytics جمع التقارير تلقائيًا لهذا المستخدم.

    Swift

    Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true)

    Objective-C

    [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:YES];

    إذا أوقف المستخدم لاحقًا خيار جمع البيانات، يمكنك تمرير false كقيمة الإلغاء، وسيتم تطبيقها في المرة التالية التي يشغّل فيها المستخدم التطبيق وستظل سارية في جميع عمليات التشغيل اللاحقة لهذا المستخدم.

إدارة بيانات "إحصاءات الأعطال"

تساعدك ميزة "إحصاءات الأعطال" في حلّ المشاكل من خلال مقارنة عمليات تتبُّع تسلسل استدعاء الدوال البرمجية المخفية الهوية بعمليات التتبُّع من تطبيقات Firebase الأخرى، وإعلامك إذا كانت مشكلتك جزءًا من مؤشر أكبر. بالنسبة إلى العديد من المشاكل، توفّر ميزة "إحصاءات الأعطال" موارد لمساعدتك في تصحيح الأخطاء.

تستخدِم ميزة "إحصاءات الأعطال" بيانات الأعطال المجمّعة لتحديد مؤشرات الثبات الشائعة. إذا كنت تفضّل عدم مشاركة بيانات تطبيقك، يمكنك إيقاف ميزة "إحصاءات الأعطال" من قائمة إحصاءات الأعطال في أعلى قائمة المشاكل في لوحة بيانات DevOps ومعدّل الاهتمام بالتطبيق > Crashlytics في Firebase.

الخطوات التالية