gecko-dev/b2g/components/LogParser.jsm
Alexandre Lissy b1a47c39c4 Bug 1079322 - Extract properties using libc functions. r=gwagner
The LogShake features allows one to shake its device to be able to dump
a set of useful logging informations. This includes several log files,
and also the Android properties. In the past, we relied on the
/dev/__properties__ file to extract their content by parsing its value.
This is duplicate work from the bionic libc and libcutils library.
Worst, the format used to store the values in this file has been changed
between JellyBean and Kitkat, so our parser was not able to dump the
values: that explains bug 1079322. To fix this we make use of some of
the underlying libc-defined functions used to iterate and get properties
values:
 - __system_property_find_nth() to retrieve one arbitrary property by
   its number (starting from 0), and this returns a struct containing
   all the informations
 - __system_property_read() to read the values contained in the struct
   that was previously retrieved
2014-11-12 06:31:00 +01:00

244 lines
6.6 KiB
JavaScript

/* jshint esnext: true */
/* global DataView */
"use strict";
this.EXPORTED_SYMBOLS = ["LogParser"];
/**
* Parse an array read from a /dev/log/ file. Format taken from
* kernel/drivers/staging/android/logger.h and system/core/logcat/logcat.cpp
*
* @param array {Uint8Array} Array read from /dev/log/ file
* @return {Array} List of log messages
*/
function parseLogArray(array) {
let data = new DataView(array.buffer);
let byteString = String.fromCharCode.apply(null, array);
let logMessages = [];
let pos = 0;
while (pos < byteString.length) {
// Parse a single log entry
// Track current offset from global position
let offset = 0;
// Length of the entry, discarded
let length = data.getUint32(pos + offset, true);
offset += 4;
// Id of the process which generated the message
let processId = data.getUint32(pos + offset, true);
offset += 4;
// Id of the thread which generated the message
let threadId = data.getUint32(pos + offset, true);
offset += 4;
// Seconds since epoch when this message was logged
let seconds = data.getUint32(pos + offset, true);
offset += 4;
// Nanoseconds since the last second
let nanoseconds = data.getUint32(pos + offset, true);
offset += 4;
// Priority in terms of the ANDROID_LOG_* constants (see below)
// This is where the length field begins counting
let priority = data.getUint8(pos + offset);
// Reset pos and offset to count from here
pos += offset;
offset = 0;
offset += 1;
// Read the tag and message, represented as null-terminated c-style strings
let tag = "";
while (byteString[pos + offset] != "\0") {
tag += byteString[pos + offset];
offset ++;
}
offset ++;
let message = "";
// The kernel log driver may have cut off the null byte (logprint.c)
while (byteString[pos + offset] != "\0" && offset < length) {
message += byteString[pos + offset];
offset ++;
}
// Un-skip the missing null terminator
if (offset === length) {
offset --;
}
offset ++;
pos += offset;
// Log messages are occasionally delimited by newlines, but are also
// sometimes followed by newlines as well
if (message.charAt(message.length - 1) === "\n") {
message = message.substring(0, message.length - 1);
}
// Add an aditional time property to mimic the milliseconds since UTC
// expected by Date
let time = seconds * 1000.0 + nanoseconds/1000000.0;
// Log messages with interleaved newlines are considered to be separate log
// messages by logcat
for (let lineMessage of message.split("\n")) {
logMessages.push({
processId: processId,
threadId: threadId,
seconds: seconds,
nanoseconds: nanoseconds,
time: time,
priority: priority,
tag: tag,
message: lineMessage + "\n"
});
}
}
return logMessages;
}
/**
* Get a thread-time style formatted string from time
* @param time {Number} Milliseconds since epoch
* @return {String} Formatted time string
*/
function getTimeString(time) {
let date = new Date(time);
function pad(number) {
if ( number < 10 ) {
return "0" + number;
}
return number;
}
return pad( date.getMonth() + 1 ) +
"-" + pad( date.getDate() ) +
" " + pad( date.getHours() ) +
":" + pad( date.getMinutes() ) +
":" + pad( date.getSeconds() ) +
"." + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5);
}
/**
* Pad a string using spaces on the left
* @param str {String} String to pad
* @param width {Number} Desired string length
*/
function padLeft(str, width) {
while (str.length < width) {
str = " " + str;
}
return str;
}
/**
* Pad a string using spaces on the right
* @param str {String} String to pad
* @param width {Number} Desired string length
*/
function padRight(str, width) {
while (str.length < width) {
str = str + " ";
}
return str;
}
/** Constant values taken from system/core/liblog */
const ANDROID_LOG_UNKNOWN = 0;
const ANDROID_LOG_DEFAULT = 1;
const ANDROID_LOG_VERBOSE = 2;
const ANDROID_LOG_DEBUG = 3;
const ANDROID_LOG_INFO = 4;
const ANDROID_LOG_WARN = 5;
const ANDROID_LOG_ERROR = 6;
const ANDROID_LOG_FATAL = 7;
const ANDROID_LOG_SILENT = 8;
/**
* Map a priority number to its abbreviated string equivalent
* @param priorityNumber {Number} Log-provided priority number
* @return {String} Priority number's abbreviation
*/
function getPriorityString(priorityNumber) {
switch (priorityNumber) {
case ANDROID_LOG_VERBOSE:
return "V";
case ANDROID_LOG_DEBUG:
return "D";
case ANDROID_LOG_INFO:
return "I";
case ANDROID_LOG_WARN:
return "W";
case ANDROID_LOG_ERROR:
return "E";
case ANDROID_LOG_FATAL:
return "F";
case ANDROID_LOG_SILENT:
return "S";
default:
return "?";
}
}
/**
* Mimic the logcat "threadtime" format, generating a formatted string from a
* log message object.
* @param logMessage {Object} A log message from the list returned by parseLogArray
* @return {String} threadtime formatted summary of the message
*/
function formatLogMessage(logMessage) {
// MM-DD HH:MM:SS.ms pid tid priority tag: message
// from system/core/liblog/logprint.c:
return getTimeString(logMessage.time) +
" " + padLeft(""+logMessage.processId, 5) +
" " + padLeft(""+logMessage.threadId, 5) +
" " + getPriorityString(logMessage.priority) +
" " + padRight(logMessage.tag, 8) +
": " + logMessage.message;
}
/**
* Pretty-print an array of bytes read from a log file by parsing then
* threadtime formatting its entries.
* @param array {Uint8Array} Array of a log file's bytes
* @return {String} Pretty-printed log
*/
function prettyPrintLogArray(array) {
let logMessages = parseLogArray(array);
return logMessages.map(formatLogMessage).join("");
}
/**
* Pretty-print an array read from the list of propreties.
* @param {Object} Object representing the properties
* @return {String} Human-readable string of property name: property value
*/
function prettyPrintPropertiesArray(properties) {
let propertiesString = "";
for(let propName in properties) {
propertiesString += "[" + propName + "]: [" + properties[propName] + "]\n";
}
return propertiesString;
}
/**
* Pretty-print a normal array. Does nothing.
* @param array {Uint8Array} Input array
*/
function prettyPrintArray(array) {
return array;
}
this.LogParser = {
parseLogArray: parseLogArray,
prettyPrintArray: prettyPrintArray,
prettyPrintLogArray: prettyPrintLogArray,
prettyPrintPropertiesArray: prettyPrintPropertiesArray
};