Skip to content

Commit 3cd0b0b

Browse files
Added better formatting, styling, colors, icons, etc... to log viewer component.
1 parent 1dbbbca commit 3cd0b0b

File tree

1 file changed

+157
-27
lines changed

1 file changed

+157
-27
lines changed

components/LogViewer.tsx

Lines changed: 157 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,109 @@
11
import React, { useEffect, useRef, useState } from 'react';
2-
import { ScrollView, Text, View, Button } from 'react-native';
2+
import { ScrollView, Text, View, Button, StyleSheet } from 'react-native';
33
import { ConsoleView } from 'react-native-console-view';
44

5+
// Log level types and styling properties
6+
type LogLevel = 'log' | 'error' | 'warn';
7+
8+
// Emoji indicators for each log level
9+
const emojiForLevel: Record<LogLevel, string> = {
10+
log: '🟢',
11+
error: '🔴',
12+
warn: '🟡',
13+
};
14+
15+
// Colors for each log level
16+
const colorForLevel: Record<LogLevel, string> = {
17+
log: '#2ecc40', // Green
18+
error: '#e74c3c', // Red
19+
warn: '#f1c40f', // Yellow
20+
};
21+
22+
interface LogEntry {
23+
level: LogLevel;
24+
message: string;
25+
timestamp: string;
26+
}
27+
528
const LogViewer = () => {
6-
const [logs, setLogs] = useState<string[]>([]);
29+
const [logs, setLogs] = useState<LogEntry[]>([]);
30+
const scrollViewRef = useRef<ScrollView>(null);
31+
32+
// Flag to prevent recursive logging
33+
const isLoggingRef = useRef(false);
734

8-
const flushingRef = useRef(false);
935
useEffect(() => {
1036
const originalLog = console.log;
1137
const originalError = console.error;
1238
const originalWarn = console.warn;
1339

14-
const enqueue = (level: 'log' | 'error' | 'warn', args: any[]) => {
15-
// Defer state update to microtask to avoid setState during another component's render
16-
Promise.resolve().then(() => {
17-
setLogs((prev) => [...prev, `[${level.toUpperCase()}] ${args.map(String).join(' ')}`]);
18-
});
40+
const enqueue = (level: LogLevel, args: any[]) => {
41+
// Prevent recursive logging
42+
if (isLoggingRef.current) {
43+
return;
44+
}
45+
46+
try {
47+
isLoggingRef.current = true;
48+
49+
// Format timestamp for better readability
50+
const now = new Date();
51+
const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now
52+
.getSeconds()
53+
.toString()
54+
.padStart(2, '0')}.${now.getMilliseconds().toString().padStart(3, '0')}`;
55+
56+
// Safely convert args to strings
57+
let messageStr = '';
58+
try {
59+
messageStr = args
60+
.map((arg) => {
61+
if (typeof arg === 'string') return arg;
62+
if (arg instanceof Error) return arg.toString();
63+
try {
64+
return JSON.stringify(arg);
65+
} catch (e) {
66+
return String(arg);
67+
}
68+
})
69+
.join(' ');
70+
} catch (err) {
71+
messageStr = 'Error stringifying log message';
72+
}
73+
74+
// Defer state update to microtask to avoid setState during another component's render
75+
Promise.resolve().then(() => {
76+
setLogs((prev) => {
77+
// Add new log to the beginning (newest first)
78+
const newLogs = [
79+
{
80+
level,
81+
message: messageStr,
82+
timestamp,
83+
},
84+
...prev,
85+
];
86+
87+
// Keep only the latest 100 logs to prevent memory issues
88+
return newLogs.slice(0, 100);
89+
});
90+
});
91+
} finally {
92+
isLoggingRef.current = false;
93+
}
1994
};
2095

2196
console.log = (...args) => {
22-
enqueue('log', args);
2397
originalLog(...args);
98+
enqueue('log', args);
2499
};
25100
console.error = (...args) => {
26-
enqueue('error', args);
27101
originalError(...args);
102+
enqueue('error', args);
28103
};
29104
console.warn = (...args) => {
30-
enqueue('warn', args);
31105
originalWarn(...args);
106+
enqueue('warn', args);
32107
};
33108

34109
return () => {
@@ -38,29 +113,84 @@ const LogViewer = () => {
38113
};
39114
}, []);
40115

116+
// We don't need auto-scroll since newest logs are at the top
117+
useEffect(() => {
118+
if (scrollViewRef.current && logs.length > 0) {
119+
scrollViewRef.current?.scrollTo({ y: 0, animated: true });
120+
}
121+
}, [logs.length]);
122+
41123
return (
42-
<View
43-
style={{
44-
flex: 1,
45-
padding: 10,
46-
backgroundColor: 'black',
47-
borderRadius: 15, // Rounded corners
48-
borderWidth: 2, // Border thickness
49-
borderColor: 'white', // White border
50-
margin: 10, // Margin for better visibility
51-
}}
52-
>
124+
<View style={styles.container}>
53125
<ConsoleView enabled={true} breakpoint={'mobile'} />
54-
<ScrollView style={{ marginTop: 10 }}>
126+
<ScrollView
127+
ref={scrollViewRef}
128+
style={styles.scrollView}
129+
contentContainerStyle={styles.scrollContent}
130+
showsVerticalScrollIndicator={true}
131+
nestedScrollEnabled={true}
132+
scrollEnabled={true}
133+
>
55134
{logs.map((log, index) => (
56-
<Text key={index} style={{ color: 'white', fontSize: 12 }}>
57-
{log}
58-
</Text>
135+
<View key={index} style={styles.logItem}>
136+
<Text style={[styles.logHeader, { color: colorForLevel[log.level] }]}>
137+
{emojiForLevel[log.level]} [{log.timestamp}] [{log.level.toUpperCase()}]
138+
</Text>
139+
<Text style={styles.logMessage}>{log.message}</Text>
140+
{index < logs.length - 1 && <View style={styles.separator} />}
141+
</View>
59142
))}
60143
</ScrollView>
61-
<Button title="Clear Logs" onPress={() => setLogs([])} />
144+
<View style={styles.buttonContainer}>
145+
<Button title="Clear Logs" color="#3498db" onPress={() => setLogs([])} />
146+
</View>
62147
</View>
63148
);
64149
};
65150

151+
const styles = StyleSheet.create({
152+
container: {
153+
padding: 10,
154+
backgroundColor: '#121212',
155+
borderRadius: 15,
156+
borderWidth: 2,
157+
borderColor: '#3498db',
158+
margin: 10,
159+
height: 400, // Fixed height instead of flex: 1
160+
overflow: 'hidden', // Prevent content from overflowing
161+
},
162+
scrollView: {
163+
marginTop: 10,
164+
height: 300, // Fixed height to ensure scrollability
165+
flexGrow: 0, // Prevent expanding
166+
},
167+
scrollContent: {
168+
paddingBottom: 10,
169+
},
170+
logItem: {
171+
marginBottom: 8,
172+
paddingHorizontal: 8,
173+
paddingVertical: 4,
174+
},
175+
logHeader: {
176+
fontSize: 13,
177+
fontWeight: 'bold',
178+
marginBottom: 4,
179+
},
180+
logMessage: {
181+
color: 'white',
182+
fontSize: 12,
183+
marginLeft: 8,
184+
},
185+
separator: {
186+
borderBottomWidth: 1,
187+
borderBottomColor: '#444',
188+
marginTop: 8,
189+
marginBottom: 8,
190+
},
191+
buttonContainer: {
192+
marginTop: 10,
193+
},
194+
});
195+
66196
export default LogViewer;

0 commit comments

Comments
 (0)