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 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,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user