From 64ff6eb9cf1165232819395c24b468d564118ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=BCchele?= Date: Fri, 31 Aug 2018 10:02:43 -0700 Subject: [PATCH] iOS logs Summary: - Cleans up iOS log listener code - Retries to create a connection to the log stream if it failed - logs errors with the log parser Reviewed By: jknoxville Differential Revision: D9613055 fbshipit-source-id: 33a24e474be62fc2a906f022a68547594f2e66c1 --- src/devices/BaseDevice.js | 15 +++++++-- src/devices/IOSDevice.js | 68 ++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/devices/BaseDevice.js b/src/devices/BaseDevice.js index 1dcf589e8..a601cd521 100644 --- a/src/devices/BaseDevice.js +++ b/src/devices/BaseDevice.js @@ -8,15 +8,24 @@ import type stream from 'stream'; import {SonarDevicePlugin} from 'sonar'; -export type DeviceLogEntry = { +export type LogLevel = + | 'unknown' + | 'verbose' + | 'debug' + | 'info' + | 'warn' + | 'error' + | 'fatal'; + +export type DeviceLogEntry = {| date: Date, pid: number, tid: number, app?: string, - type: 'unknown' | 'verbose' | 'debug' | 'info' | 'warn' | 'error' | 'fatal', + type: LogLevel, tag: string, message: string, -}; +|}; export type DeviceShell = { stdout: stream.Readable, diff --git a/src/devices/IOSDevice.js b/src/devices/IOSDevice.js index 03fda8456..b74b89b83 100644 --- a/src/devices/IOSDevice.js +++ b/src/devices/IOSDevice.js @@ -7,6 +7,7 @@ import type { DeviceType, + LogLevel, DeviceLogEntry, DeviceLogListener, } from './BaseDevice.js'; @@ -15,11 +16,12 @@ import BaseDevice from './BaseDevice.js'; import JSONStream from 'JSONStream'; import {Transform} from 'stream'; -type RawLogEntry = { - activityID: string, // Number in string format +type IOSLogLevel = 'Default' | 'Info' | 'Debug' | 'Error' | 'Fault'; + +type RawLogEntry = {| eventMessage: string, - eventType: string, machTimestamp: number, + messageType: IOSLogLevel, processID: number, processImagePath: string, processImageUUID: string, @@ -28,10 +30,10 @@ type RawLogEntry = { senderImageUUID: string, senderProgramCounter: number, threadID: number, - timestamp: string, // "2017-09-27 16:21:15.771213-0400" + timestamp: string, timezoneName: string, traceID: string, -}; +|}; export default class IOSDevice extends BaseDevice { supportedPlugins = ['DeviceLogs']; @@ -58,7 +60,11 @@ export default class IOSDevice extends BaseDevice { return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time']; } - addLogListener(callback: DeviceLogListener) { + addLogListener(callback: DeviceLogListener, retries: number = 3) { + if (retries === 0) { + console.error('Attaching iOS log listener continuously failed.'); + return; + } if (!this.log) { this.log = child_process.spawn( 'xcrun', @@ -91,40 +97,50 @@ export default class IOSDevice extends BaseDevice { }); } - this.log.stdout - .pipe(new StripLogPrefix()) - .pipe(JSONStream.parse('*')) - .on('data', (data: RawLogEntry) => { - callback(IOSDevice.parseLogEntry(data)); - }); + try { + this.log.stdout + .pipe(new StripLogPrefix()) + .pipe(JSONStream.parse('*')) + .on('data', (data: RawLogEntry) => { + callback(IOSDevice.parseLogEntry(data)); + }); + } catch (e) { + console.error('Could not parse iOS log stream.', e); + // restart log stream + this.log.kill(); + this.log = null; + this.addLogListener(callback, retries - 1); + } } static parseLogEntry(entry: RawLogEntry): DeviceLogEntry { - let type = 'unknown'; - if (entry.eventMessage.indexOf('[debug]') !== -1) { + const LOG_MAPPING: Map = new Map([ + ['Default', 'debug'], + ['Info', 'info'], + ['Debug', 'debug'], + ['Error', 'error'], + ['Fault', 'fatal'], + ]); + let type: LogLevel = LOG_MAPPING.get(entry.messageType) || 'unknown'; + + // when Apple log levels are not used, log messages can be prefixed with + // their loglevel. + if (entry.eventMessage.startsWith('[debug]')) { type = 'debug'; - } else if (entry.eventMessage.indexOf('[info]') !== -1) { + } else if (entry.eventMessage.startsWith('[info]')) { type = 'info'; - } else if (entry.eventMessage.indexOf('[warn]') !== -1) { + } else if (entry.eventMessage.startsWith('[warn]')) { type = 'warn'; - } else if (entry.eventMessage.indexOf('[error]') !== -1) { + } else if (entry.eventMessage.startsWith('[error]')) { type = 'error'; } - - // remove timestamp in front of message - entry.eventMessage = entry.eventMessage.replace( - /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} /, - '', - ); - // remove type from mesage entry.eventMessage = entry.eventMessage.replace( /^\[(debug|info|warn|error)\]/, '', ); - const tags = entry.processImagePath.split('/'); - const tag = tags[tags.length - 1]; + const tag = entry.processImagePath.split('/').pop(); return { date: new Date(entry.timestamp),