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