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
This commit is contained in:
Daniel Büchele
2018-08-31 10:02:43 -07:00
committed by Facebook Github Bot
parent 0f08b04571
commit 64ff6eb9cf
2 changed files with 54 additions and 29 deletions

View File

@@ -8,15 +8,24 @@
import type stream from 'stream'; import type stream from 'stream';
import {SonarDevicePlugin} from 'sonar'; import {SonarDevicePlugin} from 'sonar';
export type DeviceLogEntry = { export type LogLevel =
| 'unknown'
| 'verbose'
| 'debug'
| 'info'
| 'warn'
| 'error'
| 'fatal';
export type DeviceLogEntry = {|
date: Date, date: Date,
pid: number, pid: number,
tid: number, tid: number,
app?: string, app?: string,
type: 'unknown' | 'verbose' | 'debug' | 'info' | 'warn' | 'error' | 'fatal', type: LogLevel,
tag: string, tag: string,
message: string, message: string,
}; |};
export type DeviceShell = { export type DeviceShell = {
stdout: stream.Readable, stdout: stream.Readable,

View File

@@ -7,6 +7,7 @@
import type { import type {
DeviceType, DeviceType,
LogLevel,
DeviceLogEntry, DeviceLogEntry,
DeviceLogListener, DeviceLogListener,
} from './BaseDevice.js'; } from './BaseDevice.js';
@@ -15,11 +16,12 @@ import BaseDevice from './BaseDevice.js';
import JSONStream from 'JSONStream'; import JSONStream from 'JSONStream';
import {Transform} from 'stream'; import {Transform} from 'stream';
type RawLogEntry = { type IOSLogLevel = 'Default' | 'Info' | 'Debug' | 'Error' | 'Fault';
activityID: string, // Number in string format
type RawLogEntry = {|
eventMessage: string, eventMessage: string,
eventType: string,
machTimestamp: number, machTimestamp: number,
messageType: IOSLogLevel,
processID: number, processID: number,
processImagePath: string, processImagePath: string,
processImageUUID: string, processImageUUID: string,
@@ -28,10 +30,10 @@ type RawLogEntry = {
senderImageUUID: string, senderImageUUID: string,
senderProgramCounter: number, senderProgramCounter: number,
threadID: number, threadID: number,
timestamp: string, // "2017-09-27 16:21:15.771213-0400" timestamp: string,
timezoneName: string, timezoneName: string,
traceID: string, traceID: string,
}; |};
export default class IOSDevice extends BaseDevice { export default class IOSDevice extends BaseDevice {
supportedPlugins = ['DeviceLogs']; supportedPlugins = ['DeviceLogs'];
@@ -58,7 +60,11 @@ export default class IOSDevice extends BaseDevice {
return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time']; 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) { if (!this.log) {
this.log = child_process.spawn( this.log = child_process.spawn(
'xcrun', 'xcrun',
@@ -91,40 +97,50 @@ export default class IOSDevice extends BaseDevice {
}); });
} }
this.log.stdout try {
.pipe(new StripLogPrefix()) this.log.stdout
.pipe(JSONStream.parse('*')) .pipe(new StripLogPrefix())
.on('data', (data: RawLogEntry) => { .pipe(JSONStream.parse('*'))
callback(IOSDevice.parseLogEntry(data)); .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 { static parseLogEntry(entry: RawLogEntry): DeviceLogEntry {
let type = 'unknown'; const LOG_MAPPING: Map<IOSLogLevel, LogLevel> = new Map([
if (entry.eventMessage.indexOf('[debug]') !== -1) { ['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'; type = 'debug';
} else if (entry.eventMessage.indexOf('[info]') !== -1) { } else if (entry.eventMessage.startsWith('[info]')) {
type = 'info'; type = 'info';
} else if (entry.eventMessage.indexOf('[warn]') !== -1) { } else if (entry.eventMessage.startsWith('[warn]')) {
type = 'warn'; type = 'warn';
} else if (entry.eventMessage.indexOf('[error]') !== -1) { } else if (entry.eventMessage.startsWith('[error]')) {
type = '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 // remove type from mesage
entry.eventMessage = entry.eventMessage.replace( entry.eventMessage = entry.eventMessage.replace(
/^\[(debug|info|warn|error)\]/, /^\[(debug|info|warn|error)\]/,
'', '',
); );
const tags = entry.processImagePath.split('/'); const tag = entry.processImagePath.split('/').pop();
const tag = tags[tags.length - 1];
return { return {
date: new Date(entry.timestamp), date: new Date(entry.timestamp),