@wener/common 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ai/qwen3vl/utils.js.map +1 -1
- package/lib/cn/ChineseResidentIdNo.js +1 -1
- package/lib/cn/ChineseResidentIdNo.js.map +1 -1
- package/lib/cn/Mod11.js +1 -1
- package/lib/cn/Mod11.js.map +1 -1
- package/lib/consola/formatLogObject.js +5 -5
- package/lib/consola/formatLogObject.js.map +1 -1
- package/lib/data/maybeNumber.js +1 -1
- package/lib/data/maybeNumber.js.map +1 -1
- package/lib/data/types.d.js.map +1 -1
- package/lib/dayjs/formatDuration.js.map +1 -1
- package/lib/dayjs/resolveRelativeTime.js +9 -80
- package/lib/dayjs/resolveRelativeTime.js.map +1 -1
- package/lib/drain3/Drain.js +356 -0
- package/lib/drain3/Drain.js.map +1 -0
- package/lib/drain3/LogCluster.js +38 -0
- package/lib/drain3/LogCluster.js.map +1 -0
- package/lib/drain3/Node.js +39 -0
- package/lib/drain3/Node.js.map +1 -0
- package/lib/drain3/TemplateMiner.js +204 -0
- package/lib/drain3/TemplateMiner.js.map +1 -0
- package/lib/drain3/index.js +31 -0
- package/lib/drain3/index.js.map +1 -0
- package/lib/drain3/persistence/FilePersistence.js +24 -0
- package/lib/drain3/persistence/FilePersistence.js.map +1 -0
- package/lib/drain3/persistence/MemoryPersistence.js +18 -0
- package/lib/drain3/persistence/MemoryPersistence.js.map +1 -0
- package/lib/drain3/persistence/PersistenceHandler.js +5 -0
- package/lib/drain3/persistence/PersistenceHandler.js.map +1 -0
- package/lib/drain3/types.js +7 -0
- package/lib/drain3/types.js.map +1 -0
- package/lib/fs/IFileSystem.d.js.map +1 -1
- package/lib/fs/createBrowserFileSystem.js +4 -2
- package/lib/fs/createBrowserFileSystem.js.map +1 -1
- package/lib/fs/createMemoryFileSystem.js +7 -6
- package/lib/fs/createMemoryFileSystem.js.map +1 -1
- package/lib/fs/createSandboxFileSystem.js.map +1 -1
- package/lib/fs/createWebDavFileSystem.js +22 -5
- package/lib/fs/createWebDavFileSystem.js.map +1 -1
- package/lib/fs/createWebFileSystem.js +225 -0
- package/lib/fs/createWebFileSystem.js.map +1 -0
- package/lib/fs/findMimeType.js +1 -1
- package/lib/fs/findMimeType.js.map +1 -1
- package/lib/fs/index.js +1 -1
- package/lib/fs/index.js.map +1 -1
- package/lib/fs/minio/createMinioFileSystem.js +974 -0
- package/lib/fs/minio/createMinioFileSystem.js.map +1 -0
- package/lib/fs/minio/index.js +2 -0
- package/lib/fs/minio/index.js.map +1 -0
- package/lib/fs/orpc/createContractClientFileSystem.js +3 -3
- package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -1
- package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -1
- package/lib/fs/s3/createS3MiniFileSystem.js +116 -68
- package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -1
- package/lib/fs/server/createDatabaseFileSystem.js +7 -7
- package/lib/fs/server/createDatabaseFileSystem.js.map +1 -1
- package/lib/fs/server/createNodeFileSystem.js +30 -5
- package/lib/fs/server/createNodeFileSystem.js.map +1 -1
- package/lib/fs/tests/runFileSystemTest.js +27 -26
- package/lib/fs/tests/runFileSystemTest.js.map +1 -1
- package/lib/fs/utils.js.map +1 -1
- package/lib/fs/webdav/index.js +2 -0
- package/lib/fs/webdav/index.js.map +1 -0
- package/lib/jsonschema/JsonSchema.js +5 -5
- package/lib/jsonschema/JsonSchema.js.map +1 -1
- package/lib/jsonschema/forEachJsonSchema.js +1 -1
- package/lib/jsonschema/forEachJsonSchema.js.map +1 -1
- package/lib/jsonschema/types.d.js.map +1 -1
- package/lib/meta/defineMetadata.js.map +1 -1
- package/lib/orpc/createOpenApiContractClient.js.map +1 -1
- package/lib/password/PHC.js +2 -2
- package/lib/password/PHC.js.map +1 -1
- package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBase64PasswordAlgorithm.js +1 -1
- package/lib/password/createBase64PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -1
- package/lib/password/createPBKDF2PasswordAlgorithm.js +1 -1
- package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createScryptPasswordAlgorithm.js +3 -3
- package/lib/password/createScryptPasswordAlgorithm.js.map +1 -1
- package/lib/resource/ListQuery.js.map +1 -1
- package/lib/resource/index.js.map +1 -1
- package/lib/s3/formatS3Url.js +2 -2
- package/lib/s3/formatS3Url.js.map +1 -1
- package/lib/s3/parseS3Url.js +1 -1
- package/lib/s3/parseS3Url.js.map +1 -1
- package/lib/schema/SchemaRegistry.js.map +1 -1
- package/lib/schema/TypeSchema.d.js.map +1 -1
- package/lib/schema/createSchemaData.js +4 -4
- package/lib/schema/createSchemaData.js.map +1 -1
- package/lib/schema/findJsonSchemaByPath.js.map +1 -1
- package/lib/schema/formatZodError.js +42 -44
- package/lib/schema/formatZodError.js.map +1 -1
- package/lib/schema/toJsonSchema.js +4 -4
- package/lib/schema/toJsonSchema.js.map +1 -1
- package/lib/schema/validate.js +1 -1
- package/lib/schema/validate.js.map +1 -1
- package/lib/utils/buildRedactorFormSchema.js +1 -1
- package/lib/utils/buildRedactorFormSchema.js.map +1 -1
- package/package.json +32 -13
- package/src/ai/qwen3vl/utils.ts +1 -1
- package/src/cn/ChineseResidentIdNo.ts +1 -1
- package/src/cn/Mod11.ts +1 -1
- package/src/cn/__snapshots__/ChineseResidentIdNo.test.ts.snap +1 -1
- package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +0 -23
- package/src/cn/parseChineseNumber.test.ts +4 -4
- package/src/consola/formatLogObject.ts +6 -6
- package/src/data/maybeNumber.ts +1 -1
- package/src/data/parseSort.test.ts +0 -1
- package/src/data/types.d.ts +2 -2
- package/src/dayjs/formatDuration.ts +2 -2
- package/src/dayjs/resolveRelativeTime.ts +11 -14
- package/src/drain3/Drain.test.ts +378 -0
- package/src/drain3/Drain.ts +394 -0
- package/src/drain3/LogCluster.ts +46 -0
- package/src/drain3/Node.ts +53 -0
- package/src/drain3/TemplateMiner.ts +246 -0
- package/src/drain3/index.ts +36 -0
- package/src/drain3/persistence/FilePersistence.ts +24 -0
- package/src/drain3/persistence/MemoryPersistence.ts +23 -0
- package/src/drain3/persistence/PersistenceHandler.ts +19 -0
- package/src/drain3/types.ts +75 -0
- package/src/fs/IFileSystem.d.ts +1 -2
- package/src/fs/createBrowserFileSystem.ts +7 -5
- package/src/fs/createMemoryFileSystem.ts +9 -13
- package/src/fs/createSandboxFileSystem.ts +1 -1
- package/src/fs/createWebDavFileSystem.ts +28 -10
- package/src/fs/createWebFileSystem.ts +242 -0
- package/src/fs/findMimeType.ts +1 -4
- package/src/fs/index.ts +1 -1
- package/src/fs/minio/createMinioFileSystem.ts +1148 -0
- package/src/fs/minio/index.ts +1 -0
- package/src/fs/orpc/createContractClientFileSystem.ts +5 -5
- package/src/fs/orpc/server/createFileSystemContractImpl.ts +1 -1
- package/src/fs/s3/createS3MiniFileSystem.ts +119 -78
- package/src/fs/s3/s3fs.test.ts +441 -0
- package/src/fs/s3/s3mini.test.ts +2 -2
- package/src/fs/server/createDatabaseFileSystem.ts +7 -7
- package/src/fs/server/createNodeFileSystem.ts +32 -13
- package/src/fs/server/dbfs.test.ts +2 -1
- package/src/fs/tests/runFileSystemTest.ts +29 -28
- package/src/fs/utils.ts +1 -1
- package/src/fs/webdav/index.ts +1 -0
- package/src/jsonschema/JsonSchema.ts +5 -5
- package/src/jsonschema/forEachJsonSchema.ts +1 -1
- package/src/jsonschema/types.d.ts +1 -1
- package/src/meta/defineMetadata.ts +1 -1
- package/src/orpc/createOpenApiContractClient.ts +2 -2
- package/src/password/PHC.ts +3 -3
- package/src/password/createArgon2PasswordAlgorithm.ts +1 -1
- package/src/password/createBase64PasswordAlgorithm.ts +2 -2
- package/src/password/createBcryptPasswordAlgorithm.ts +4 -2
- package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
- package/src/password/createScryptPasswordAlgorithm.ts +4 -4
- package/src/resource/ListQuery.ts +4 -1
- package/src/resource/index.ts +2 -2
- package/src/s3/formatS3Url.test.ts +1 -1
- package/src/s3/formatS3Url.ts +2 -2
- package/src/s3/parseS3Url.ts +1 -1
- package/src/schema/SchemaRegistry.ts +1 -1
- package/src/schema/TypeSchema.d.ts +6 -6
- package/src/schema/createSchemaData.ts +4 -4
- package/src/schema/findJsonSchemaByPath.ts +4 -4
- package/src/schema/formatZodError.test.ts +2 -1
- package/src/schema/formatZodError.ts +50 -62
- package/src/schema/toJsonSchema.ts +6 -6
- package/src/schema/validate.ts +1 -1
- package/src/utils/buildRedactorFormSchema.ts +3 -3
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { LogCluster } from './LogCluster';
|
|
2
|
+
import { Node } from './Node';
|
|
3
|
+
import type { ClusterUpdateType, DrainOptions, SearchStrategy } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simple LRU cache using Map iteration order.
|
|
7
|
+
* Map preserves insertion order; re-inserting a key moves it to the end.
|
|
8
|
+
*/
|
|
9
|
+
class SimpleLRU<K, V> {
|
|
10
|
+
private readonly map = new Map<K, V>();
|
|
11
|
+
constructor(private readonly max: number) {}
|
|
12
|
+
|
|
13
|
+
get(key: K): V | undefined {
|
|
14
|
+
const value = this.map.get(key);
|
|
15
|
+
if (value !== undefined) {
|
|
16
|
+
// Move to end (most recently used)
|
|
17
|
+
this.map.delete(key);
|
|
18
|
+
this.map.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set(key: K, value: V): void {
|
|
24
|
+
if (this.map.has(key)) {
|
|
25
|
+
this.map.delete(key);
|
|
26
|
+
} else if (this.map.size >= this.max) {
|
|
27
|
+
// Evict oldest (first key)
|
|
28
|
+
const first = this.map.keys().next().value;
|
|
29
|
+
if (first !== undefined) {
|
|
30
|
+
this.map.delete(first);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
this.map.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
values(): IterableIterator<V> {
|
|
37
|
+
return this.map.values();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isSliceEqual<T>(a: T[], b: T[]): boolean {
|
|
42
|
+
if (a.length !== b.length) return false;
|
|
43
|
+
for (let i = 0; i < a.length; i++) {
|
|
44
|
+
if (a[i] !== b[i]) return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hasNumbers(s: string): boolean {
|
|
50
|
+
return /\d/.test(s);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Core Drain algorithm for log clustering.
|
|
55
|
+
*
|
|
56
|
+
* Drain is an online log parsing algorithm that groups log messages into clusters
|
|
57
|
+
* based on their structural similarity, extracting templates by parameterizing
|
|
58
|
+
* variable parts of the logs.
|
|
59
|
+
*/
|
|
60
|
+
export class Drain {
|
|
61
|
+
public readonly logClusterDepth: number;
|
|
62
|
+
public readonly maxNodeDepth: number;
|
|
63
|
+
public readonly simTh: number;
|
|
64
|
+
public readonly maxChildren: number;
|
|
65
|
+
public readonly rootNode: Node;
|
|
66
|
+
public readonly maxClusters: number;
|
|
67
|
+
public readonly extraDelimiters: readonly string[];
|
|
68
|
+
public readonly paramStr: string;
|
|
69
|
+
public readonly parametrizeNumericTokens: boolean;
|
|
70
|
+
|
|
71
|
+
private readonly idToCluster: SimpleLRU<number, LogCluster>;
|
|
72
|
+
private clustersCounter: number = 0;
|
|
73
|
+
|
|
74
|
+
constructor(options: DrainOptions = {}) {
|
|
75
|
+
this.logClusterDepth = options.logClusterDepth ?? 4;
|
|
76
|
+
this.simTh = options.simTh ?? 0.4;
|
|
77
|
+
this.maxChildren = options.maxChildren ?? 100;
|
|
78
|
+
this.maxClusters = options.maxClusters ?? 1000;
|
|
79
|
+
this.extraDelimiters = options.extraDelimiters ?? [];
|
|
80
|
+
this.paramStr = options.paramStr ?? '<*>';
|
|
81
|
+
this.parametrizeNumericTokens = options.parametrizeNumericTokens ?? true;
|
|
82
|
+
|
|
83
|
+
if (this.logClusterDepth < 3) {
|
|
84
|
+
throw new Error('depth argument must be at least 3');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.maxNodeDepth = this.logClusterDepth - 2;
|
|
88
|
+
this.rootNode = Node.newNode();
|
|
89
|
+
this.idToCluster = new SimpleLRU<number, LogCluster>(this.maxClusters);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
addLogMessage(content: string): { cluster: LogCluster; updateType: ClusterUpdateType } {
|
|
93
|
+
const contentTokens = this.getContentAsTokens(content);
|
|
94
|
+
const matchCluster = this.treeSearch(this.rootNode, contentTokens, this.simTh, false);
|
|
95
|
+
|
|
96
|
+
let updateType: ClusterUpdateType = 'none';
|
|
97
|
+
|
|
98
|
+
if (matchCluster === null) {
|
|
99
|
+
this.clustersCounter++;
|
|
100
|
+
const clusterId = this.clustersCounter;
|
|
101
|
+
const cluster = new LogCluster(clusterId, contentTokens);
|
|
102
|
+
this.idToCluster.set(clusterId, cluster);
|
|
103
|
+
this.addSeqToPrefixTree(this.rootNode, cluster);
|
|
104
|
+
updateType = 'created';
|
|
105
|
+
return { cluster, updateType };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const newTemplateTokens = this.createTemplate(contentTokens, matchCluster.logTemplateTokens);
|
|
109
|
+
|
|
110
|
+
if (!isSliceEqual(newTemplateTokens, matchCluster.logTemplateTokens)) {
|
|
111
|
+
matchCluster.logTemplateTokens = newTemplateTokens;
|
|
112
|
+
updateType = 'templateChanged';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
matchCluster.size++;
|
|
116
|
+
|
|
117
|
+
// Touch cluster to update its position in the LRU cache
|
|
118
|
+
this.idToCluster.get(matchCluster.clusterId);
|
|
119
|
+
|
|
120
|
+
return { cluster: matchCluster, updateType };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getContentAsTokens(content: string): string[] {
|
|
124
|
+
let processed = content.trim();
|
|
125
|
+
for (const delimiter of this.extraDelimiters) {
|
|
126
|
+
processed = processed.replaceAll(delimiter, ' ');
|
|
127
|
+
}
|
|
128
|
+
return processed.split(/\s+/).filter((token) => token.length > 0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private treeSearch(rootNode: Node, tokens: string[], simTh: number, includeParams: boolean): LogCluster | null {
|
|
132
|
+
const tokenCount = tokens.length;
|
|
133
|
+
const firstNode = rootNode.keyToChildNode.get(tokenCount.toString());
|
|
134
|
+
|
|
135
|
+
if (firstNode === undefined) return null;
|
|
136
|
+
|
|
137
|
+
if (tokenCount === 0) {
|
|
138
|
+
const firstClusterId = firstNode.clusterIds[0];
|
|
139
|
+
if (firstClusterId === undefined) return null;
|
|
140
|
+
return this.idToCluster.get(firstClusterId) ?? null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let currentNode: Node = firstNode;
|
|
144
|
+
let currentNodeDepth = 1;
|
|
145
|
+
for (const token of tokens) {
|
|
146
|
+
if (currentNodeDepth >= this.maxNodeDepth || currentNodeDepth === tokenCount) break;
|
|
147
|
+
|
|
148
|
+
const keyToChildNode: Map<string, Node> = currentNode.keyToChildNode;
|
|
149
|
+
let nextNode: Node | undefined = keyToChildNode.get(token);
|
|
150
|
+
if (nextNode === undefined) {
|
|
151
|
+
nextNode = keyToChildNode.get(this.paramStr);
|
|
152
|
+
}
|
|
153
|
+
if (nextNode === undefined) return null;
|
|
154
|
+
|
|
155
|
+
currentNode = nextNode;
|
|
156
|
+
currentNodeDepth += 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return this.fastMatch(currentNode.clusterIds, tokens, simTh, includeParams);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fastMatch(clusterIds: number[], tokens: string[], simTh: number, includeParams: boolean): LogCluster | null {
|
|
163
|
+
let maxSim = -1;
|
|
164
|
+
let maxParamCount = -1;
|
|
165
|
+
let maxCluster: LogCluster | null = null;
|
|
166
|
+
|
|
167
|
+
for (const clusterId of clusterIds) {
|
|
168
|
+
const cluster = this.idToCluster.get(clusterId);
|
|
169
|
+
if (cluster === undefined) continue;
|
|
170
|
+
|
|
171
|
+
const [currentSim, paramCount] = this.getSeqDistance(cluster.logTemplateTokens, tokens, includeParams);
|
|
172
|
+
|
|
173
|
+
if (currentSim > maxSim || (currentSim === maxSim && paramCount > maxParamCount)) {
|
|
174
|
+
maxSim = currentSim;
|
|
175
|
+
maxParamCount = paramCount;
|
|
176
|
+
maxCluster = cluster;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return maxSim >= simTh ? maxCluster : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private getSeqDistance(seq1: string[], seq2: string[], includeParams: boolean): [number, number] {
|
|
184
|
+
if (seq1.length !== seq2.length) {
|
|
185
|
+
throw new Error(`seq1 length ${seq1.length} not equals to seq2 length ${seq2.length}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (seq1.length === 0) return [1, 0];
|
|
189
|
+
|
|
190
|
+
let simTokens = 0;
|
|
191
|
+
let paramCount = 0;
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < seq1.length; i++) {
|
|
194
|
+
if (seq1[i] === this.paramStr) {
|
|
195
|
+
paramCount++;
|
|
196
|
+
} else if (seq1[i] === seq2[i]) {
|
|
197
|
+
simTokens++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (includeParams) simTokens += paramCount;
|
|
202
|
+
|
|
203
|
+
return [simTokens / seq1.length, paramCount];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private addSeqToPrefixTree(rootNode: Node, cluster: LogCluster): void {
|
|
207
|
+
const tokenCount = cluster.logTemplateTokens.length;
|
|
208
|
+
const tokenCountStr = tokenCount.toString();
|
|
209
|
+
let firstLayerNode = rootNode.keyToChildNode.get(tokenCountStr);
|
|
210
|
+
if (firstLayerNode === undefined) {
|
|
211
|
+
firstLayerNode = Node.newNode();
|
|
212
|
+
rootNode.keyToChildNode.set(tokenCountStr, firstLayerNode);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let currentNode = firstLayerNode;
|
|
216
|
+
|
|
217
|
+
if (tokenCount === 0) {
|
|
218
|
+
currentNode.clusterIds = [cluster.clusterId];
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let currentDepth = 1;
|
|
223
|
+
for (const token of cluster.logTemplateTokens) {
|
|
224
|
+
if (currentDepth >= this.maxNodeDepth || currentDepth >= tokenCount) {
|
|
225
|
+
const newClusterIds: number[] = [];
|
|
226
|
+
for (const clusterId of currentNode.clusterIds) {
|
|
227
|
+
if (this.idToCluster.get(clusterId) !== undefined) {
|
|
228
|
+
newClusterIds.push(clusterId);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
newClusterIds.push(cluster.clusterId);
|
|
232
|
+
currentNode.clusterIds = newClusterIds;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!currentNode.keyToChildNode.has(token)) {
|
|
237
|
+
if (this.parametrizeNumericTokens && hasNumbers(token)) {
|
|
238
|
+
const node = currentNode.keyToChildNode.get(this.paramStr);
|
|
239
|
+
if (node === undefined) {
|
|
240
|
+
const newNode = Node.newNode();
|
|
241
|
+
currentNode.keyToChildNode.set(this.paramStr, newNode);
|
|
242
|
+
currentNode = newNode;
|
|
243
|
+
} else {
|
|
244
|
+
currentNode = node;
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
const wildcardNode = currentNode.keyToChildNode.get(this.paramStr);
|
|
248
|
+
if (wildcardNode !== undefined) {
|
|
249
|
+
if (currentNode.keyToChildNode.size < this.maxChildren) {
|
|
250
|
+
const newNode = Node.newNode();
|
|
251
|
+
currentNode.keyToChildNode.set(token, newNode);
|
|
252
|
+
currentNode = newNode;
|
|
253
|
+
} else {
|
|
254
|
+
currentNode = wildcardNode;
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
if (currentNode.keyToChildNode.size + 1 < this.maxChildren) {
|
|
258
|
+
const newNode = Node.newNode();
|
|
259
|
+
currentNode.keyToChildNode.set(token, newNode);
|
|
260
|
+
currentNode = newNode;
|
|
261
|
+
} else if (currentNode.keyToChildNode.size + 1 === this.maxChildren) {
|
|
262
|
+
const newNode = Node.newNode();
|
|
263
|
+
currentNode.keyToChildNode.set(this.paramStr, newNode);
|
|
264
|
+
currentNode = newNode;
|
|
265
|
+
} else {
|
|
266
|
+
currentNode = currentNode.keyToChildNode.get(this.paramStr)!;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
currentNode = currentNode.keyToChildNode.get(token)!;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
currentDepth++;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private createTemplate(seq1: string[], seq2: string[]): string[] {
|
|
279
|
+
if (seq1.length !== seq2.length) {
|
|
280
|
+
throw new Error(`seq1 length ${seq1.length} not equals to seq2 length ${seq2.length}`);
|
|
281
|
+
}
|
|
282
|
+
const retVal = [...seq2];
|
|
283
|
+
for (let i = 0; i < seq1.length; i++) {
|
|
284
|
+
if (seq1[i] !== seq2[i]) {
|
|
285
|
+
retVal[i] = this.paramStr;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return retVal;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
match(content: string, strategy: SearchStrategy = 'never'): LogCluster | null {
|
|
292
|
+
const requiredSimTh = 1.0;
|
|
293
|
+
const contentTokens = this.getContentAsTokens(content);
|
|
294
|
+
|
|
295
|
+
const fullSearch = (): LogCluster | null => {
|
|
296
|
+
const allIds = this.getClustersIdsForSeqLen(contentTokens.length);
|
|
297
|
+
return this.fastMatch(allIds, contentTokens, requiredSimTh, true);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
if (strategy === 'always') return fullSearch();
|
|
301
|
+
|
|
302
|
+
const matchCluster = this.treeSearch(this.rootNode, contentTokens, requiredSimTh, true);
|
|
303
|
+
if (matchCluster !== null) return matchCluster;
|
|
304
|
+
|
|
305
|
+
if (strategy === 'never') return null;
|
|
306
|
+
|
|
307
|
+
return fullSearch();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private getClustersIdsForSeqLen(seqLen: number): number[] {
|
|
311
|
+
const appendClusterRecursive = (node: Node, idListToFill: number[]): void => {
|
|
312
|
+
idListToFill.push(...node.clusterIds);
|
|
313
|
+
for (const childNode of node.keyToChildNode.values()) {
|
|
314
|
+
appendClusterRecursive(childNode, idListToFill);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const currentNode = this.rootNode.keyToChildNode.get(seqLen.toString());
|
|
319
|
+
if (currentNode === undefined) return [];
|
|
320
|
+
|
|
321
|
+
const target: number[] = [];
|
|
322
|
+
appendClusterRecursive(currentNode, target);
|
|
323
|
+
return target;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
getClusters(): LogCluster[] {
|
|
327
|
+
return [...this.idToCluster.values()];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
toJSON() {
|
|
331
|
+
return {
|
|
332
|
+
logClusterDepth: this.logClusterDepth,
|
|
333
|
+
maxNodeDepth: this.maxNodeDepth,
|
|
334
|
+
simTh: this.simTh,
|
|
335
|
+
maxChildren: this.maxChildren,
|
|
336
|
+
rootNode: this.rootNode.toJSON(),
|
|
337
|
+
maxClusters: this.maxClusters,
|
|
338
|
+
extraDelimiters: [...this.extraDelimiters],
|
|
339
|
+
paramStr: this.paramStr,
|
|
340
|
+
parametrizeNumericTokens: this.parametrizeNumericTokens,
|
|
341
|
+
clusters: this.getClusters().map((c) => c.toJSON()),
|
|
342
|
+
clustersCounter: this.clustersCounter,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
static fromJSON(data: {
|
|
347
|
+
logClusterDepth: number;
|
|
348
|
+
maxNodeDepth: number;
|
|
349
|
+
simTh: number;
|
|
350
|
+
maxChildren: number;
|
|
351
|
+
rootNode: {
|
|
352
|
+
keyToChildNode: Record<string, unknown>;
|
|
353
|
+
clusterIds: number[];
|
|
354
|
+
};
|
|
355
|
+
maxClusters: number;
|
|
356
|
+
extraDelimiters: string[];
|
|
357
|
+
paramStr: string;
|
|
358
|
+
parametrizeNumericTokens: boolean;
|
|
359
|
+
clusters: Array<{
|
|
360
|
+
clusterId: number;
|
|
361
|
+
logTemplateTokens: string[];
|
|
362
|
+
size: number;
|
|
363
|
+
}>;
|
|
364
|
+
clustersCounter: number;
|
|
365
|
+
}): Drain {
|
|
366
|
+
const drain = new Drain({
|
|
367
|
+
logClusterDepth: data.logClusterDepth,
|
|
368
|
+
simTh: data.simTh,
|
|
369
|
+
maxChildren: data.maxChildren,
|
|
370
|
+
maxClusters: data.maxClusters,
|
|
371
|
+
extraDelimiters: data.extraDelimiters,
|
|
372
|
+
paramStr: data.paramStr,
|
|
373
|
+
parametrizeNumericTokens: data.parametrizeNumericTokens,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
drain.rootNode.keyToChildNode.clear();
|
|
377
|
+
drain.rootNode.clusterIds = [];
|
|
378
|
+
|
|
379
|
+
const rootNode = Node.fromJSON(data.rootNode);
|
|
380
|
+
for (const [key, node] of rootNode.keyToChildNode) {
|
|
381
|
+
drain.rootNode.keyToChildNode.set(key, node);
|
|
382
|
+
}
|
|
383
|
+
drain.rootNode.clusterIds = rootNode.clusterIds;
|
|
384
|
+
|
|
385
|
+
for (const clusterData of data.clusters) {
|
|
386
|
+
const cluster = LogCluster.fromJSON(clusterData);
|
|
387
|
+
drain.idToCluster.set(cluster.clusterId, cluster);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
drain.clustersCounter = data.clustersCounter;
|
|
391
|
+
|
|
392
|
+
return drain;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a cluster of similar log messages with a common template.
|
|
3
|
+
*/
|
|
4
|
+
export class LogCluster {
|
|
5
|
+
constructor(
|
|
6
|
+
public readonly clusterId: number,
|
|
7
|
+
public logTemplateTokens: string[],
|
|
8
|
+
public size: number = 1,
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the template as a space-separated string.
|
|
13
|
+
*/
|
|
14
|
+
getTemplate(): string {
|
|
15
|
+
return this.logTemplateTokens.join(' ');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns a string representation of the cluster.
|
|
20
|
+
*/
|
|
21
|
+
toString(): string {
|
|
22
|
+
return `ID=${this.clusterId.toString().padEnd(5)} : size=${this.size.toString().padEnd(10)}: ${this.getTemplate()}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a LogCluster from JSON data.
|
|
27
|
+
*/
|
|
28
|
+
static fromJSON(data: { clusterId: number; logTemplateTokens: string[]; size: number }): LogCluster {
|
|
29
|
+
return new LogCluster(data.clusterId, data.logTemplateTokens, data.size);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Converts the cluster to JSON for serialization.
|
|
34
|
+
*/
|
|
35
|
+
toJSON(): {
|
|
36
|
+
clusterId: number;
|
|
37
|
+
logTemplateTokens: string[];
|
|
38
|
+
size: number;
|
|
39
|
+
} {
|
|
40
|
+
return {
|
|
41
|
+
clusterId: this.clusterId,
|
|
42
|
+
logTemplateTokens: this.logTemplateTokens,
|
|
43
|
+
size: this.size,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a node in the prefix tree used by Drain algorithm.
|
|
3
|
+
*/
|
|
4
|
+
export class Node {
|
|
5
|
+
/**
|
|
6
|
+
* Map of token keys to child nodes.
|
|
7
|
+
*/
|
|
8
|
+
public readonly keyToChildNode: Map<string, Node> = new Map();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List of cluster IDs associated with this node.
|
|
12
|
+
*/
|
|
13
|
+
public clusterIds: number[] = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new empty node.
|
|
17
|
+
*/
|
|
18
|
+
static newNode(): Node {
|
|
19
|
+
return new Node();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Converts the node to JSON for serialization.
|
|
24
|
+
*/
|
|
25
|
+
toJSON(): {
|
|
26
|
+
keyToChildNode: Record<string, unknown>;
|
|
27
|
+
clusterIds: number[];
|
|
28
|
+
} {
|
|
29
|
+
const keyToChildNode: Record<string, unknown> = {};
|
|
30
|
+
for (const [key, node] of this.keyToChildNode) {
|
|
31
|
+
keyToChildNode[key] = node.toJSON();
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
keyToChildNode,
|
|
35
|
+
clusterIds: this.clusterIds,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Node from JSON data.
|
|
41
|
+
*/
|
|
42
|
+
static fromJSON(data: { keyToChildNode: Record<string, unknown>; clusterIds: number[] }): Node {
|
|
43
|
+
const node = new Node();
|
|
44
|
+
node.clusterIds = data.clusterIds;
|
|
45
|
+
for (const [key, childData] of Object.entries(data.keyToChildNode)) {
|
|
46
|
+
node.keyToChildNode.set(
|
|
47
|
+
key,
|
|
48
|
+
Node.fromJSON(childData as { keyToChildNode: Record<string, unknown>; clusterIds: number[] }),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return node;
|
|
52
|
+
}
|
|
53
|
+
}
|