Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a possibility to customize logcat filtering #528

Merged
merged 2 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 61 additions & 9 deletions lib/logcat.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,57 @@
import _ from 'lodash';
import { SubProcess, exec } from 'teen_process';
import { logger } from 'appium-support';
import { logger, util } from 'appium-support';
import B from 'bluebird';
import events from 'events';
const { EventEmitter } = events;

import { SubProcess, exec } from 'teen_process';

const log = 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';

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 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) => _.toLower(priority) === _.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 (!_.isArray(filterSpecs)) {
filterSpecs = [filterSpecs];
}
return filterSpecs
.filter((spec) => spec && _.isString(spec) && !spec.startsWith('-'))
.map((spec) => spec.includes(':') ? requireSpec(spec) : spec);
}


class Logcat extends EventEmitter {
constructor (opts = {}) {
Expand All @@ -22,7 +65,7 @@ class Logcat extends EventEmitter {
this.logIdxSinceLastRequest = 0;
}

async startCapture () {
async startCapture (opts = {}) {
let started = false;
return await new B(async (_resolve, _reject) => { // eslint-disable-line promise/param-names
const resolve = function (...args) {
Expand All @@ -38,8 +81,18 @@ class Logcat extends EventEmitter {
await this.clear();
}

log.debug('Starting logcat capture');
this.proc = new SubProcess(this.adb.path, this.adb.defaultArgs.concat(['logcat', '-v', 'threadtime']));
const {
format = DEFAULT_FORMAT,
filterSpecs = [],
} = opts;
const cmd = [
...this.adb.defaultArgs,
'logcat',
'-v', requireFormat(format),
...formatFilterSpecs(filterSpecs),
];
log.debug(`Starting logs capture with command: ${util.quote([this.adb.path, ...cmd])}`);
this.proc = new SubProcess(this.adb.path, cmd);
this.proc.on('exit', (code, signal) => {
log.error(`Logcat terminated with code ${code}, signal ${signal}`);
this.proc = null;
Expand Down Expand Up @@ -122,11 +175,10 @@ class Logcat extends EventEmitter {
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(' ')}'`);
const args = [...this.adb.defaultArgs, 'logcat', '-c'];
await exec(this.adb.path, args);
} catch (err) {
log.warn(`Failed to clear logcat logs: ${err.message}`);
log.warn(`Failed to clear logcat logs: ${err.stderr || err.message}`);
}
}
}
Expand Down
29 changes: 26 additions & 3 deletions lib/tools/adb-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -979,22 +979,45 @@ methods.restart = async function restart () {
}
};

/**
* @typedef {Object} LogcatOpts
* @property {string} format The log print format, where <format> is one of:
* brief process tag thread raw time threadtime long
* `threadtime` is the default value.
* @property {Array<string>} filterSpecs Series of <tag>[:priority]
* where <tag> is a log component tag (or * for all) and priority is:
* V Verbose
* D Debug
* I Info
* W Warn
* E Error
* F Fatal
* S Silent (supress all output)
*
* '*' means '*:d' and <tag> by itself means <tag>:v
*
* If not specified on the commandline, filterspec is set from ANDROID_LOG_TAGS.
* If no filterspec is found, filter defaults to '*:I'
*/

/**
* Start the logcat process to gather logs.
*
* @throws {error} If restart fails.
* @param {?LogcatOpts} opts
* @throws {Error} If restart fails.
*/
methods.startLogcat = async function startLogcat () {
methods.startLogcat = async function startLogcat (opts = {}) {
if (!_.isEmpty(this.logcat)) {
throw new Error("Trying to start logcat capture but it's already started!");
}

this.logcat = new Logcat({
adb: this.executable,
debug: false,
debugTrace: false,
clearDeviceLogsOnStart: !!this.clearDeviceLogsOnStart,
});
await this.logcat.startCapture();
await this.logcat.startCapture(opts);
};

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/tools/system-calls.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ systemCallMethods.shell = async function shell (cmd, opts = {}) {

systemCallMethods.createSubProcess = function createSubProcess (args = []) {
// add the default arguments
args = this.executable.defaultArgs.concat(args);
args = [...this.executable.defaultArgs, ...args];
log.debug(`Creating ADB subprocess with args: ${JSON.stringify(args)}`);
return new SubProcess(this.getAdbPath(), args);
};
Expand Down
11 changes: 7 additions & 4 deletions test/unit/logcat-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { withMocks } from 'appium-test-support';
chai.use(chaiAsPromised);

describe('logcat', withMocks({teen_process}, function (mocks) {
let adb = {path: 'dummyPath', defaultArgs: []};
let logcat = new Logcat({adb, debug: false, debugTrace: false});
const adb = {path: 'dummyPath', defaultArgs: []};
const logcat = new Logcat({adb, debug: false, debugTrace: false});

afterEach(function () {
mocks.verify();
Expand All @@ -21,13 +21,16 @@ describe('logcat', withMocks({teen_process}, function (mocks) {
let conn = new events.EventEmitter();
conn.start = () => { };
mocks.teen_process.expects('SubProcess')
.withArgs('dummyPath', ['logcat', '-v', 'threadtime'])
.withArgs('dummyPath', ['logcat', '-v', 'brief', 'yolo2:d', '*:v'])
.onFirstCall()
.returns(conn);
setTimeout(function () {
conn.emit('lines-stdout', ['- beginning of system\r']);
}, 0);
await logcat.startCapture();
await logcat.startCapture({
format: 'brief',
filterSpecs: ['yolo2:d', ':k', '-asd:e'],
});
let logs = logcat.getLogs();
logs.should.have.length.above(0);
});
Expand Down