log listener
Summary: The logs plugin opened a new log connection every time it was activated and never closed the connection. This is now changed. Once a device is connected, a log connection is opened. The logs plugin subscribes and unsubscribes to this connection. This allows the logs plugin it even access the logs from when it was not activated and ensures to only open on connection to read the logs. Logs are persisted when switching away from the plugin. Also removes the spinner from the logs plugin, as it loads much faster now. Reviewed By: jknoxville Differential Revision: D9613054 fbshipit-source-id: e37ea56c563450e7fc4e3c85a015292be1f2dbfc
This commit is contained in:
committed by
Facebook Github Bot
parent
a30e0b53e9
commit
afdc846a8b
@@ -5,7 +5,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {DeviceType, DeviceShell, DeviceLogListener} from './BaseDevice.js';
|
||||
import type {DeviceType, DeviceShell} from './BaseDevice.js';
|
||||
|
||||
import {Priority} from 'adbkit-logcat-fb';
|
||||
import child_process from 'child_process';
|
||||
@@ -25,6 +25,41 @@ export default class AndroidDevice extends BaseDevice {
|
||||
if (deviceType == 'physical') {
|
||||
this.supportedPlugins.push('DeviceCPU');
|
||||
}
|
||||
|
||||
this.adb.openLogcat(this.serial).then(reader => {
|
||||
reader.on('entry', entry => {
|
||||
if (this.logListeners.size > 0) {
|
||||
let type = 'unknown';
|
||||
if (entry.priority === Priority.VERBOSE) {
|
||||
type = 'verbose';
|
||||
}
|
||||
if (entry.priority === Priority.DEBUG) {
|
||||
type = 'debug';
|
||||
}
|
||||
if (entry.priority === Priority.INFO) {
|
||||
type = 'info';
|
||||
}
|
||||
if (entry.priority === Priority.WARN) {
|
||||
type = 'warn';
|
||||
}
|
||||
if (entry.priority === Priority.ERROR) {
|
||||
type = 'error';
|
||||
}
|
||||
if (entry.priority === Priority.FATAL) {
|
||||
type = 'fatal';
|
||||
}
|
||||
|
||||
this.notifyLogListeners({
|
||||
tag: entry.tag,
|
||||
pid: entry.pid,
|
||||
tid: entry.tid,
|
||||
message: entry.message,
|
||||
date: entry.date,
|
||||
type,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
supportedPlugins = [
|
||||
@@ -43,41 +78,6 @@ export default class AndroidDevice extends BaseDevice {
|
||||
return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time'];
|
||||
}
|
||||
|
||||
addLogListener(callback: DeviceLogListener) {
|
||||
this.adb.openLogcat(this.serial).then(reader => {
|
||||
reader.on('entry', async entry => {
|
||||
let type = 'unknown';
|
||||
if (entry.priority === Priority.VERBOSE) {
|
||||
type = 'verbose';
|
||||
}
|
||||
if (entry.priority === Priority.DEBUG) {
|
||||
type = 'debug';
|
||||
}
|
||||
if (entry.priority === Priority.INFO) {
|
||||
type = 'info';
|
||||
}
|
||||
if (entry.priority === Priority.WARN) {
|
||||
type = 'warn';
|
||||
}
|
||||
if (entry.priority === Priority.ERROR) {
|
||||
type = 'error';
|
||||
}
|
||||
if (entry.priority === Priority.FATAL) {
|
||||
type = 'fatal';
|
||||
}
|
||||
|
||||
callback({
|
||||
tag: entry.tag,
|
||||
pid: entry.pid,
|
||||
tid: entry.tid,
|
||||
message: entry.message,
|
||||
date: entry.date,
|
||||
type,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
reverse(): Promise<void> {
|
||||
if (this.deviceType === 'physical') {
|
||||
return this.adb
|
||||
|
||||
@@ -62,6 +62,9 @@ export default class BaseDevice {
|
||||
// possible src of icon to display next to the device title
|
||||
icon: ?string;
|
||||
|
||||
logListeners: Map<Symbol, DeviceLogListener> = new Map();
|
||||
logEntries: Array<DeviceLogEntry> = [];
|
||||
|
||||
supportsOS(os: string) {
|
||||
return os.toLowerCase() === this.os.toLowerCase();
|
||||
}
|
||||
@@ -80,8 +83,22 @@ export default class BaseDevice {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
addLogListener(listener: DeviceLogListener) {
|
||||
throw new Error('unimplemented');
|
||||
addLogListener(callback: DeviceLogListener): Symbol {
|
||||
const id = Symbol();
|
||||
this.logListeners.set(id, callback);
|
||||
this.logEntries.map(callback);
|
||||
return id;
|
||||
}
|
||||
|
||||
notifyLogListeners(entry: DeviceLogEntry) {
|
||||
this.logEntries.push(entry);
|
||||
if (this.logListeners.size > 0) {
|
||||
this.logListeners.forEach(listener => listener(entry));
|
||||
}
|
||||
}
|
||||
|
||||
removeLogListener(id: Symbol) {
|
||||
this.logListeners.delete(id);
|
||||
}
|
||||
|
||||
spawnShell(): DeviceShell {
|
||||
|
||||
@@ -5,12 +5,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {
|
||||
DeviceType,
|
||||
LogLevel,
|
||||
DeviceLogEntry,
|
||||
DeviceLogListener,
|
||||
} from './BaseDevice.js';
|
||||
import type {DeviceType, LogLevel, DeviceLogEntry} from './BaseDevice.js';
|
||||
import child_process from 'child_process';
|
||||
import BaseDevice from './BaseDevice.js';
|
||||
import JSONStream from 'JSONStream';
|
||||
@@ -47,7 +42,7 @@ export default class IOSDevice extends BaseDevice {
|
||||
super(serial, deviceType, title);
|
||||
|
||||
this.buffer = '';
|
||||
this.log = null;
|
||||
this.log = this.startLogListener();
|
||||
}
|
||||
|
||||
teardown() {
|
||||
@@ -60,7 +55,7 @@ export default class IOSDevice extends BaseDevice {
|
||||
return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time'];
|
||||
}
|
||||
|
||||
addLogListener(callback: DeviceLogListener, retries: number = 3) {
|
||||
startLogListener(retries: number = 3) {
|
||||
if (retries === 0) {
|
||||
console.error('Attaching iOS log listener continuously failed.');
|
||||
return;
|
||||
@@ -102,14 +97,15 @@ export default class IOSDevice extends BaseDevice {
|
||||
.pipe(new StripLogPrefix())
|
||||
.pipe(JSONStream.parse('*'))
|
||||
.on('data', (data: RawLogEntry) => {
|
||||
callback(IOSDevice.parseLogEntry(data));
|
||||
const entry = IOSDevice.parseLogEntry(data);
|
||||
this.notifyLogListeners(entry);
|
||||
});
|
||||
} 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);
|
||||
this.startLogListener(retries - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {
|
||||
DeviceType,
|
||||
DeviceLogEntry,
|
||||
DeviceLogListener,
|
||||
} from './BaseDevice.js';
|
||||
import type {DeviceType, DeviceLogEntry} from './BaseDevice.js';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
@@ -40,6 +36,8 @@ export default class OculusDevice extends BaseDevice {
|
||||
|
||||
this.watcher = null;
|
||||
this.processedFileMap = {};
|
||||
|
||||
this.setupListener();
|
||||
}
|
||||
|
||||
teardown() {
|
||||
@@ -69,63 +67,63 @@ export default class OculusDevice extends BaseDevice {
|
||||
}
|
||||
}
|
||||
|
||||
processText(text: Buffer, callback: DeviceLogListener) {
|
||||
processText(text: Buffer) {
|
||||
text
|
||||
.toString()
|
||||
.split('\r\n')
|
||||
.forEach(line => {
|
||||
const regex = /(.*){(\S+)}\s*\[([\w :.\\]+)\](.*)/;
|
||||
const match = regex.exec(line);
|
||||
let entry;
|
||||
if (match && match.length === 5) {
|
||||
callback({
|
||||
entry = {
|
||||
tid: 0,
|
||||
pid: 0,
|
||||
date: new Date(Date.parse(match[1])),
|
||||
type: this.mapLogLevel(match[2]),
|
||||
tag: match[3],
|
||||
message: match[4],
|
||||
});
|
||||
};
|
||||
} else if (line.trim() === '') {
|
||||
// skip
|
||||
} else {
|
||||
callback({
|
||||
entry = {
|
||||
tid: 0,
|
||||
pid: 0,
|
||||
date: new Date(),
|
||||
type: 'verbose',
|
||||
tag: 'failed-parse',
|
||||
message: line,
|
||||
});
|
||||
};
|
||||
}
|
||||
if (entry) {
|
||||
this.notifyLogListeners(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addLogListener = (callback: DeviceLogListener) => {
|
||||
this.setupListener(callback);
|
||||
};
|
||||
|
||||
async setupListener(callback: DeviceLogListener) {
|
||||
async setupListener() {
|
||||
const files = await fs.readdir(getLogsPath());
|
||||
this.watchedFile = files
|
||||
.filter(file => file.startsWith('Service_'))
|
||||
.sort()
|
||||
.pop();
|
||||
this.watch(callback);
|
||||
this.timer = setTimeout(() => this.checkForNewLog(callback), 5000);
|
||||
this.watch();
|
||||
this.timer = setTimeout(() => this.checkForNewLog(), 5000);
|
||||
}
|
||||
|
||||
watch(callback: DeviceLogListener) {
|
||||
watch() {
|
||||
const filePath = getLogsPath(this.watchedFile);
|
||||
fs.watchFile(filePath, async (current, previous) => {
|
||||
const readLen = current.size - previous.size;
|
||||
const buffer = new Buffer(readLen);
|
||||
const fd = await fs.open(filePath, 'r');
|
||||
await fs.read(fd, buffer, 0, readLen, previous.size);
|
||||
this.processText(buffer, callback);
|
||||
this.processText(buffer);
|
||||
});
|
||||
}
|
||||
|
||||
async checkForNewLog(callback: DeviceLogListener) {
|
||||
async checkForNewLog() {
|
||||
const files = await fs.readdir(getLogsPath());
|
||||
const latestLog = files
|
||||
.filter(file => file.startsWith('Service_'))
|
||||
@@ -135,8 +133,8 @@ export default class OculusDevice extends BaseDevice {
|
||||
const oldFilePath = getLogsPath(this.watchedFile);
|
||||
fs.unwatchFile(oldFilePath);
|
||||
this.watchedFile = latestLog;
|
||||
this.watch(callback);
|
||||
this.watch();
|
||||
}
|
||||
this.timer = setTimeout(() => this.checkForNewLog(callback), 5000);
|
||||
this.timer = setTimeout(() => this.checkForNewLog(), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {DeviceLogListener} from './BaseDevice.js';
|
||||
import BaseDevice from './BaseDevice.js';
|
||||
|
||||
export default class WindowsDevice extends BaseDevice {
|
||||
@@ -22,6 +21,4 @@ export default class WindowsDevice extends BaseDevice {
|
||||
supportedColumns(): Array<string> {
|
||||
return [];
|
||||
}
|
||||
|
||||
addLogListener(_callback: DeviceLogListener) {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user