logcat.js
3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import _ from 'lodash';
import { SubProcess, exec } from 'teen_process';
import { logger } from 'appium-support';
import B from 'bluebird';
import events from 'events';
const { EventEmitter } = events;
const log = logger.getLogger('Logcat');
const MAX_BUFFER_SIZE = 10000;
const LOGCAT_PROC_STARTUP_TIMEOUT = 10000;
class Logcat extends EventEmitter {
constructor (opts = {}) {
super();
this.adb = opts.adb;
this.clearLogs = opts.clearDeviceLogsOnStart || false;
this.debug = opts.debug;
this.debugTrace = opts.debugTrace;
this.maxBufferSize = opts.maxBufferSize || MAX_BUFFER_SIZE;
this.logs = [];
this.logIdxSinceLastRequest = 0;
}
async startCapture () {
let started = false;
return await new B(async (_resolve, _reject) => { // eslint-disable-line promise/param-names
const resolve = function (...args) {
started = true;
_resolve(...args);
};
const reject = function (...args) {
started = true;
_reject(...args);
};
if (this.clearLogs) {
await this.clear();
}
log.debug('Starting logcat capture');
this.proc = new SubProcess(this.adb.path, this.adb.defaultArgs.concat(['logcat', '-v', 'threadtime']));
this.proc.on('exit', (code, signal) => {
log.error(`Logcat terminated with code ${code}, signal ${signal}`);
this.proc = null;
if (!started) {
log.warn('Logcat not started. Continuing');
resolve();
}
});
this.proc.on('lines-stderr', (lines) => {
for (let line of lines) {
if (/execvp\(\)/.test(line)) {
log.error('Logcat process failed to start');
reject(new Error(`Logcat process failed to start. stderr: ${line}`));
}
this.outputHandler(_.trim(line), 'STDERR: ');
}
resolve();
});
this.proc.on('lines-stdout', (lines) => {
resolve();
for (let line of lines) {
this.outputHandler(_.trim(line));
}
});
await this.proc.start(0);
// resolve after a timeout, even if no output was recorded
setTimeout(resolve, LOGCAT_PROC_STARTUP_TIMEOUT);
});
}
outputHandler (output, prefix = '') {
if (!output) {
return;
}
if (this.logs.length >= this.maxBufferSize) {
this.logs.shift();
if (this.logIdxSinceLastRequest > 0) {
--this.logIdxSinceLastRequest;
}
}
const outputObj = {
timestamp: Date.now(),
level: 'ALL',
message: output,
};
this.logs.push(outputObj);
this.emit('output', outputObj);
const isTrace = /W\/Trace/.test(output);
if (this.debug && (!isTrace || this.debugTrace)) {
log.debug(prefix + output);
}
}
async stopCapture () {
log.debug('Stopping logcat capture');
if (!this.proc || !this.proc.isRunning) {
log.debug('Logcat already stopped');
this.proc = null;
return;
}
this.proc.removeAllListeners('exit');
await this.proc.stop();
this.proc = null;
}
getLogs () {
if (this.logIdxSinceLastRequest < this.logs.length) {
const result = this.logs.slice(this.logIdxSinceLastRequest);
this.logIdxSinceLastRequest = this.logs.length;
return result;
}
return [];
}
getAllLogs () {
return this.logs;
}
async clear () {
log.debug('Clearing logcat logs from device');
try {
const args = this.adb.defaultArgs.concat(['logcat', '-c']);
log.debug(`Running '${this.adb.path} ${args.join(' ')}'`);
await exec(this.adb.path, args);
} catch (err) {
log.warn(`Failed to clear logcat logs: ${err.message}`);
}
}
}
export default Logcat;