• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2022 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15var VirtualizedList = window.VirtualizedList.default;
16const rowHeight = 30;
17
18function formatDate(dt) {
19  function pad2(n) {
20    return (n < 10 ? '0' : '') + n;
21  }
22
23  return dt.getFullYear() + pad2(dt.getMonth() + 1) + pad2(dt.getDate()) + ' ' +
24      pad2(dt.getHours()) + ':' + pad2(dt.getMinutes()) + ':' +
25      pad2(dt.getSeconds());
26}
27
28let data = [];
29function clearLogs() {
30  data = [{
31    'message': 'Logs started',
32    'levelno': 20,
33    time: formatDate(new Date()),
34    'levelname': '\u001b[35m\u001b[1mINF\u001b[0m',
35    'args': [],
36    'fields': {'module': '', 'file': '', 'timestamp': '', 'keys': ''}
37  }];
38}
39clearLogs();
40
41let nonAdditionalDataFields =
42    ['_hosttime', 'levelname', 'levelno', 'args', 'fields', 'message', 'time'];
43let additionalHeaders = [];
44function updateHeadersFromData(data) {
45  let dirty = false;
46  Object.keys(data).forEach((columnName) => {
47    if (nonAdditionalDataFields.indexOf(columnName) === -1 &&
48        additionalHeaders.indexOf(columnName) === -1) {
49      additionalHeaders.push(columnName);
50      dirty = true;
51    }
52  });
53  Object.keys(data.fields || {}).forEach((columnName) => {
54    if (nonAdditionalDataFields.indexOf(columnName) === -1 &&
55        additionalHeaders.indexOf(columnName) === -1) {
56      additionalHeaders.push(columnName);
57      dirty = true;
58    }
59  });
60
61  const headerDOM = document.querySelector('.log-header');
62  if (dirty) {
63    headerDOM.innerHTML = `
64      <span class="_hosttime">Time</span>
65      <span class="level">Level</span>
66      ${
67        additionalHeaders
68            .map((key) => `
69        <span class="${key}">${key}</span>
70      `).join('\n')}
71      <span class="msg">Message</span>`
72  }
73
74  // Also update column widths to match actual row.
75  const headerChildren = Array.from(headerDOM.children);
76
77  const firstRow = document.querySelector('.log-container .log-entry');
78  const firstRowChildren = Array.from(firstRow.children);
79  headerChildren.forEach((col, index) => {
80    if (firstRowChildren[index]) {
81      col.setAttribute(
82          'style',
83          `width:${firstRowChildren[index].getBoundingClientRect().width}`);
84      col.setAttribute('title', col.innerText);
85    }
86  })
87}
88
89function getUrlHashParameter(param) {
90  var params = getUrlHashParameters();
91  return params[param];
92}
93
94function getUrlHashParameters() {
95  var sPageURL = window.location.hash;
96  if (sPageURL)
97    sPageURL = sPageURL.split('#')[1];
98  var pairs = sPageURL.split('&');
99  var object = {};
100  pairs.forEach(function(pair, i) {
101    pair = pair.split('=');
102    if (pair[0] !== '')
103      object[pair[0]] = pair[1];
104  });
105  return object;
106}
107let currentTheme = {};
108let defaultLogStyleRule = 'color: #ffffff;';
109let columnStyleRules = {};
110let defaultColumnStyles = [];
111let logLevelStyles = {};
112const logLevelToString = {
113  10: 'DBG',
114  20: 'INF',
115  21: 'OUT',
116  30: 'WRN',
117  40: 'ERR',
118  50: 'CRT',
119  70: 'FTL'
120};
121
122function setCurrentTheme(newTheme) {
123  currentTheme = newTheme;
124  defaultLogStyleRule = parseStyle(newTheme.default);
125  document.querySelector('body').setAttribute('style', defaultLogStyleRule);
126  // Apply default font styles to columns
127  let styles = [];
128  Object.keys(newTheme).forEach(key => {
129    if (key.startsWith('log-table-column-')) {
130      styles.push(newTheme[key]);
131    }
132    if (key.startsWith('log-level-')) {
133      logLevelStyles[parseInt(key.replace('log-level-', ''))] =
134          parseStyle(newTheme[key]);
135    }
136  });
137  defaultColumnStyles = styles;
138}
139
140function parseStyle(rule) {
141  const ruleList = rule.split(' ');
142  let outputStyle = ruleList.map(fragment => {
143    if (fragment.startsWith('bg:')) {
144      return `background-color: ${fragment.replace('bg:', '')}`
145    } else if (fragment === 'bold') {
146      return `font-weight: bold`;
147    } else if (fragment === 'underline') {
148      return `text-decoration: underline`;
149    } else if (fragment.startsWith('#')) {
150      return `color: ${fragment}`;
151    }
152  });
153  return outputStyle.join(';')
154}
155
156function applyStyling(data, applyColors = false) {
157  let colIndex = 0;
158  Object.keys(data).forEach(key => {
159    if (columnStyleRules[key] && typeof data[key] === 'string') {
160      Object.keys(columnStyleRules[key]).forEach(token => {
161        data[key] = data[key].replaceAll(
162            token,
163            `<span
164              style="${defaultLogStyleRule};${
165                applyColors ? (defaultColumnStyles
166                                   [colIndex % defaultColumnStyles.length]) :
167                              ''};${parseStyle(columnStyleRules[key][token])};">
168                ${token}
169            </span>`);
170      });
171    } else if (key === 'fields') {
172      data[key] = applyStyling(data.fields, true);
173    }
174    if (applyColors) {
175      data[key] = `<span
176      style="${
177          parseStyle(
178              defaultColumnStyles[colIndex % defaultColumnStyles.length])}">
179        ${data[key]}
180      </span>`;
181    }
182    colIndex++;
183  });
184  return data;
185}
186
187(function() {
188const container = document.querySelector('.log-container');
189const height = window.innerHeight - 50
190let follow = true;
191// Initialize our VirtualizedList
192var virtualizedList = new VirtualizedList(container, {
193  height,
194  rowCount: data.length,
195  rowHeight: rowHeight,
196  estimatedRowHeight: rowHeight,
197  renderRow: (index) => {
198    const element = document.createElement('div');
199    element.classList.add('log-entry');
200    element.setAttribute('style', `height: ${rowHeight}px;`);
201    const logData = data[index];
202    element.innerHTML = `
203        <span class="time">${logData.time}</span>
204        <span class="level" style="${logLevelStyles[logData.levelno] || ''}">${
205        logLevelToString[logData.levelno]}</span>
206        ${
207        additionalHeaders
208            .map(
209                (key) => `
210          <span class="${key}">${
211                    logData[key] || logData.fields[key] || ''}</span>
212        `).join('\n')}
213        <span class="msg">${logData.message}</span>
214      `;
215    return element;
216  },
217  initialIndex: 0,
218  onScroll: (scrollTop, event) => {
219    const offset =
220        virtualizedList._sizeAndPositionManager.getUpdatedOffsetForIndex({
221          containerSize: height,
222          targetIndex: data.length - 1,
223        });
224
225    if (scrollTop < offset) {
226      follow = false;
227    } else {
228      follow = true;
229    }
230  }
231});
232
233const port = getUrlHashParameter('ws')
234const hostname = location.hostname || '127.0.0.1';
235var ws = new WebSocket(`ws://${hostname}:${port}/`);
236ws.onmessage = function(event) {
237  let dataObj;
238  try {
239    dataObj = JSON.parse(event.data);
240  } catch (e) {
241  }
242  if (!dataObj)
243    return;
244
245  if (dataObj.__pw_console_colors) {
246    const colors = dataObj.__pw_console_colors;
247    setCurrentTheme(colors.classes);
248    if (colors.column_values) {
249      columnStyleRules = {...colors.column_values};
250    }
251  } else {
252    const currentData = {...dataObj, time: formatDate(new Date())};
253    updateHeadersFromData(currentData);
254    data.push(applyStyling(currentData));
255    virtualizedList.setRowCount(data.length);
256    if (follow) {
257      virtualizedList.scrollToIndex(data.length - 1);
258    }
259  }
260};
261})();
262