@ananotherdeveloper/drain3js 0.9.11-rev1
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/LICENSE +25 -0
- package/README.md +237 -0
- package/dist/drain.d.ts +67 -0
- package/dist/drain.d.ts.map +1 -0
- package/dist/drain.js +442 -0
- package/dist/drain.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/jaccard-drain.d.ts +9 -0
- package/dist/jaccard-drain.d.ts.map +1 -0
- package/dist/jaccard-drain.js +214 -0
- package/dist/jaccard-drain.js.map +1 -0
- package/dist/masking.d.ts +23 -0
- package/dist/masking.d.ts.map +1 -0
- package/dist/masking.js +59 -0
- package/dist/masking.js.map +1 -0
- package/dist/persistence/file-persistence.d.ts +8 -0
- package/dist/persistence/file-persistence.d.ts.map +1 -0
- package/dist/persistence/file-persistence.js +18 -0
- package/dist/persistence/file-persistence.js.map +1 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +4 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/memory-buffer-persistence.d.ts +7 -0
- package/dist/persistence/memory-buffer-persistence.d.ts.map +1 -0
- package/dist/persistence/memory-buffer-persistence.js +11 -0
- package/dist/persistence/memory-buffer-persistence.js.map +1 -0
- package/dist/persistence/persistence-handler.d.ts +5 -0
- package/dist/persistence/persistence-handler.d.ts.map +1 -0
- package/dist/persistence/persistence-handler.js +3 -0
- package/dist/persistence/persistence-handler.js.map +1 -0
- package/dist/simple-profiler.d.ts +34 -0
- package/dist/simple-profiler.d.ts.map +1 -0
- package/dist/simple-profiler.js +135 -0
- package/dist/simple-profiler.js.map +1 -0
- package/dist/template-miner-config.d.ts +48 -0
- package/dist/template-miner-config.d.ts.map +1 -0
- package/dist/template-miner-config.js +73 -0
- package/dist/template-miner-config.js.map +1 -0
- package/dist/template-miner.d.ts +35 -0
- package/dist/template-miner.d.ts.map +1 -0
- package/dist/template-miner.js +258 -0
- package/dist/template-miner.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Based on Drain3 by International Business Machines and the Drain3 project contributors.
|
|
4
|
+
Original: https://github.com/logpai/Drain3
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2020-2022 International Business Machines and the Drain3 project contributors.
|
|
7
|
+
Copyright (c) 2026 ananotherdeveloper
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# drain3js
|
|
2
|
+
|
|
3
|
+
TypeScript port of [Drain3](https://github.com/logpai/Drain3) - a persistent and streaming log template miner that uses a fixed-depth parse tree.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@ananotherdeveloper/drain3js)
|
|
6
|
+
[](https://github.com/ananotherdeveloper/drain3js/actions/workflows/ci.yml)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## What is Drain3?
|
|
10
|
+
|
|
11
|
+
Drain3 extracts log templates from raw log messages in a streaming fashion. Given logs like:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Connected to 10.0.0.1
|
|
15
|
+
Connected to 10.0.0.2
|
|
16
|
+
Disk error on /dev/sda1
|
|
17
|
+
Disk error on /dev/sdb2
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
It mines templates:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Connected to <IP>
|
|
24
|
+
Disk error on <*>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Based on the paper: *"Drain: An Online Log Parsing Approach with Fixed Depth Tree"* by Pinjia He, Jieming Zhu, Zibin Zheng, and Michael R. Lyu (ICWS 2017).
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @ananotherdeveloper/drain3js
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { TemplateMiner, TemplateMinerConfig } from '@ananotherdeveloper/drain3js';
|
|
39
|
+
|
|
40
|
+
const config = new TemplateMinerConfig();
|
|
41
|
+
const miner = new TemplateMiner(null, config);
|
|
42
|
+
|
|
43
|
+
const logs = [
|
|
44
|
+
'Connected to 10.0.0.1',
|
|
45
|
+
'Connected to 10.0.0.2',
|
|
46
|
+
'Disk error on /dev/sda1',
|
|
47
|
+
'Disk error on /dev/sdb2',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const log of logs) {
|
|
51
|
+
const result = miner.addLogMessage(log);
|
|
52
|
+
console.log(`Template: ${result.templateMined} (cluster #${result.clusterId})`);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Streaming**: Process logs one at a time - no need to batch
|
|
59
|
+
- **Persistent state**: Save and restore miner state via pluggable persistence handlers
|
|
60
|
+
- **Masking**: Regex-based masking to normalize IPs, numbers, hex values, etc. before mining
|
|
61
|
+
- **Two algorithms**: Standard `Drain` (token-count tree) and `JaccardDrain` (Jaccard similarity)
|
|
62
|
+
- **Parameter extraction**: Extract variable parameters from logs using mined templates
|
|
63
|
+
- **Memory efficient**: Optional LRU-based cluster eviction with configurable max clusters
|
|
64
|
+
- **Inference mode**: Match logs against existing templates without creating new clusters
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
Use `TemplateMinerConfig` to customize behavior:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { TemplateMinerConfig } from '@ananotherdeveloper/drain3js';
|
|
72
|
+
|
|
73
|
+
const config = new TemplateMinerConfig();
|
|
74
|
+
|
|
75
|
+
// Load from a JSON config object
|
|
76
|
+
config.load({
|
|
77
|
+
drain: {
|
|
78
|
+
engine: 'Drain', // 'Drain' or 'JaccardDrain'
|
|
79
|
+
simTh: 0.4, // Similarity threshold (default: 0.4)
|
|
80
|
+
depth: 4, // Parse tree depth (default: 4, minimum: 3)
|
|
81
|
+
maxChildren: 100, // Max children per node (default: 100)
|
|
82
|
+
maxClusters: null, // Max clusters, null = unlimited (default: null)
|
|
83
|
+
extraDelimiters: ['_'], // Additional token delimiters (default: [])
|
|
84
|
+
parametrizeNumericTokens: true,
|
|
85
|
+
},
|
|
86
|
+
masking: {
|
|
87
|
+
instructions: [
|
|
88
|
+
{ regexPattern: '((?:\\d+\\.){3}\\d+)', maskWith: 'IP' },
|
|
89
|
+
{ regexPattern: '0x[a-fA-F0-9]+', maskWith: 'HEX' },
|
|
90
|
+
{ regexPattern: '\\d+', maskWith: 'NUM' },
|
|
91
|
+
],
|
|
92
|
+
maskPrefix: '<',
|
|
93
|
+
maskSuffix: '>',
|
|
94
|
+
},
|
|
95
|
+
snapshot: {
|
|
96
|
+
snapshotIntervalMinutes: 5,
|
|
97
|
+
compressState: true,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const miner = new TemplateMiner(null, config);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
You can also load configuration from a JSON file:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
config.load('/path/to/config.json');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Persistence
|
|
111
|
+
|
|
112
|
+
Save and restore miner state using persistence handlers:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { TemplateMiner, TemplateMinerConfig, FilePersistence } from '@ananotherdeveloper/drain3js';
|
|
116
|
+
|
|
117
|
+
const persistence = new FilePersistence('./drain3_state.bin');
|
|
118
|
+
const config = new TemplateMinerConfig();
|
|
119
|
+
const miner = new TemplateMiner(persistence, config);
|
|
120
|
+
|
|
121
|
+
// State is automatically saved based on snapshotIntervalMinutes
|
|
122
|
+
// and whenever a new cluster is created
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Built-in persistence handlers:
|
|
126
|
+
|
|
127
|
+
| Handler | Description |
|
|
128
|
+
|---------|-------------|
|
|
129
|
+
| `FilePersistence` | Saves state to a local file |
|
|
130
|
+
| `MemoryBufferPersistence` | Stores state in memory (useful for testing) |
|
|
131
|
+
|
|
132
|
+
Implement the `PersistenceHandler` interface for custom storage (Redis, S3, etc.):
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { PersistenceHandler } from '@ananotherdeveloper/drain3js';
|
|
136
|
+
|
|
137
|
+
class RedisPersistence implements PersistenceHandler {
|
|
138
|
+
saveState(state: Buffer): void { /* ... */ }
|
|
139
|
+
loadState(): Buffer | null { /* ... */ }
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Masking
|
|
144
|
+
|
|
145
|
+
Mask sensitive or variable data before template mining:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
config.load({
|
|
149
|
+
masking: {
|
|
150
|
+
instructions: [
|
|
151
|
+
{ regexPattern: '((?:\\d+\\.){3}\\d+)', maskWith: 'IP' },
|
|
152
|
+
{ regexPattern: '0x[a-fA-F0-9]+', maskWith: 'HEX' },
|
|
153
|
+
{ regexPattern: '(?<=user=)\\w+', maskWith: 'USER' },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Input: `Connected to 10.0.0.1 by user=admin`
|
|
160
|
+
After masking: `Connected to <IP> by user=<USER>`
|
|
161
|
+
|
|
162
|
+
## Parameter Extraction
|
|
163
|
+
|
|
164
|
+
Extract variable parameters from logs using mined templates:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const result = miner.addLogMessage('Connected to 10.0.0.1');
|
|
168
|
+
const params = miner.extractParameters(result.templateMined, 'Connected to 10.0.0.1');
|
|
169
|
+
// [{ value: '10.0.0.1', maskName: 'IP' }]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Inference Mode
|
|
173
|
+
|
|
174
|
+
Match logs against existing templates without creating new clusters:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Training phase
|
|
178
|
+
miner.addLogMessage('User alice logged in');
|
|
179
|
+
miner.addLogMessage('User bob logged in');
|
|
180
|
+
|
|
181
|
+
// Inference phase
|
|
182
|
+
const match = miner.match('User charlie logged in');
|
|
183
|
+
if (match) {
|
|
184
|
+
console.log(`Matched template: ${match.getTemplate()}`);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The `match()` method supports three search strategies:
|
|
189
|
+
- `'never'` (default) - Only search the tree path
|
|
190
|
+
- `'fallback'` - Tree search first, then full search if no match
|
|
191
|
+
- `'always'` - Always search all clusters for the token count
|
|
192
|
+
|
|
193
|
+
## API Reference
|
|
194
|
+
|
|
195
|
+
### `TemplateMiner`
|
|
196
|
+
|
|
197
|
+
| Method | Description |
|
|
198
|
+
|--------|-------------|
|
|
199
|
+
| `addLogMessage(logMessage)` | Process a log message and return mining result |
|
|
200
|
+
| `match(logMessage, strategy?)` | Match against existing templates |
|
|
201
|
+
| `extractParameters(template, logMessage)` | Extract parameters with mask names |
|
|
202
|
+
| `getParameterList(template, logMessage)` | Extract parameter values only |
|
|
203
|
+
| `saveState(reason)` | Manually save state |
|
|
204
|
+
| `loadState()` | Manually load state |
|
|
205
|
+
|
|
206
|
+
### `MinerResult`
|
|
207
|
+
|
|
208
|
+
| Field | Type | Description |
|
|
209
|
+
|-------|------|-------------|
|
|
210
|
+
| `changeType` | `string` | `'cluster_created'`, `'cluster_template_changed'`, or `'none'` |
|
|
211
|
+
| `clusterId` | `number` | ID of the matched/created cluster |
|
|
212
|
+
| `clusterSize` | `number` | Number of logs in the cluster |
|
|
213
|
+
| `templateMined` | `string` | The current template string |
|
|
214
|
+
| `clusterCount` | `number` | Total number of clusters |
|
|
215
|
+
|
|
216
|
+
### Low-level API
|
|
217
|
+
|
|
218
|
+
Use `Drain` or `JaccardDrain` directly for advanced use cases:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { Drain } from '@ananotherdeveloper/drain3js';
|
|
222
|
+
|
|
223
|
+
const drain = new Drain(4, 0.4, 100);
|
|
224
|
+
const [cluster, changeType] = drain.addLogMessage('Connected to 10.0.0.1');
|
|
225
|
+
console.log(cluster.getTemplate());
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Attribution
|
|
229
|
+
|
|
230
|
+
This project is a TypeScript port of [Drain3](https://github.com/logpai/Drain3) by IBM Research.
|
|
231
|
+
|
|
232
|
+
The Drain algorithm is based on the paper:
|
|
233
|
+
> Pinjia He, Jieming Zhu, Zibin Zheng, and Michael R. Lyu. "Drain: An Online Log Parsing Approach with Fixed Depth Tree," *Proceedings of the 2017 IEEE International Conference on Web Services (ICWS)*, 2017.
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
[MIT](LICENSE)
|
package/dist/drain.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Profiler } from './simple-profiler.js';
|
|
2
|
+
export type FullSearchStrategy = 'never' | 'fallback' | 'always';
|
|
3
|
+
export declare class LogCluster {
|
|
4
|
+
logTemplateTokens: string[];
|
|
5
|
+
clusterId: number;
|
|
6
|
+
size: number;
|
|
7
|
+
constructor(logTemplateTokens: Iterable<string>, clusterId: number);
|
|
8
|
+
getTemplate(): string;
|
|
9
|
+
toString(): string;
|
|
10
|
+
}
|
|
11
|
+
export interface ClusterMap {
|
|
12
|
+
/** Get with LRU touch (updates recency) */
|
|
13
|
+
get(key: number): LogCluster | undefined;
|
|
14
|
+
/** Get without LRU touch (does not update recency) */
|
|
15
|
+
peek(key: number): LogCluster | undefined;
|
|
16
|
+
set(key: number, value: LogCluster): void;
|
|
17
|
+
has(key: number): boolean;
|
|
18
|
+
delete(key: number): boolean;
|
|
19
|
+
values(): IterableIterator<LogCluster>;
|
|
20
|
+
keys(): IterableIterator<number>;
|
|
21
|
+
readonly size: number;
|
|
22
|
+
}
|
|
23
|
+
export declare class Node {
|
|
24
|
+
keyToChildNode: Map<string, Node>;
|
|
25
|
+
clusterIds: number[];
|
|
26
|
+
constructor();
|
|
27
|
+
}
|
|
28
|
+
export declare abstract class DrainBase {
|
|
29
|
+
logClusterDepth: number;
|
|
30
|
+
maxNodeDepth: number;
|
|
31
|
+
simTh: number;
|
|
32
|
+
maxChildren: number;
|
|
33
|
+
rootNode: Node;
|
|
34
|
+
profiler: Profiler;
|
|
35
|
+
extraDelimiters: string[];
|
|
36
|
+
maxClusters: number | null;
|
|
37
|
+
paramStr: string;
|
|
38
|
+
parametrizeNumericTokens: boolean;
|
|
39
|
+
idToCluster: ClusterMap;
|
|
40
|
+
clustersCounter: number;
|
|
41
|
+
constructor(depth?: number, simTh?: number, maxChildren?: number, maxClusters?: number | null, extraDelimiters?: string[], profiler?: Profiler, paramStr?: string, parametrizeNumericTokens?: boolean);
|
|
42
|
+
get clusters(): LogCluster[];
|
|
43
|
+
static hasNumbers(s: string): boolean;
|
|
44
|
+
fastMatch(clusterIds: number[], tokens: string[], simTh: number, includeParams: boolean): LogCluster | null;
|
|
45
|
+
printTree(file?: {
|
|
46
|
+
write(s: string): void;
|
|
47
|
+
}, maxClusters?: number): void;
|
|
48
|
+
private printNode;
|
|
49
|
+
getContentAsTokens(content: string): string[];
|
|
50
|
+
addLogMessage(content: string): [LogCluster, string];
|
|
51
|
+
private arraysEqual;
|
|
52
|
+
getTotalClusterSize(): number;
|
|
53
|
+
getClustersIdsForSeqLen(seqFir: number | string): number[];
|
|
54
|
+
abstract treeSearch(rootNode: Node, tokens: string[], simTh: number, includeParams: boolean): LogCluster | null;
|
|
55
|
+
abstract addSeqToPrefixTree(rootNode: Node, cluster: LogCluster): void;
|
|
56
|
+
abstract getSeqDistance(seq1: string[], seq2: string[], includeParams: boolean): [number, number];
|
|
57
|
+
abstract createTemplate(seq1: string[], seq2: string[]): string[];
|
|
58
|
+
abstract match(content: string, fullSearchStrategy?: FullSearchStrategy): LogCluster | null;
|
|
59
|
+
}
|
|
60
|
+
export declare class Drain extends DrainBase {
|
|
61
|
+
treeSearch(rootNode: Node, tokens: string[], simTh: number, includeParams: boolean): LogCluster | null;
|
|
62
|
+
addSeqToPrefixTree(rootNode: Node, cluster: LogCluster): void;
|
|
63
|
+
getSeqDistance(seq1: string[], seq2: string[], includeParams: boolean): [number, number];
|
|
64
|
+
createTemplate(seq1: string[], seq2: string[]): string[];
|
|
65
|
+
match(content: string, fullSearchStrategy?: FullSearchStrategy): LogCluster | null;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=drain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../src/drain.ts"],"names":[],"mappings":"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"}
|