@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.
Files changed (47) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +237 -0
  3. package/dist/drain.d.ts +67 -0
  4. package/dist/drain.d.ts.map +1 -0
  5. package/dist/drain.js +442 -0
  6. package/dist/drain.js.map +1 -0
  7. package/dist/index.d.ts +13 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +16 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/jaccard-drain.d.ts +9 -0
  12. package/dist/jaccard-drain.d.ts.map +1 -0
  13. package/dist/jaccard-drain.js +214 -0
  14. package/dist/jaccard-drain.js.map +1 -0
  15. package/dist/masking.d.ts +23 -0
  16. package/dist/masking.d.ts.map +1 -0
  17. package/dist/masking.js +59 -0
  18. package/dist/masking.js.map +1 -0
  19. package/dist/persistence/file-persistence.d.ts +8 -0
  20. package/dist/persistence/file-persistence.d.ts.map +1 -0
  21. package/dist/persistence/file-persistence.js +18 -0
  22. package/dist/persistence/file-persistence.js.map +1 -0
  23. package/dist/persistence/index.d.ts +4 -0
  24. package/dist/persistence/index.d.ts.map +1 -0
  25. package/dist/persistence/index.js +4 -0
  26. package/dist/persistence/index.js.map +1 -0
  27. package/dist/persistence/memory-buffer-persistence.d.ts +7 -0
  28. package/dist/persistence/memory-buffer-persistence.d.ts.map +1 -0
  29. package/dist/persistence/memory-buffer-persistence.js +11 -0
  30. package/dist/persistence/memory-buffer-persistence.js.map +1 -0
  31. package/dist/persistence/persistence-handler.d.ts +5 -0
  32. package/dist/persistence/persistence-handler.d.ts.map +1 -0
  33. package/dist/persistence/persistence-handler.js +3 -0
  34. package/dist/persistence/persistence-handler.js.map +1 -0
  35. package/dist/simple-profiler.d.ts +34 -0
  36. package/dist/simple-profiler.d.ts.map +1 -0
  37. package/dist/simple-profiler.js +135 -0
  38. package/dist/simple-profiler.js.map +1 -0
  39. package/dist/template-miner-config.d.ts +48 -0
  40. package/dist/template-miner-config.d.ts.map +1 -0
  41. package/dist/template-miner-config.js +73 -0
  42. package/dist/template-miner-config.js.map +1 -0
  43. package/dist/template-miner.d.ts +35 -0
  44. package/dist/template-miner.d.ts.map +1 -0
  45. package/dist/template-miner.js +258 -0
  46. package/dist/template-miner.js.map +1 -0
  47. 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
+ [![npm version](https://img.shields.io/npm/v/%40ananotherdeveloper%2Fdrain3js.svg)](https://www.npmjs.com/package/@ananotherdeveloper/drain3js)
6
+ [![CI](https://github.com/ananotherdeveloper/drain3js/actions/workflows/ci.yml/badge.svg)](https://github.com/ananotherdeveloper/drain3js/actions/workflows/ci.yml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)
@@ -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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"}