Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 35d75d1

Browse files
committed
Hack together ui for experimenting with logging events as JSON over WS
1 parent 67a48ef commit 35d75d1

File tree

11 files changed

+243
-38
lines changed

11 files changed

+243
-38
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useLayoutEffect, useState } from 'react';
2+
3+
export function useWindowSize() {
4+
const [size, setSize] = useState([0, 0]);
5+
useLayoutEffect(() => {
6+
function updateSize() {
7+
setSize([window.innerWidth, window.innerHeight]);
8+
}
9+
window.addEventListener('resize', updateSize);
10+
updateSize();
11+
return () => window.removeEventListener('resize', updateSize);
12+
}, []);
13+
return size;
14+
}

interface/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export * from './RestController';
1414

1515
export * from './WebSocketFormLoader';
1616
export * from './WebSocketController';
17+
export * from './WindowSize';

interface/src/ntp/TimeFormat.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import moment from 'moment';
22

33
export const formatIsoDateTime = (isoDateString: string) => moment.parseZone(isoDateString).format('ll @ HH:mm:ss');
4+
export const formatIsoDateTimeToHr = (isoDateString: string) => moment.parseZone(isoDateString).format('YYYY-MM-DD HH:mm:ssZZ');
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React, { FC } from 'react';
2+
3+
import { LogEvent, LogLevel } from './types';
4+
import { Theme, makeStyles, Box } from '@material-ui/core';
5+
import { useWindowSize } from '../components';
6+
import { formatIsoDateTimeToHr } from '../ntp/TimeFormat';
7+
8+
interface LogEventConsoleProps {
9+
events: LogEvent[];
10+
}
11+
12+
const topOffset = () => {
13+
const wrapper = document.getElementById('system-tabs');
14+
if (wrapper) {
15+
return wrapper.getBoundingClientRect().bottom;
16+
}
17+
return 0;
18+
}
19+
20+
const useStyles = makeStyles((theme: Theme) => ({
21+
console: {
22+
padding: theme.spacing(2),
23+
height: (topOffset: () => number) => `calc(100vh - ${topOffset()}px)`,
24+
backgroundColor: "black"
25+
},
26+
entry: {
27+
color: "#bbbbbb",
28+
fontFamily: "Courier New, monospace",
29+
fontSize: "14px",
30+
letterSpacing: "normal"
31+
},
32+
file: {
33+
color: "#00ffff"
34+
},
35+
debug: {
36+
color: "#0000ff"
37+
},
38+
info: {
39+
color: "#00ff00"
40+
},
41+
warning: {
42+
color: "#ffff00"
43+
},
44+
error: {
45+
color: "#ff0000"
46+
},
47+
unknown: {
48+
color: "#ffffff"
49+
}
50+
}));
51+
52+
53+
const LogEventConsole: FC<LogEventConsoleProps> = (props) => {
54+
useWindowSize();
55+
const classes = useStyles(topOffset);
56+
const { events } = props;
57+
58+
const styleLevel = (level: LogLevel) => {
59+
switch (level) {
60+
case LogLevel.DEBUG:
61+
return classes.debug;
62+
case LogLevel.INFO:
63+
return classes.info;
64+
case LogLevel.WARNING:
65+
return classes.warning;
66+
case LogLevel.ERROR:
67+
return classes.error;
68+
default:
69+
return classes.unknown;
70+
}
71+
}
72+
73+
const levelLabel = (level: LogLevel) => {
74+
switch (level) {
75+
case LogLevel.DEBUG:
76+
return "DEBUG";
77+
case LogLevel.INFO:
78+
return "INFO";
79+
case LogLevel.WARNING:
80+
return "WARNING";
81+
case LogLevel.ERROR:
82+
return "ERROR";
83+
default:
84+
return "UNKNOWN";
85+
}
86+
}
87+
88+
const paddedLevelLabel = (level: LogLevel) => {
89+
let label = levelLabel(level);
90+
return label.padStart(7, '\xa0');
91+
}
92+
93+
return (
94+
<Box className={classes.console}>
95+
{events.map(e => (
96+
<div className={classes.entry}>
97+
<span>{formatIsoDateTimeToHr(e.time)} </span>
98+
<span className={styleLevel(e.level)}>{paddedLevelLabel(e.level)} </span>
99+
<span className={classes.file}>{`${e.file}[${e.line}]`} </span>
100+
<span>{e.message}</span>
101+
</div>
102+
))}
103+
</Box>
104+
);
105+
};
106+
107+
108+
109+
export default LogEventConsole;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import Sockette from 'sockette';
3+
import { withSnackbar, WithSnackbarProps } from 'notistack';
4+
5+
import { addAccessTokenParameter } from '../authentication';
6+
import { LogEvent } from './types';
7+
import { WEB_SOCKET_ROOT } from '../api/Env';
8+
import LogEventConsole from './LogEventConsole';
9+
10+
const LOG_EVENT_WEB_SOCKET_URL = WEB_SOCKET_ROOT + "log";
11+
12+
interface LogEventControllerState {
13+
ws: Sockette;
14+
connected: boolean;
15+
events: LogEvent[];
16+
}
17+
18+
class LogEventController extends React.Component<WithSnackbarProps, LogEventControllerState> {
19+
20+
constructor(props: WithSnackbarProps) {
21+
super(props);
22+
this.state = {
23+
ws: new Sockette(addAccessTokenParameter(LOG_EVENT_WEB_SOCKET_URL), {
24+
onmessage: this.onMessage,
25+
onopen: this.onOpen,
26+
onclose: this.onClose,
27+
}),
28+
connected: false,
29+
events: []
30+
};
31+
}
32+
33+
componentWillUnmount() {
34+
this.state.ws.close();
35+
}
36+
37+
onMessage = (event: MessageEvent) => {
38+
const rawData = event.data;
39+
if (typeof rawData === 'string' || rawData instanceof String) {
40+
const event = JSON.parse(rawData as string) as LogEvent;
41+
this.setState(state => ({ events: [...state.events, event] }));
42+
}
43+
}
44+
45+
onOpen = () => {
46+
this.setState({ connected: true });
47+
}
48+
49+
onClose = () => {
50+
this.setState({ connected: false });
51+
}
52+
53+
render() {
54+
return (
55+
<LogEventConsole events={this.state.events} />
56+
);
57+
}
58+
59+
}
60+
61+
export default withSnackbar(LogEventController);

interface/src/system/System.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { MenuAppBar } from '../components';
88

99
import SystemStatusController from './SystemStatusController';
1010
import OTASettingsController from './OTASettingsController';
11+
import LogEventController from './LogEventController';
1112

1213
type SystemProps = AuthenticatedContextProps & RouteComponentProps;
1314

@@ -21,12 +22,14 @@ class System extends Component<SystemProps> {
2122
const { authenticatedContext } = this.props;
2223
return (
2324
<MenuAppBar sectionTitle="System">
24-
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
25+
<Tabs id="system-tabs" value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
2526
<Tab value="/system/status" label="System Status" />
27+
<Tab value="/system/log" label="Log" />
2628
<Tab value="/system/ota" label="OTA Settings" disabled={!authenticatedContext.me.admin} />
2729
</Tabs>
2830
<Switch>
2931
<AuthenticatedRoute exact path="/system/status" component={SystemStatusController} />
32+
<AuthenticatedRoute exact path="/system/log" component={LogEventController} />
3033
<AuthenticatedRoute exact path="/system/ota" component={OTASettingsController} />
3134
<Redirect to="/system/status" />
3235
</Switch>

interface/src/system/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,19 @@ export interface OTASettings {
1515
port: number;
1616
password: string;
1717
}
18+
19+
export enum LogLevel {
20+
DEBUG = 0,
21+
INFO = 1,
22+
WARNING = 2,
23+
ERROR = 3
24+
}
25+
26+
27+
export interface LogEvent {
28+
time: string;
29+
level: LogLevel;
30+
file: string;
31+
line: number;
32+
message: string;
33+
}

lib/framework/ESPUtils.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@
55

66
class ESPUtils {
77
public:
8-
static String defaultDeviceValue(String prefix = "") {
8+
static String defaultDeviceValue(const String prefix = "") {
99
#ifdef ESP32
1010
return prefix + String((unsigned long)ESP.getEfuseMac(), HEX);
1111
#elif defined(ESP8266)
1212
return prefix + String(ESP.getChipId(), HEX);
1313
#endif
1414
}
1515

16-
static String toISOString(tm* time, bool incOffset) {
16+
static String toISOString(const tm* time, bool incOffset) {
1717
char time_string[25];
1818
strftime(time_string, 25, incOffset ? "%FT%T%z" : "%FT%TZ", time);
1919
return String(time_string);
2020
}
2121

22-
static String toHrString(tm* time) {
23-
char time_string[22];
24-
strftime(time_string, 22, "%F %T%z", time);
22+
static String toHrString(const tm* time) {
23+
char time_string[25];
24+
strftime(time_string, 25, "%F %T%z", time);
2525
return time_string;
2626
}
2727
};

lib/framework/Logger.h

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,22 @@
66
#include <functional>
77
#include <time.h>
88

9-
#define COLOR_RESET "\x1B[0m"
10-
#define COLOR_BLACK "\x1B[0;30m"
11-
#define COLOR_RED "\x1B[0;31m"
12-
#define COLOR_GREEN "\x1B[0;32m"
13-
#define COLOR_YELLOW "\x1B[0;33m"
14-
#define COLOR_BLUE "\x1B[0;34m"
15-
#define COLOR_MAGENTA "\x1B[0;35m"
16-
#define COLOR_CYAN "\x1B[0;36m"
17-
#define COLOR_WHITE "\x1B[0;37m"
9+
#define FSH_P const __FlashStringHelper*
1810

1911
enum LogLevel { DEBUG = 0, INFO = 1, WARNING = 2, ERROR = 3 };
2012

21-
#define LOGF_D(fmt, ...) Logger::logf(LogLevel::DEBUG, PSTR(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
22-
#define LOGF_I(fmt, ...) Logger::logf(LogLevel::INFO, PSTR(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
23-
#define LOGF_W(fmt, ...) Logger::logf(LogLevel::WARNING, PSTR(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
24-
#define LOGF_E(fmt, ...) Logger::logf(LogLevel::ERROR, PSTR(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
13+
#define LOGF_D(fmt, ...) Logger::logf(LogLevel::DEBUG, F(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
14+
#define LOGF_I(fmt, ...) Logger::logf(LogLevel::INFO, F(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
15+
#define LOGF_W(fmt, ...) Logger::logf(LogLevel::WARNING, F(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
16+
#define LOGF_E(fmt, ...) Logger::logf(LogLevel::ERROR, F(__FILE__), __LINE__, PSTR(fmt), ##__VA_ARGS__)
2517

26-
#define LOG_D(msg) Logger::log(LogLevel::DEBUG, PSTR(__FILE__), __LINE__, PSTR(msg))
27-
#define LOG_I(msg) Logger::log(LogLevel::INFO, PSTR(__FILE__), __LINE__, PSTR(msg))
28-
#define LOG_W(msg) Logger::log(LogLevel::WARNING, PSTR(__FILE__), __LINE__, PSTR(msg))
29-
#define LOG_E(msg) Logger::log(LogLevel::ERROR, PSTR(__FILE__), __LINE__, PSTR(msg))
18+
#define LOG_D(msg) Logger::log(LogLevel::DEBUG, F(__FILE__), __LINE__, F(msg))
19+
#define LOG_I(msg) Logger::log(LogLevel::INFO, F(__FILE__), __LINE__, F(msg))
20+
#define LOG_W(msg) Logger::log(LogLevel::WARNING, F(__FILE__), __LINE__, F(msg))
21+
#define LOG_E(msg) Logger::log(LogLevel::ERROR, F(__FILE__), __LINE__, F(msg))
3022

3123
typedef size_t log_event_handler_id_t;
32-
typedef std::function<void(tm* time, LogLevel level, const char* file, const uint16_t line, const char* message)>
33-
LogEventHandler;
24+
typedef std::function<void(tm* time, LogLevel level, String& file, int line, String& message)> LogEventHandler;
3425

3526
typedef struct LogEventHandlerInfo {
3627
static log_event_handler_id_t currentEventHandlerId;
@@ -60,11 +51,11 @@ class Logger {
6051
}
6152
}
6253

63-
static void log(LogLevel level, const char* file, int line, const char* message) {
54+
static void log(LogLevel level, FSH_P file, int line, FSH_P message) {
6455
logEvent(level, file, line, message);
6556
}
6657

67-
static void logf(LogLevel level, const char* file, int line, PGM_P format, ...) {
58+
static void logf(LogLevel level, FSH_P file, int line, PGM_P format, ...) {
6859
va_list args;
6960

7061
// inital buffer, we can extend it if the formatted string doesn't fit
@@ -89,7 +80,7 @@ class Logger {
8980
}
9081

9182
// we failed to allocate
92-
logEvent(level, file, line, PSTR("Error formatting log message"));
83+
logEvent(level, file, line, F("Error formatting log message"));
9384
}
9485

9586
private:
@@ -99,7 +90,7 @@ class Logger {
9990
// class is static-only, prevent instantiation
10091
}
10192

102-
static void logEvent(LogLevel level, char const* file, int line, char const* message) {
93+
static void logEvent(LogLevel level, String file, int line, String message) {
10394
time_t now = time(nullptr);
10495
tm* time = localtime(&now);
10596
for (const LogEventHandlerInfo& eventHandler : _eventHandlers) {

lib/framework/SerialLogHandler.h

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,42 @@
55
#include <Logger.h>
66
#include <ESPUtils.h>
77

8+
#define COLOR_RESET "\x1B[0m"
9+
#define COLOR_CYAN "\x1B[0;36m"
10+
#define COLOR_BLUE "\x1B[0;34m"
11+
#define COLOR_GREEN "\x1B[0;32m"
12+
#define COLOR_YELLOW "\x1B[0;33m"
13+
#define COLOR_RED "\x1B[0;31m"
14+
#define COLOR_WHITE "\x1B[0;37m"
15+
// #define COLOR_MAGENTA "\x1B[0;35m" - perhaps for trace?
16+
817
class SerialLogHandler {
918
public:
10-
static void logEvent(tm* time, LogLevel level, char const* file, int line, char const* message) {
19+
static void logEvent(const tm* time, LogLevel level, const String& file, int line, const String& message) {
1120
Serial.printf_P(PSTR("%s %s%7s %s%s[%d] %s%s\r\n"),
1221
ESPUtils::toHrString(time).c_str(),
1322
levelColor(level),
1423
levelString(level),
1524
COLOR_CYAN,
16-
file,
25+
file.c_str(),
1726
line,
1827
COLOR_RESET,
19-
message);
28+
message.c_str());
2029
}
2130

2231
private:
2332
static const char* levelString(LogLevel level) {
2433
switch (level) {
2534
case LogLevel::DEBUG:
26-
return PSTR("DEBUG");
35+
return "DEBUG";
2736
case LogLevel::INFO:
28-
return PSTR("INFO");
37+
return "INFO";
2938
case LogLevel::WARNING:
30-
return PSTR("WARNING");
39+
return "WARNING";
3140
case LogLevel::ERROR:
32-
return PSTR("ERROR");
41+
return "ERROR";
3342
default:
34-
return PSTR("UNKNOWN");
43+
return "UNKNOWN";
3544
}
3645
}
3746
static const char* levelColor(LogLevel level) {

0 commit comments

Comments
 (0)