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
This commit is contained in:
committed by
Facebook Github Bot
parent
0f08b04571
commit
64ff6eb9cf
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
});
|
||||
}
|
||||
|
||||
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<IOSLogLevel, LogLevel> = 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),
|
||||
|
||||
Reference in New Issue
Block a user