blob: 28f55153e857f25bf3bb5b7712e7d562d0ee87b5 [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 {ChildPart, html, LitElement} from 'lit';
import {Directive, directive} from 'lit/directive.js';
import {customElement, property, state} from 'lit/decorators.js';
import { LogField } from '../src/fields';
import {
formatLogPayload, formatMalformedLog,
formatTargetMonotonic, messageForEvent
} from '../src/format_log_text';
import { FfxEventData, FfxLogData, LogData } from '../src/log_data';
class AttributeSetter extends Directive {
update(part: ChildPart, [attributes]: [any]) {
const domElement = (part.parentNode as Element);
for (const attr in attributes) {
const value = attributes[attr];
domElement.setAttribute(`data-${attr}`, value);
}
return this.render(attributes);
}
render(_attributes: any) {
return '';
}
}
@customElement('log-view-row')
export class LogRow extends LitElement {
@property({attribute: true, type: Object})
public log: FfxLogData | undefined = undefined;
@state()
private symbolized: string | undefined;
private fields!: Record<string, any>;
private dataAttributes!: Record<string, any>;
constructor(log: FfxLogData, hasPreviousLog: boolean) {
super();
this.log = log;
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);
}
}
createRenderRoot() {
return this;
}
render() {
const attributeSetter = directive(AttributeSetter);
return html`
<div class="log-entry">
${attributeSetter(this.dataAttributes)}
${Object.keys(constant.LOGS_HEADERS).map(this.htmlForFieldKey.bind(this))}
</div>`;
}
private htmlForFieldKey(fieldKey: string) {
const content = this.fields[fieldKey] ?? '';
return html`
<div
id="${fieldKey}"
title="${fieldKey === 'message' ? '' : content}"
class="log-list-cell${fieldKey === 'message' ? ' msg-cell' : ''}"
>
${content}
</div>`;
}
/**
* Formats row for Ffx Logs.
*/
private formatFfxEventRow(event: FfxEventData, hasPreviousLog: boolean) {
const fields =
{'moniker': constant.FFX_MONIKER, 'message': messageForEvent(event, hasPreviousLog)};
this.dataAttributes = fields;
this.fields = 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.dataAttributes = fields;
this.fields = 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.dataAttributes = fields;
this.fields = fields;
}
/**
* Formats row for Target Log and Symbolized Logs.
*/
private formatRow(log: LogData) {
let fields = {} as Record<string, any>;
this.dataAttributes = createAttributes(log);
let header: LogField;
for (header in constant.LOGS_HEADERS) {
let field = header.toLowerCase();
fields[field] = this.getLogCell(log, field);
}
this.fields = 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 '';
}
}
}
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];
}
}
if (data.metadata.pid) {
attrs['pid'] = data.metadata.pid;
}
if (data.metadata.pid) {
attrs['tid'] = data.metadata.tid;
}
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;
}