Reacciona el crash nativo en la producción

Creamos una aplicación con React Native para mejorar UX y las características de nuestra aplicación Cordova anterior.

Todo salió bien. Varios meses de desarrollo, control de calidad, revisión de la aplicación y luego publicados en App Store. Funcionó en todos los dispositivos que probamos, desde iPhone 4s hasta iPhone 6s +, probamos en iOS 8.3 (el simulador más antiguo que puedes download a través de xCode) a 10.0.

Después del lanzamiento, muchos usuarios comenzaron a informar que la aplicación se bloquea antes de que la pantalla de bienvenida se desvanezca. Comportamiento que no hemos visto en la revisión de la aplicación, testing o en cualquier otro lugar antes.

Investigamos los "lockings" en xCode y, obviamente, no aparecieron, porque cientos de usuarios experimentaron un locking y solo pudimos ver pocos, lo que parecía no estar relacionado con el inicio.

Lanzamos una versión actualizada con Crashlytics integrada, pero eso tampoco ayudó. Tampoco tenemos errores de Crashlytics para este problema específico, lo que significa que probablemente el problema ocurra antes

¿Alguna idea de dónde debo mirar a continuación? Realmente no queremos volver a la versión anterior y perder meses de trabajo.

La aplicación utiliza alnetworkingedor de ~ 100 MB de memory cuando todo está cargado, por lo que no debería ser un problema, supongo. Está sucediendo en todas las versiones de iOS en todos los dispositivos. No podemos aislar el error solo para usuarios específicos.

Cuando no parece haber otra vía de análisis, recurro a la humilde caída de la registración.

Anteriormente he usado la siguiente técnica en aplicaciones iOS de producción. Esto es un poco de trabajo para configurar, pero una vez que funciona es tremendamente útil para muchos otros problemas en el futuro. No solo los lockings, sino cualquier otro comportamiento extraño que informan los usuarios que no puede replicar en sus entornos de testing.

  1. Lo primero que debe hacer la aplicación es comprobar si el inicio PREVIOUS fue exitoso o no al leer algunos valores que deberían haberse escrito para los valores pnetworkingeterminados al principio y al final del inicio anterior (detalles en el próximo paso). Si el inicio PREVIOUS NO fue exitoso, déle al usuario la opción de ejecutarse en algún tipo de 'modo seguro' (lo que esto significa depende de lo que su aplicación intente hacer al inicio, pero para mí no significa cargar ningún dato o hacerlo cualquier cosa, aparte de mostrar la interfaz de usuario sin elementos dependientes de datos, para algunas aplicaciones, incluso podría llegar a cargar una interfaz de usuario completamente diferente que incluye solo herramientas de diagnóstico o herramientas de eliminación / restauración de datos).
  2. Lo siguiente que debería hacer la aplicación después de determinar que el inicio anterior estaba bien (o que este es el primer inicio) es escribir algún tipo de estado "startupBegan" a los valores pnetworkingeterminados tan pronto como sea posible y luego más adelante algún tipo de " startupCompleted "solo cuando ha finalizado el inicio (lo que significa" inicio completamente completado "es dependiente de la aplicación, pero realmente quiere estar seguro de que la interfaz de usuario es totalmente receptiva en este momento y muestra todo lo que necesita, esto puede ser un poco difícil de determinar a veces, ya que algunas cosas no se ejecutan hasta después de que la pantalla de bienvenida ha desaparecido, etc., si no puede encontrar otra forma, supongo que puede activar esto con un timer, pero sería bastante feo, es mejor encontrar alguna forma de determinar cuándo el inicio realmente está completo). Estos valores pueden usarse para determinar si el inicio comenzó, pero no se completó y son los pasos 1 (arriba) para determinar si el inicio anterior fue exitoso o no.
  3. Incluya muchos loggings en la aplicación y escriba loggings en un file. Creo que hay herramientas de terceros que puede get para hacer esto, pero escribí mi propio método (a continuación) que simplemente networkingirige stderr a un file si se ejecuta en producción y no está conectado a XCode. Tenga en count que NSLog() escribe en stderr, no en stdout.
  4. Ofrezca a la aplicación la posibilidad de enviar files de logging por correo electrónico a su dirección de correo electrónico de soporte; esto debe estar disponible en el "modo seguro" de la aplicación (así como en el modo normal). En modo normal, hago esto bastante oscuro para que el usuario no se dé count cuando todo está bien (p. Ej., Un button en la parte inferior de una vista 'Configuración' o 'Acerca de'). Le digo al usuario cómo encontrar el button cuando han enviado una request de soporte para la que realmente necesito los loggings.
  5. Rota los loggings en cada inicio para evitar que consumn demasiado espacio, pero asegúrate de mantener algunas rotaciones, de lo contrario solo obtendrás el logging del inicio del modo seguro, lo cual es inútil.

Muchas variaciones en esto son posibles. Incluyendo cosas como solo habilitar el logging si el usuario ha configurado una configuration para ello. Es posible que tenga que agregar una gran cantidad de loggings alnetworkingedor de áreas particulares de código a veces cuando un usuario informa un problema en particular y luego lo elimina nuevamente después de resolver el problema (si le preocupan los problemas de performance / almacenamiento relacionados con el logging).

Para mi aplicación (Objective-C), los lugares para include mi código para escribir el estado de inicio a los valores pnetworkingeterminados fueron los siguientes (puede haber mejores lugares más adecuados para su aplicación):

  • "startupBegan" al comienzo de la aplicación del delegado de la application:didFinishLaunchingWithOptions:
  • "startupCompleted" al final de la vista del controllerDidAppear (NO viewWillAppear ! hay muchas cosas que pueden salir mal entre estos dos enviados)

PD. Mi viejo método de networkingirección y rotation de loggings era algo así (Objective-C):

 - (void)logRedirectRotate { // If stderr not going to an XCode console (then running in production) if ( ! isatty(STDERR_FILENO) ) { // Rotate logs int rotationsCount = 3; NSMutableArray *logRotations = [NSMutableArray array]; for ( int i = 0; i < rotationsCount; i++ ) { [logRotations addObject:[pathToLogsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"appnameorbundleid.%d.log", i]]]; } [[NSFileManager defaultManager] removeItemAtPath:[logRotations lastObject] error:nil]; for ( int i = rotationsCount - 1; i > 0; i-- ) { [[NSFileManager defaultManager] moveItemAtPath:[logRotations objectAtIndex:i - 1] toPath:[logRotations objectAtIndex:i] error:nil]; } // Redirect stderr to current log file rotation freopen([[logRotations objectAtIndex:0] cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr); } } 

El problema tardó tanto en resolverse debido a la mala comunicación entre nosotros y nuestros usuarios. La aplicación en realidad NO se bloquea , simplemente no se inicia (lo que es lo mismo para algunos usuarios).

Después de que descubrimos eso, nos dimos count de que uno de los events no se está activando (el que oculta la pantalla de bienvenida extendida) y aquí es donde los usuarios se quedaron atrapados. Una de las bibliotecas que estábamos utilizando no manejaba correctamente el escenario de error y hacía que nuestro trabajo fuera mucho más difícil. Tuve la suerte de entrar en ese estado mientras probaba y podía continuar desde allí.

Actualicé el código para manejar ese escenario y el problema ahora está resuelto.

Tuve este problema y mi caso puede ser bastante específico pero compartiré mi experiencia de todos modos.

El punto aquí es que la aplicación solo falla en la producción, por lo que es algo que se dispara solo en la producción que está dañando la compilation. En nuestro caso, el culpable fue una de las dependencies de React que hace minificación.

Cosas para llevar:

  1. Vigile las actualizaciones de sus dependencies.
  2. Usar logging de locking