mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 20:17:37 +00:00
b1a47c39c4
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
244 lines
6.6 KiB
JavaScript
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
|
|
};
|