blob: 01c38417535a0eb49b1c2e7d3e7bdbee061d30fa [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as constant from '../src/constants';
import { LogField } from '../src/fields';
import {
formatLogPayload, formatMalformedLog,
formatTargetMonotonic, messageForEvent
} from '../src/format_log_text';
import { FfxEventData, FfxLogData, LogData } from '../src/log_data';
export class LogRow {
private logRow: HTMLElement;
private symbolized: string | undefined;
constructor(private log: FfxLogData, hasPreviousLog: boolean) {
this.logRow = this.initLogRow();
if (this.log.data.TargetLog !== undefined) {
this.formatRow(this.log.data.TargetLog);
} else if (this.log.data.FfxEvent !== undefined) {
this.formatFfxEventRow(this.log.data.FfxEvent, hasPreviousLog);
} else if (this.log.data.MalformedTargetLog !== undefined) {
this.formatMalformedRow(this.log.data.MalformedTargetLog);
} else if (this.log.data.SymbolizedTargetLog !== undefined) {
this.symbolized = this.log.data.SymbolizedTargetLog[1];
this.formatRow(this.log.data.SymbolizedTargetLog[0]);
} else if (this.log.data.ViewerEvent !== undefined) {
this.formatViewerEventRow(this.log.data.ViewerEvent);
}
}
/**
* Returns the core element.
*/
get element() {
return this.logRow;
}
/**
* Creates log row element containing useful metadata in `data-*` attributes.
*
* @param attributes the map of attribute name to value.
* @returns the log row html element.
*/
private createLogRowAttributes(attributes: Record<string, any>) {
for (const attributeName in attributes) {
this.logRow.setAttribute(`data-${attributeName}`, attributes[attributeName]);
}
}
/**
* Creates the inner html of the fields.
*
* @param fields fields of the log element.
* @returns the inner html string with present fields filled.
*/
private createLogInnerHtml(fields: Record<string, any>) {
let fieldKey: LogField;
for (fieldKey in constant.LOGS_HEADERS) {
let cell = document.createElement('td');
let content = '';
if (fields[fieldKey]) {
content = fields[fieldKey];
}
if (fieldKey === 'message') {
cell.classList.add('msg-cell');
} else {
cell.title = content;
}
cell.innerText = content;
cell.setAttribute('id', fieldKey);
this.logRow.appendChild(cell);
}
}
/**
* Formats row for Ffx Logs.
*/
private formatFfxEventRow(event: FfxEventData, hasPreviousLog: boolean) {
const fields =
{ 'moniker': constant.FFX_MONIKER, 'message': messageForEvent(event, hasPreviousLog) };
this.createLogRowAttributes(fields);
this.createLogInnerHtml(fields);
}
/**
* Formats row for Malformed Logs.
* @param malformedLog the malformed log to render as a message.
*/
private formatMalformedRow(malformedLog: string) {
const fields = { 'message': formatMalformedLog(malformedLog) };
this.createLogRowAttributes(fields);
this.createLogInnerHtml(fields);
}
/**
* Formats row for an viewer synthesizedd log.
* @param message the message to display.
*/
private formatViewerEventRow(message: string) {
const fields = { message, moniker: constant.VSCODE_SYNTHETIC_MONIKER };
this.createLogRowAttributes(fields);
this.createLogInnerHtml(fields);
}
/**
* Formats row for Target Log and Symbolized Logs.
*/
private formatRow(log: LogData) {
let fields = {} as Record<string, any>;
const attributes = createAttributes(log);
this.createLogRowAttributes(attributes);
let header: LogField;
for (header in constant.LOGS_HEADERS) {
let field = header.toLowerCase();
fields[field] = this.getLogCell(log, field);
}
this.createLogInnerHtml(fields);
}
/**
* Get the field data from a cell in the Log Viewer table.
*
* @param logColumn the field data that determines the column of the table.
* @returns cell element as a string.
*/
private getLogCell(log: LogData, logColumn: string): string {
switch (logColumn) {
case 'timestamp':
const time = formatTargetMonotonic(log.metadata.timestamp);
return time;
case 'pid':
const pid = log.metadata.pid ?? '';
return pid.toString();
case 'tid':
const tid = log.metadata.tid ?? '';
return tid.toString();
case 'moniker':
return log.moniker;
case 'tags':
const tags = (log.metadata.tags ?? []).join(',');
return tags;
case 'severity':
return log.metadata.severity;
case 'message':
const msg = this.symbolized ?? formatLogPayload(log['payload']!);
return msg;
default:
return '';
}
}
/**
* Initialize LogRow element.
*/
private initLogRow(): HTMLElement {
let logRow = document.createElement('tr');
logRow.classList.add('log-entry');
return logRow;
}
}
const COMPONENT_URL_REGEX =
/fuchsia-(pkg|boot):\/\/[^\/]*\/(?<package>[^\\]*).*#meta\/(?<manifest>.+\.cmx?)/;
/**
* Creates a map with the attributes to be placed in `data-*` for each log element.
* @param data the log data associated with the log element
* @returns a map of attribute key name to its value.
*/
function createAttributes(data: LogData): Record<string, any> {
let attrs: Record<string, any> = {
moniker: data.moniker,
severity: data.metadata.severity.toLowerCase(),
};
if (data.metadata.tags) {
attrs['tags'] = `${data.metadata.tags.length}`;
for (let i = 0; i < data.metadata.tags.length; i++) {
attrs[`tag-${i}`] = data.metadata.tags[i];
}
}
const urlResult = COMPONENT_URL_REGEX.exec(data.metadata.component_url);
if (urlResult) {
if (urlResult.groups?.package) {
attrs['package-name'] = urlResult.groups.package;
}
if (urlResult.groups?.manifest) {
attrs['manifest'] = urlResult.groups.manifest;
}
}
if (data.payload.root.message) {
attrs['message'] = data.payload.root.message.value;
}
if (data.payload.root.printf) {
attrs['message'] = `${data.payload.root.printf} [${data.payload.root.printf.args.join(', ')}]`;
}
for (const [key, value] of Object.entries(data.payload.root.keys ?? {})) {
attrs[`custom-${key}`] = value;
}
return attrs;
}