| // 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; |
| } |