Skip to content

Commit 2e64158

Browse files
authored
Analyze only a slice of file (#5)
* analyze only a subset of big files
1 parent 775c0b4 commit 2e64158

11 files changed

+169
-66
lines changed

README.md

+26-6
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ For details, refer to its [README.md](https://github.com/vish9812/analog/blob/ma
4343

4444
## Prerequisite
4545

46-
- Ensure you have Python 3 installed on your system then you can simply execute the `analog script` to run the app.
46+
- Ensure you have Python 3 [installed](https://www.python.org/downloads/) on your system then you can simply execute the Python server command to run the app.
4747
- Otherwise host the `index.html` manually on any server of your preference.
4848

4949
## Getting Started
@@ -56,19 +56,39 @@ Follow these steps to run the app:
5656

5757
3. Open a terminal and navigate to the app's directory.
5858

59-
4. Execute the `analog script` to start the app.
59+
4. Execute the following `python` command to start the app:
6060

61-
- For Non-Windows, execute the script `./analog.sh`.
62-
- For Windows, use powershell:
63-
- _Unblock_(one-time operation) the powershell file `analog.ps1` to be allowed to execute on the system: `Unblock-File .\analog.ps1`
64-
- Execute the script `.\analog.ps1`
61+
- For Linux/Mac: `python3 -m http.server 20002`
62+
- For Windows: `python -m http.server 20002`
6563

6664
5. Open your web browser and visit the following URL: `localhost:20002`
6765

6866
You are now ready to use the Analog and take advantage of its powerful log analysis features.
6967

7068
Enjoy analyzing your log data!
7169

70+
## FAQ
71+
72+
### Expected log format
73+
74+
Following are the 3 must have keys in the logs:
75+
76+
- level
77+
- timestamp
78+
- msg
79+
80+
Example Format for JSON logs:
81+
82+
```
83+
{"timestamp":"2023-10-16 10:13:16.710 +11:00","level":"debug","msg":"Received HTTP request","dynamicKey1":"value 1","dynamicKey2":"value 2"}
84+
```
85+
86+
Example Format for plain-text logs:
87+
88+
```
89+
debug [2023-10-16 10:13:16.710 +11:00] Received HTTP request dynamicKey1="value 1" dynamicKey2=value 2
90+
```
91+
7292
## License
7393

7494
This app is released under the [MIT License](https://github.com/vish9812/analog/blob/main/LICENSE).

analog.ps1

-1
This file was deleted.

analog.sh

-2
This file was deleted.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"test": "vitest --silent",
1414
"test-logs": "vitest",
1515
"coverage": "vitest run --coverage --silent",
16-
"build": "vite build && cp analog.sh analog && cp analog.ps1 analog && bun build ./src/cmd/index.ts --compile --outfile ./analog/analog && zip -r analog.zip analog",
16+
"build": "vite build && bun build ./src/cmd/index.ts --compile --outfile ./analog/analog && zip -r analog.zip analog",
1717
"preview": "vite preview"
1818
},
1919
"license": "MIT",

src/models/logData.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ describe("init", () => {
115115
}
116116

117117
const logData = new LogData();
118-
logData.init(file as any, logsIterator);
118+
logData.initFileInfo(file as any);
119+
logData.init(logsIterator);
119120

120121
expect(logData.fileInfo.name, "file name").toEqual(file.name);
121122
expect(logData.fileInfo.size, "file size").toEqual(file.size);

src/models/logData.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ class LogData {
6969
error: "error",
7070
};
7171

72-
init(file: File, logsGeneratorFn: () => LogsGenerator) {
73-
this.initFileInfo(file);
74-
72+
init(logsGeneratorFn: () => LogsGenerator) {
7573
const summaryMap: SummaryMap = {
7674
httpCodes: new Map<string, GroupedMsg>(),
7775
jobs: new Map<string, GroupedMsg>(),
@@ -83,7 +81,6 @@ class LogData {
8381
let count = 0;
8482
for (const log of logsGeneratorFn()) {
8583
if (log == null) {
86-
console.warn("non-supported log format.");
8784
continue;
8885
}
8986

@@ -107,7 +104,7 @@ class LogData {
107104
);
108105
}
109106

110-
private initFileInfo(file: File) {
107+
initFileInfo(file: File) {
111108
this.fileInfo = {
112109
name: file.name,
113110
size: file.size,

src/pages/normalize/index.tsx

+59-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ListItemIcon,
99
ListItemText,
1010
Stack,
11+
TextField,
1112
styled,
1213
} from "@suid/material";
1314
import useViewModel from "./useViewModel";
@@ -41,13 +42,31 @@ function Normalize() {
4142
processingFile,
4243
handleAnalyzeClick,
4344
handleFileUpload,
45+
setTimeRange,
4446
} = useViewModel();
4547

4648
const newFileText = () =>
4749
logDatas().length === 1 ? "Compare With" : "New File";
4850

4951
return (
5052
<Grid container spacing={2}>
53+
<Grid item xs={12}>
54+
<p>Following are the 3 must have keys in the logs:</p>
55+
<pre>{`
56+
- level
57+
- timestamp
58+
- msg
59+
`}</pre>
60+
<p>Example Format for JSON logs:</p>
61+
<pre>{`
62+
{"timestamp":"2023-10-16 10:13:16.710 +11:00","level":"debug","msg":"Received HTTP request","dynamicKey1":"value 1","dynamicKey2":"value 2"}
63+
`}</pre>
64+
65+
<p>Example Format for plain-text logs:</p>
66+
<pre>{`
67+
debug [2023-10-16 10:13:16.710 +11:00] Received HTTP request dynamicKey1="value 1" dynamicKey2=value 2
68+
`}</pre>
69+
</Grid>
5170
<Grid item xs={12}>
5271
<Stack
5372
direction="row"
@@ -66,7 +85,11 @@ function Normalize() {
6685
<HiddenInput
6786
type="file"
6887
multiple={false}
69-
onChange={(e) => handleFileUpload(e.target.files!, new LogData())}
88+
onChange={(e) => {
89+
handleFileUpload(e.target.files, new LogData());
90+
// To allow uploading the same file again and triggering the "onChange" event for the same file
91+
e.target.value = "";
92+
}}
7093
/>
7194
</Button>
7295
<Show when={processingFile()}>
@@ -86,21 +109,45 @@ function Normalize() {
86109
</Stack>
87110
</Grid>
88111
<Show when={!!logDatas().length}>
89-
<Grid item xs={3}>
112+
<Grid item xs={12} textAlign="center">
113+
You can <em>optionally</em> filter the files by time
114+
<br />- In case of big files( &gt; 70MB) for faster processing of data
115+
<br />- To compare different time slices of the same or different
116+
files
117+
</Grid>
118+
<Grid item xs={12}>
90119
<List subheader="Files">
91120
<For each={logDatas()}>
92-
{(logData) => {
121+
{(logData, idx) => {
93122
return (
94123
<ListItem disablePadding>
95-
<ListItemButton>
96-
<ListItemIcon>
97-
<InsertDriveFileIcon />
98-
</ListItemIcon>
99-
<ListItemText
100-
primary={logData.fileInfo.name}
101-
secondary={prettyBytes(logData.fileInfo.size)}
102-
/>
103-
</ListItemButton>
124+
<Grid container spacing={2}>
125+
<Grid item xs={3}>
126+
<ListItemButton>
127+
<ListItemIcon>
128+
<InsertDriveFileIcon />
129+
</ListItemIcon>
130+
<ListItemText
131+
primary={logData.fileInfo.name}
132+
secondary={prettyBytes(logData.fileInfo.size)}
133+
/>
134+
</ListItemButton>
135+
</Grid>
136+
<Grid item xs={3}>
137+
<TextField
138+
fullWidth={true}
139+
label="Start Time"
140+
onChange={(_, val) => setTimeRange(idx(), "min", val)}
141+
/>
142+
</Grid>
143+
<Grid item xs={3}>
144+
<TextField
145+
fullWidth={true}
146+
label="End Time"
147+
onChange={(_, val) => setTimeRange(idx(), "max", val)}
148+
/>
149+
</Grid>
150+
</Grid>
104151
</ListItem>
105152
);
106153
}}

src/pages/normalize/useViewModel.test.tsx

+5-7
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ describe("useViewModel", () => {
2424
createRoot((dispose) => {
2525
const setPage = vi.fn();
2626

27-
useViewModel().handleAnalyzeClick(setPage);
27+
const vm = useViewModel();
28+
vm.handleAnalyzeClick(setPage);
2829

30+
expect(vm.processingFile(), "processingFile").toEqual(false);
31+
expect(vm.analyzeDisabled(), "analyzeDisabled").toEqual(false);
2932
expect(setPage, "setPage").toHaveBeenCalledWith(Pages.analyze);
3033

3134
dispose();
@@ -47,20 +50,15 @@ describe("useViewModel", () => {
4750
"$filesLen file uploaded",
4851
async ({ files, newFileDisabled, filesLen }) => {
4952
const logData = new LogData();
50-
const spyInit = vi.spyOn(normalizer, "init").mockResolvedValue();
51-
const spyAddLogData = vi.spyOn(comparer, "addLogData");
5253

5354
await vm.handleFileUpload(files as any, logData);
5455

5556
expect(vm.analyzeDisabled(), "analyzeDisabled").toEqual(false);
56-
expect(vm.processingFile(), "processingFile").toEqual(false);
5757
expect(vm.newFileDisabled(), "newFileDisabled").toEqual(
5858
newFileDisabled
5959
);
6060
expect(vm.logDatas(), "logDatas").toBeTruthy();
61-
expect(vm.logDatas().length, "logDatas().length").toEqual(filesLen);
62-
expect(spyInit, "spyInit").toHaveBeenCalledWith(logData, files[0]);
63-
expect(spyAddLogData, "spyAddLogData").toHaveBeenCalledWith(logData);
61+
expect(vm.logDatas().length, "length").toEqual(filesLen);
6462
}
6563
);
6664

src/pages/normalize/useViewModel.tsx

+43-11
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,63 @@
11
import comparer from "@al/services/comparer";
2-
import LogData from "@al/models/logData";
2+
import LogData, { type JSONLog } from "@al/models/logData";
33
import { Setter, createSignal } from "solid-js";
44
import { PagesValues, Pages } from "../usePage";
55
import normalizer from "@al/services/normalizer";
6+
import { createStore } from "solid-js/store";
7+
8+
// Temporarily store files
9+
// Empty the array once processed to free the memory
10+
let logFiles: File[] = [];
11+
12+
interface TimeRange {
13+
min: string;
14+
max: string;
15+
}
616

717
function useViewModel() {
818
const [analyzeDisabled, setAnalyzeDisabled] = createSignal(true);
919
const [processingFile, setProcessingFile] = createSignal(false);
20+
const [timeRange, setTimeRange] = createStore<TimeRange[]>([]);
1021
const [logDatas, setLogDatas] = createSignal<LogData[]>([]);
1122
const newFileDisabled = () => logDatas().length > 1;
1223

13-
const handleAnalyzeClick = (setPage: Setter<PagesValues>) => {
24+
async function handleAnalyzeClick(setPage: Setter<PagesValues>) {
25+
setProcessingFile(true);
26+
setAnalyzeDisabled(true);
27+
28+
for (let i = 0; i < logFiles.length; i++) {
29+
const logData = logDatas()[i];
30+
await normalizer.init(logData, logFiles[i], getFilterFn(timeRange[i]));
31+
comparer.addLogData(logData);
32+
}
33+
34+
setAnalyzeDisabled(false);
35+
setProcessingFile(false);
36+
37+
logFiles = [];
38+
1439
setPage(Pages.analyze);
15-
};
40+
}
1641

17-
const handleFileUpload = async (files: FileList, logData: LogData) => {
42+
function handleFileUpload(files: FileList | null, logData: LogData) {
1843
if (!files || !files.length) return;
1944

20-
setProcessingFile(true);
21-
setAnalyzeDisabled(true);
45+
const file = files[0];
46+
logData.initFileInfo(file);
2247

23-
await normalizer.init(logData, files[0]);
24-
comparer.addLogData(logData);
48+
logFiles.push(file);
2549
setLogDatas((prev) => [...prev, logData]);
26-
50+
setTimeRange(timeRange.length, { min: "", max: "" });
2751
setAnalyzeDisabled(false);
28-
setProcessingFile(false);
29-
};
52+
}
53+
54+
function getFilterFn(timeRange: TimeRange) {
55+
return ({ timestamp }: JSONLog) =>
56+
!!(
57+
(timeRange.min && timestamp < timeRange.min) ||
58+
(timeRange.max && timestamp > timeRange.max)
59+
);
60+
}
3061

3162
return {
3263
logDatas,
@@ -35,6 +66,7 @@ function useViewModel() {
3566
processingFile,
3667
handleAnalyzeClick,
3768
handleFileUpload,
69+
setTimeRange,
3870
};
3971
}
4072

0 commit comments

Comments
 (0)