first commit

This commit is contained in:
2026-09-08 20:28:54 +08:00
commit 2ada8d3d5a
37380 changed files with 4886169 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
Copyright © 2013 CyberAgent, Inc.
Copyright © 2016 The OpenSTF Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+237
View File
@@ -0,0 +1,237 @@
# adbkit-logcat
**adbkit-logcat** provides a [Node.js][nodejs] interface for working with output produced by the Android [`logcat` tool][logcat-site]. It takes a log stream (that you must create separately), parses it, and emits log entries in real-time as they occur. Possible use cases include storing logs in a database, forwarding logs via [MessagePack][msgpack], or just advanced filtering.
## Getting started
Install via NPM:
```bash
npm install --save adbkit-logcat
```
Note that while adbkit-logcat is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.
### Examples
#### Output all log messages
##### JavaScript
```javascript
var logcat = require('adbkit-logcat');
var spawn = require('child_process').spawn;
// Retrieve a binary log stream
var proc = spawn('adb', ['logcat', '-B']);
// Connect logcat to the stream
reader = logcat.readStream(proc.stdout);
reader.on('entry', function(entry) {
console.log(entry.message);
});
// Make sure we don't leave anything hanging
process.on('exit', function() {
proc.kill();
});
```
##### CoffeeScript
```coffeescript
Logcat = require 'adbkit-logcat'
{spawn} = require 'child_process'
# Retrieve a binary log stream
proc = spawn 'adb', ['logcat', '-B']
# Connect logcat to the stream
reader = Logcat.readStream proc.stdout
reader.on 'entry', (entry) ->
console.log entry.message
# Make sure we don't leave anything hanging
process.on 'exit', ->
proc.kill()
```
## API
### Logcat
#### logcat.Priority
Exposes `Priority`. See below for details.
#### logcat.Reader
Exposes `Reader`. See below for details.
#### logcat.readStream(stream[, options])
Creates a logcat reader instance from the provided logcat event [`Stream`][node-stream]. Note that you must create the stream separately.
* **stream** The event stream to read.
* **options** Optional. The following options are supported:
- **format** The format of the stream. Currently, the only supported value is `'binary'`, which (for example) `adb logcat -B` produces. Defaults to `'binary'`.
- **fixLineFeeds** All programs run via the ADB shell transform any `'\n'` in the output to `'\r\n'`, which breaks binary content. If set, this option reverses the transformation before parsing the stream. Defaults to `true`.
* Returns: The `Reader` instance.
### Priority
#### Constants
The following static properties are available:
* **Priority.UNKNOWN** i.e. `0`.
* **Priority.DEFAULT** i.e. `1`. Not available when reading a stream.
* **Priority.VERBOSE** i.e. `2`.
* **Priority.DEBUG** i.e. `3`.
* **Priority.INFO** i.e. `4`.
* **Priority.WARN** i.e. `5`.
* **Priority.ERROR** i.e. `6`.
* **Priority.FATAL** i.e. `7`.
* **Priority.SILENT** i.e. `8`. Not available when reading a stream.
#### Priority.fromLetter(letter)
Static method to convert the given `letter` into a numeric priority. For example, `Priority.fromName('d')` would return `Priority.DEBUG`.
* **letter** The priority as a `String`. Any single, case-insensitive character matching the first character of any `Priority` constant is accepted.
* Returns: The priority as a `Number`, or `undefined`.
#### Priority.fromName(name)
Static method to convert the given `name` into a numeric priority. For example, `Priority.fromName('debug')` (or `Priority.fromName('d')`) would return `Priority.DEBUG`.
* **name** The priority as a `String`. Any full, case-insensitive match of the `Priority` constants is accepted. If no match is found, falls back to `Priority.fromLetter()`.
* Returns: The priority as a `Number`, or `undefined`.
#### Priority.toLetter(priority)
Static method to convert the numeric priority into its letter representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'D'`.
* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
* Returns: The priority as a `String` letter, or `undefined`.
#### Priority.toName(priority)
Static method to convert the numeric priority into its full string representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'DEBUG'`.
* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
* Returns: The priority as a `String`, or `undefined`.
### Reader
A reader instance, which is an [`EventEmitter`][node-events].
#### Events
The following events are available:
* **error** **(err)** Emitted when an error occurs.
* **err** An `Error`.
* **end** Emitted when the stream ends.
* **finish** Emitted when the stream finishes.
* **entry** **(entry)** Emitted when the stream finishes.
* **entry** A log `Entry`. See below for details.
#### constructor([options])
For advanced users. Manually constructs a `Reader` instance. Useful for testing and/or playing around. Normally you would use `logcat.readStream()` to create the instance.
* **options** See `logcat.readStream()` for details.
* Returns: N/A
#### reader.connect(stream)
For advanced users. When instantiated manually (not via `logcat.readStream()`), connects the `Reader` instance to the given stream.
* **stream** See `logcat.readStream()` for details.
* Returns: The `Reader` instance.
#### reader.end()
Convenience method for ending the stream.
* Returns: The `Reader` instance.
#### reader.exclude(tag)
Skip entries with the provided tag. Alias for `reader.include(tag, Priority.SILENT)`. Note that even skipped events have to be parsed so that they can be ignored.
* **tag** The tag string to exclude. If `'*'`, works the same as `reader.excludeAll()`.
* Returns: The `Reader` instance.
#### reader.excludeAll()
Skip **ALL** entries. Alias for `reader.includeAll(Priority.SILENT)`. Any entries you wish to see must be included via `include()`/`includeAll()`.
* Returns: The `Reader` instance.
#### reader.include(tag[, priority])
Include all entries with the given tag and a priority higher or equal to the given `priority`.
* **tag** The tag string to include. If `'*'`, works the same as `reader.includeAll(priority)`.
* **priority** Optional. A lower bound for the priority. Any numeric `Priority` constant or any `String` value accepted by `Priority.fromName()` is accepted. Defaults to `Priority.DEBUG`.
* Returns: The `Reader` instance.
#### reader.includeAll([priority])
Include all entries with a priority higher or equal to the given `priority`.
* **tag** The tag string to exclude.
* **priority** Optional. See `reader.include()` for details.
* Returns: The `Reader` instance.
#### reader.resetFilters()
Resets all inclusions/exclusions.
* Returns: The `Reader` instance.
### Entry
A log entry.
#### Properties
The following properties are available:
* **date** Event time as a `Date`.
* **pid** Process ID as a `Number`.
* **tid** Thread ID as a `Number`.
* **priority** Event priority as a `Number`. You can use `logcat.Priority` to convert the value into a `String`.
* **tag** Event tag as a `String`.
* **message** Message as a `String`.
#### entry.toBinary()
Converts the entry back to the binary log format.
* Returns: The binary event as a [`Buffer`][node-buffer].
## More information
* [logprint.c](https://github.com/android/platform_system_core/blob/master/liblog/logprint.c)
* [logcat.cpp](https://github.com/android/platform_system_core/blob/master/logcat/logcat.cpp)
* [logger.h](https://github.com/android/platform_system_core/blob/master/include/log/logger.h)
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
See [LICENSE](LICENSE).
Copyright © The OpenSTF Project. All Rights Reserved.
[nodejs]: <http://nodejs.org/>
[msgpack]: <http://msgpack.org/>
[logcat-site]: <http://developer.android.com/tools/help/logcat.html>
[node-stream]: <http://nodejs.org/api/stream.html>
[node-events]: <http://nodejs.org/api/events.html>
[node-buffer]: <http://nodejs.org/api/buffer.html>
+15
View File
@@ -0,0 +1,15 @@
(function() {
var Path;
Path = require('path');
module.exports = (function() {
switch (Path.extname(__filename)) {
case '.coffee':
return require('./src/logcat');
default:
return require('./lib/logcat');
}
})();
}).call(this);
+25
View File
@@ -0,0 +1,25 @@
(function() {
var Logcat, Priority, Reader;
Reader = require('./logcat/reader');
Priority = require('./logcat/priority');
Logcat = (function() {
function Logcat() {}
Logcat.readStream = function(stream, options) {
return new Reader(options).connect(stream);
};
return Logcat;
})();
Logcat.Reader = Reader;
Logcat.Priority = Priority;
module.exports = Logcat;
}).call(this);
+76
View File
@@ -0,0 +1,76 @@
(function() {
var Entry;
Entry = (function() {
function Entry() {
this.date = null;
this.pid = -1;
this.tid = -1;
this.priority = null;
this.tag = null;
this.message = null;
}
Entry.prototype.setDate = function(date) {
this.date = date;
};
Entry.prototype.setPid = function(pid) {
this.pid = pid;
};
Entry.prototype.setTid = function(tid) {
this.tid = tid;
};
Entry.prototype.setPriority = function(priority) {
this.priority = priority;
};
Entry.prototype.setTag = function(tag) {
this.tag = tag;
};
Entry.prototype.setMessage = function(message) {
this.message = message;
};
Entry.prototype.toBinary = function() {
var buffer, cursor, length;
length = 20;
length += 1;
length += this.tag.length;
length += 1;
length += this.message.length;
length += 1;
buffer = new Buffer(length);
cursor = 0;
buffer.writeUInt16LE(length - 20, cursor);
cursor += 4;
buffer.writeInt32LE(this.pid, cursor);
cursor += 4;
buffer.writeInt32LE(this.tid, cursor);
cursor += 4;
buffer.writeInt32LE(Math.floor(this.date.getTime() / 1000), cursor);
cursor += 4;
buffer.writeInt32LE((this.date.getTime() % 1000) * 1000000, cursor);
cursor += 4;
buffer[cursor] = this.priority;
cursor += 1;
buffer.write(this.tag, cursor, this.tag.length);
cursor += this.tag.length;
buffer[cursor] = 0x00;
cursor += 1;
buffer.write(this.message, cursor, this.message.length);
cursor += this.message.length;
buffer[cursor] = 0x00;
return buffer;
};
return Entry;
})();
module.exports = Entry;
}).call(this);
+31
View File
@@ -0,0 +1,31 @@
(function() {
var EventEmitter, Parser,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
EventEmitter = require('events').EventEmitter;
Parser = (function(superClass) {
extend(Parser, superClass);
function Parser() {
return Parser.__super__.constructor.apply(this, arguments);
}
Parser.get = function(type) {
var parser;
parser = require("./parser/" + type);
return new parser();
};
Parser.prototype.parse = function() {
throw new Error("parse() is unimplemented");
};
return Parser;
})(EventEmitter);
module.exports = Parser;
}).call(this);
+86
View File
@@ -0,0 +1,86 @@
(function() {
var Binary, Entry, Parser, Priority,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
Parser = require('../parser');
Entry = require('../entry');
Priority = require('../priority');
Binary = (function(superClass) {
var HEADER_SIZE_MAX, HEADER_SIZE_V1;
extend(Binary, superClass);
HEADER_SIZE_V1 = 20;
HEADER_SIZE_MAX = 100;
function Binary() {
this.buffer = new Buffer('');
}
Binary.prototype.parse = function(chunk) {
var cursor, data, entry, headerSize, length, nsec, sec;
this.buffer = Buffer.concat([this.buffer, chunk]);
while (this.buffer.length > 4) {
cursor = 0;
length = this.buffer.readUInt16LE(cursor);
cursor += 2;
headerSize = this.buffer.readUInt16LE(cursor);
if (headerSize < HEADER_SIZE_V1 || headerSize > HEADER_SIZE_MAX) {
headerSize = HEADER_SIZE_V1;
}
cursor += 2;
if (this.buffer.length < headerSize + length) {
break;
}
entry = new Entry;
entry.setPid(this.buffer.readInt32LE(cursor));
cursor += 4;
entry.setTid(this.buffer.readInt32LE(cursor));
cursor += 4;
sec = this.buffer.readInt32LE(cursor);
cursor += 4;
nsec = this.buffer.readInt32LE(cursor);
entry.setDate(new Date(sec * 1000 + nsec / 1000000));
cursor += 4;
cursor = headerSize;
data = this.buffer.slice(cursor, cursor + length);
cursor += length;
this.buffer = this.buffer.slice(cursor);
this._processEntry(entry, data);
}
if (this.buffer.length) {
this.emit('wait');
} else {
this.emit('drain');
}
};
Binary.prototype._processEntry = function(entry, data) {
var cursor, length;
entry.setPriority(data[0]);
cursor = 1;
length = data.length;
while (cursor < length) {
if (data[cursor] === 0) {
entry.setTag(data.slice(1, cursor).toString());
entry.setMessage(data.slice(cursor + 1, length - 1).toString());
this.emit('entry', entry);
return;
}
cursor += 1;
}
this.emit('error', new Error("Unprocessable entry data '" + data + "'"));
};
return Binary;
})(Parser);
module.exports = Binary;
}).call(this);
+89
View File
@@ -0,0 +1,89 @@
(function() {
var Priority;
Priority = (function() {
var letterNames, letters, names;
function Priority() {}
Priority.UNKNOWN = 0;
Priority.DEFAULT = 1;
Priority.VERBOSE = 2;
Priority.DEBUG = 3;
Priority.INFO = 4;
Priority.WARN = 5;
Priority.ERROR = 6;
Priority.FATAL = 7;
Priority.SILENT = 8;
names = {
0: 'UNKNOWN',
1: 'DEFAULT',
2: 'VERBOSE',
3: 'DEBUG',
4: 'INFO',
5: 'WARN',
6: 'ERROR',
7: 'FATAL',
8: 'SILENT'
};
letters = {
'?': Priority.UNKNOWN,
'V': Priority.VERBOSE,
'D': Priority.DEBUG,
'I': Priority.INFO,
'W': Priority.WARN,
'E': Priority.ERROR,
'F': Priority.FATAL,
'S': Priority.SILENT
};
letterNames = {
0: '?',
1: '?',
2: 'V',
3: 'D',
4: 'I',
5: 'W',
6: 'E',
7: 'F',
8: 'S'
};
Priority.fromName = function(name) {
var value;
value = Priority[name.toUpperCase()];
if (value || value === 0) {
return value;
}
return Priority.fromLetter(name);
};
Priority.toName = function(value) {
return names[value];
};
Priority.fromLetter = function(letter) {
return letters[letter.toUpperCase()];
};
Priority.toLetter = function(value) {
return letterNames[value];
};
return Priority;
})();
module.exports = Priority;
}).call(this);
+150
View File
@@ -0,0 +1,150 @@
(function() {
var EventEmitter, Parser, Priority, Reader, Transform,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
EventEmitter = require('events').EventEmitter;
Parser = require('./parser');
Transform = require('./transform');
Priority = require('./priority');
Reader = (function(superClass) {
extend(Reader, superClass);
Reader.ANY = '*';
function Reader(options) {
var base;
this.options = options != null ? options : {};
(base = this.options).format || (base.format = 'binary');
if (this.options.fixLineFeeds == null) {
this.options.fixLineFeeds = true;
}
this.filters = {
all: -1,
tags: {}
};
this.parser = Parser.get(this.options.format);
this.stream = null;
}
Reader.prototype.exclude = function(tag) {
if (tag === Reader.ANY) {
return this.excludeAll();
}
this.filters.tags[tag] = Priority.SILENT;
return this;
};
Reader.prototype.excludeAll = function() {
this.filters.all = Priority.SILENT;
return this;
};
Reader.prototype.include = function(tag, priority) {
if (priority == null) {
priority = Priority.DEBUG;
}
if (tag === Reader.ANY) {
return this.includeAll(priority);
}
this.filters.tags[tag] = this._priority(priority);
return this;
};
Reader.prototype.includeAll = function(priority) {
if (priority == null) {
priority = Priority.DEBUG;
}
this.filters.all = this._priority(priority);
return this;
};
Reader.prototype.resetFilters = function() {
this.filters.all = -1;
this.filters.tags = {};
return this;
};
Reader.prototype._hook = function() {
var transform;
if (this.options.fixLineFeeds) {
transform = this.stream.pipe(new Transform);
transform.on('data', (function(_this) {
return function(data) {
return _this.parser.parse(data);
};
})(this));
} else {
this.stream.on('data', (function(_this) {
return function(data) {
return _this.parser.parse(data);
};
})(this));
}
this.stream.on('error', (function(_this) {
return function(err) {
return _this.emit('error', err);
};
})(this));
this.stream.on('end', (function(_this) {
return function() {
return _this.emit('end');
};
})(this));
this.stream.on('finish', (function(_this) {
return function() {
return _this.emit('finish');
};
})(this));
this.parser.on('entry', (function(_this) {
return function(entry) {
if (_this._filter(entry)) {
return _this.emit('entry', entry);
}
};
})(this));
this.parser.on('error', (function(_this) {
return function(err) {
return _this.emit('error', err);
};
})(this));
};
Reader.prototype._filter = function(entry) {
var priority;
priority = this.filters.tags[entry.tag];
if (!(priority >= 0)) {
priority = this.filters.all;
}
return entry.priority >= priority;
};
Reader.prototype._priority = function(priority) {
if (typeof priority === 'number') {
return priority;
}
return Priority.fromName(priority);
};
Reader.prototype.connect = function(stream) {
this.stream = stream;
this._hook();
return this;
};
Reader.prototype.end = function() {
this.stream.end();
return this;
};
return Reader;
})(EventEmitter);
module.exports = Reader;
}).call(this);
+51
View File
@@ -0,0 +1,51 @@
(function() {
var Stream, Transform,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
Stream = require('stream');
Transform = (function(superClass) {
extend(Transform, superClass);
function Transform(options) {
this.savedR = null;
Transform.__super__.constructor.call(this, options);
}
Transform.prototype._transform = function(chunk, encoding, done) {
var hi, last, lo;
lo = 0;
hi = 0;
if (this.savedR) {
if (chunk[0] !== 0x0a) {
this.push(this.savedR);
}
this.savedR = null;
}
last = chunk.length - 1;
while (hi <= last) {
if (chunk[hi] === 0x0d) {
if (hi === last) {
this.savedR = chunk.slice(last);
break;
} else if (chunk[hi + 1] === 0x0a) {
this.push(chunk.slice(lo, hi));
lo = hi + 1;
}
}
hi += 1;
}
if (hi !== lo) {
this.push(chunk.slice(lo, hi));
}
done();
};
return Transform;
})(Stream.Transform);
module.exports = Transform;
}).call(this);
+49
View File
@@ -0,0 +1,49 @@
{
"name": "adbkit-logcat",
"version": "1.1.0",
"description": "A Node.js interface for working with Android's logcat output.",
"keywords": [
"adb",
"adbkit",
"logcat"
],
"bugs": {
"url": "https://github.com/openstf/adbkit-logcat/issues"
},
"license": "Apache-2.0",
"author": {
"name": "The OpenSTF Project",
"email": "contact@openstf.io",
"url": "https://openstf.io"
},
"main": "./index",
"repository": {
"type": "git",
"url": "https://github.com/openstf/adbkit-logcat.git"
},
"scripts": {
"postpublish": "grunt clean",
"prepublish": "grunt coffee",
"test": "grunt test"
},
"dependencies": {},
"devDependencies": {
"chai": "^3.5.0",
"coffee-script": "^1.10.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-coffeelint": "0.0.16",
"grunt-contrib-clean": "^1.0.0",
"grunt-contrib-coffee": "^1.0.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-exec": "^1.0.0",
"grunt-jsonlint": "^1.1.0",
"grunt-notify": "^0.4.5",
"mocha": "^3.0.2",
"sinon": "^1.17.5",
"sinon-chai": "^2.8.0"
},
"engines": {
"node": ">= 0.10.4"
}
}