appium-adb
Version:
Android Debug Bridge interface
216 lines • 7.66 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logcat = void 0;
const support_1 = require("@appium/support");
const bluebird_1 = __importDefault(require("bluebird"));
const lodash_1 = __importDefault(require("lodash"));
const node_events_1 = require("node:events");
const teen_process_1 = require("teen_process");
const lru_cache_1 = require("lru-cache");
const log = support_1.logger.getLogger('Logcat');
const MAX_BUFFER_SIZE = 10000;
const LOGCAT_PROC_STARTUP_TIMEOUT = 10000;
const SUPPORTED_FORMATS = [
'brief',
'process',
'tag',
'thread',
'raw',
'time',
'threadtime',
'long',
];
const SUPPORTED_PRIORITIES = ['v', 'd', 'i', 'w', 'e', 'f', 's'];
const DEFAULT_PRIORITY = 'v';
const DEFAULT_TAG = '*';
const DEFAULT_FORMAT = 'threadtime';
const TRACE_PATTERN = /W\/Trace/;
const EXECVP_ERR_PATTERN = /execvp\(\)/;
class Logcat extends node_events_1.EventEmitter {
adb;
clearLogs;
debug;
debugTrace;
maxBufferSize;
logs;
logIndexSinceLastRequest;
proc;
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 = new lru_cache_1.LRUCache({
max: this.maxBufferSize,
});
this.logIndexSinceLastRequest = null;
this.proc = null;
}
async startCapture(opts = {}) {
let started = false;
return await new bluebird_1.default(async (_resolve, _reject) => {
const resolve = function (...args) {
started = true;
_resolve(...args);
};
const reject = function (...args) {
started = true;
_reject(...args);
};
if (this.clearLogs) {
await this.clear();
}
const { format = DEFAULT_FORMAT, filterSpecs = [] } = opts;
const cmd = [
...this.adb.defaultArgs,
'logcat',
'-v',
requireFormat(format),
...formatFilterSpecs(filterSpecs),
];
log.debug(`Starting logs capture with command: ${support_1.util.quote([this.adb.path, ...cmd])}`);
this.proc = new teen_process_1.SubProcess(this.adb.path, cmd);
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('line-stderr', (line) => {
if (!started && EXECVP_ERR_PATTERN.test(line)) {
log.error('Logcat process failed to start');
return reject(new Error(`Logcat process failed to start. stderr: ${line}`));
}
this.outputHandler(line, 'STDERR: ');
resolve();
});
this.proc.on('line-stdout', (line) => {
this.outputHandler(line);
resolve();
});
await this.proc.start(0);
// resolve after a timeout, even if no output was recorded
setTimeout(resolve, LOGCAT_PROC_STARTUP_TIMEOUT);
});
}
async stopCapture() {
log.debug('Stopping logcat capture');
if (!this.proc?.isRunning) {
log.debug('Logcat already stopped');
this.proc = null;
return;
}
this.proc.removeAllListeners('exit');
await this.proc.stop();
this.proc = null;
}
getLogs() {
const result = [];
let recentLogIndex = null;
for (const entry of this.logs.rentries()) {
const [index, value] = entry;
if (typeof index !== 'number' || !Array.isArray(value)) {
continue;
}
const [message, timestamp] = value;
if ((this.logIndexSinceLastRequest && index > this.logIndexSinceLastRequest) ||
!this.logIndexSinceLastRequest) {
recentLogIndex = index;
result.push(toLogEntry(message, timestamp));
}
}
if (lodash_1.default.isInteger(recentLogIndex)) {
this.logIndexSinceLastRequest = recentLogIndex;
}
return result;
}
getAllLogs() {
const result = [];
for (const value of this.logs.rvalues()) {
if (!Array.isArray(value)) {
continue;
}
const [message, timestamp] = value;
result.push(toLogEntry(message, timestamp));
}
return result;
}
async clear() {
log.debug('Clearing logcat logs from device');
try {
const args = [...this.adb.defaultArgs, 'logcat', '-c'];
await (0, teen_process_1.exec)(this.adb.path, args);
}
catch (err) {
const execErr = err;
log.warn(`Failed to clear logcat logs: ${execErr.stderr || execErr.message}`);
}
}
outputHandler(logLine, prefix = '') {
const timestamp = Date.now();
let recentIndex = -1;
for (const key of this.logs.keys()) {
recentIndex = key;
break;
}
this.logs.set(++recentIndex, [logLine, timestamp]);
if (this.listenerCount('output')) {
this.emit('output', toLogEntry(logLine, timestamp));
}
if (this.debug && (this.debugTrace || !TRACE_PATTERN.test(logLine))) {
log.debug(prefix + logLine);
}
}
}
exports.Logcat = Logcat;
exports.default = Logcat;
function requireFormat(format) {
if (!SUPPORTED_FORMATS.includes(format)) {
log.info(`The format value '${format}' is unknown. Supported values are: ${SUPPORTED_FORMATS}`);
log.info(`Defaulting to '${DEFAULT_FORMAT}'`);
return DEFAULT_FORMAT;
}
return format;
}
function toLogEntry(message, timestamp) {
return {
timestamp,
level: 'ALL',
message,
};
}
function requireSpec(spec) {
const [tag, priority] = spec.split(':');
let resultTag = tag;
if (!resultTag) {
log.info(`The tag value in spec '${spec}' cannot be empty`);
log.info(`Defaulting to '${DEFAULT_TAG}'`);
resultTag = DEFAULT_TAG;
}
if (!priority) {
log.info(`The priority value in spec '${spec}' is empty. Defaulting to Verbose (${DEFAULT_PRIORITY})`);
return `${resultTag}:${DEFAULT_PRIORITY}`;
}
if (!SUPPORTED_PRIORITIES.some((p) => lodash_1.default.toLower(priority) === lodash_1.default.toLower(p))) {
log.info(`The priority value in spec '${spec}' is unknown. Supported values are: ${SUPPORTED_PRIORITIES}`);
log.info(`Defaulting to Verbose (${DEFAULT_PRIORITY})`);
return `${resultTag}:${DEFAULT_PRIORITY}`;
}
return spec;
}
function formatFilterSpecs(filterSpecs) {
if (!lodash_1.default.isArray(filterSpecs)) {
filterSpecs = [filterSpecs];
}
return filterSpecs
.filter((spec) => spec && lodash_1.default.isString(spec) && !spec.startsWith('-'))
.map((spec) => (spec.includes(':') ? requireSpec(spec) : spec));
}
//# sourceMappingURL=logcat.js.map