├── doc └── pjhe_icws2017.pdf ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .gitattributes ├── tailer ├── src │ ├── test │ │ ├── resources │ │ │ └── 3-lines.txt │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── bric3 │ │ │ └── tailer │ │ │ └── file │ │ │ └── MappedFileLineReaderTest.java │ └── main │ │ └── java │ │ └── io │ │ └── github │ │ └── bric3 │ │ └── tailer │ │ ├── tail │ │ └── TailFile.java │ │ ├── config │ │ ├── Config.java │ │ └── FromLine.java │ │ ├── drain │ │ └── DrainFile.java │ │ ├── TailerMain.java │ │ └── file │ │ └── MappedFileLineReader.java └── build.gradle.kts ├── HEADER ├── drain-java-bom └── build.gradle.kts ├── drain-java-core ├── src │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── bric3 │ │ │ └── drain │ │ │ ├── core │ │ │ ├── LogCluster.java │ │ │ ├── package-info.java │ │ │ ├── DrainState.java │ │ │ ├── Node.java │ │ │ ├── InternalLogCluster.java │ │ │ └── Drain.java │ │ │ └── internal │ │ │ ├── Tokenizer.java │ │ │ └── Stopwatch.java │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── bric3 │ │ └── drain │ │ ├── utils │ │ └── TestPaths.java │ │ └── core │ │ └── DrainBulkTest.java └── build.gradle.kts ├── settings.gradle.kts ├── drain-java-jackson ├── build.gradle.kts └── src │ ├── test │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── bric3 │ │ │ └── drain │ │ │ ├── utils │ │ │ └── TestPaths.java │ │ │ └── core │ │ │ └── DrainJsonSerializationTest.java │ └── resources │ │ └── Unity.log │ └── main │ └── java │ └── io │ └── github │ └── bric3 │ └── drain │ └── core │ └── DrainJsonSerialization.java ├── .github ├── renovate.json5 └── workflows │ └── gradle.yml ├── gradlew.bat ├── .gitignore ├── README.adoc ├── gradlew ├── LICENSE └── .editorconfig /doc/pjhe_icws2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/drain-java/HEAD/doc/pjhe_icws2017.pdf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/drain-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /tailer/src/test/resources/3-lines.txt: -------------------------------------------------------------------------------- 1 | parsed :ESC[?2004l 2 | bytes read :ESC[ 3 | 2021-01-05 20:48:34,044 [13366131] ERROR - terminal.emulator.JediEmulator - Error processing OSC 1;java 4 | -------------------------------------------------------------------------------- /HEADER: -------------------------------------------------------------------------------- 1 | drain-java 2 | 3 | Copyright (c) ${year} - ${name} 4 | 5 | This Source Code Form is subject to the terms of the Mozilla Public 6 | License, v. 2.0. If a copy of the MPL was not distributed with this 7 | file, You can obtain one at https://mozilla.org/MPL/2.0/. -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /drain-java-bom/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-platform` 3 | `maven-publish` 4 | } 5 | 6 | description = "Drain Java - BOM" 7 | 8 | dependencies { 9 | constraints { 10 | rootProject.subprojects { 11 | if (name.startsWith("drain-java-") && !name.endsWith("-bom")) { 12 | api("${group}:${name}:${version}") 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/core/LogCluster.java: -------------------------------------------------------------------------------- 1 | package io.github.bric3.drain.core; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | /** 7 | * Represents a cluster of log message parts. 8 | * 9 | *

A cluster consists of a list of tokens, one of the token might be the wild card {@link Drain#PARAM_MARKER <*>}. 10 | */ 11 | public interface LogCluster { 12 | /** 13 | * @return the cluster identifier. 14 | */ 15 | UUID clusterId(); 16 | 17 | /** 18 | * @return the list of tokens. 19 | */ 20 | List tokens(); 21 | 22 | /** 23 | * @return the number similar log messages have been seen by this cluster. 24 | */ 25 | int sightings(); 26 | } 27 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 12 | id("com.gradle.develocity") version "4.3" 13 | } 14 | 15 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 16 | 17 | rootProject.name = "drain-java" 18 | 19 | include( 20 | "drain-java-bom", 21 | "drain-java-core", 22 | "drain-java-jackson", 23 | "tailer" 24 | ) 25 | 26 | develocity { 27 | buildScan { 28 | termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" 29 | // termsOfUseAgree is handled by .gradle/init.d/configure-develocity.init.gradle.kts 30 | } 31 | } -------------------------------------------------------------------------------- /drain-java-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | plugins { 12 | `java-library` 13 | `maven-publish` 14 | } 15 | 16 | description = "Drain Java Implementation" 17 | 18 | dependencies { 19 | implementation(libs.jsr305) 20 | 21 | testImplementation(libs.assertj.core) 22 | testImplementation(libs.junit.jupiter.api) 23 | testRuntimeOnly(libs.junit.jupiter.engine) 24 | testRuntimeOnly(libs.junit.platform.launcher) 25 | } 26 | 27 | tasks { 28 | processTestResources { 29 | dependsOn(rootProject.tasks.getByPath("unpackFile")) 30 | } 31 | 32 | test { 33 | useJUnitPlatform() 34 | reports { 35 | junitXml.required.set(true) 36 | html.required.set(true) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /drain-java-core/src/test/java/io/github/bric3/drain/utils/TestPaths.java: -------------------------------------------------------------------------------- 1 | package io.github.bric3.drain.utils; 2 | 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | 7 | public class TestPaths { 8 | public static Path get(String first, String... more) { 9 | Path subPath = Paths.get(first, more); 10 | Path resolved = Paths.get("build/resources/test").resolve(subPath); 11 | if (Files.exists(resolved)) { 12 | return resolved; 13 | } 14 | resolved = Paths.get("..").resolve(resolved); 15 | if (Files.exists(resolved)) { 16 | return resolved; 17 | } 18 | resolved = Paths.get("build").resolve(subPath); 19 | if (Files.exists(resolved)) { 20 | return resolved; 21 | } 22 | resolved = Paths.get("..").resolve(resolved); 23 | if (Files.exists(resolved)) { 24 | return resolved; 25 | } 26 | 27 | 28 | throw new IllegalStateException("Could not find " + subPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /drain-java-jackson/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | plugins { 12 | `java-library` 13 | `maven-publish` 14 | } 15 | 16 | description = "Drain Java Jackson Serialization" 17 | 18 | dependencies { 19 | implementation(projects.drainJavaCore) 20 | implementation(libs.jsr305) 21 | implementation(libs.bundles.jackson) 22 | 23 | testImplementation(libs.assertj.core) 24 | testImplementation(libs.junit.jupiter.api) 25 | testRuntimeOnly(libs.junit.jupiter.engine) 26 | testRuntimeOnly(libs.junit.platform.launcher) 27 | } 28 | 29 | tasks { 30 | processTestResources { 31 | dependsOn(rootProject.tasks.getByPath("unpackFile")) 32 | } 33 | 34 | test { 35 | useJUnitPlatform() 36 | reports { 37 | junitXml.required.set(true) 38 | html.required.set(true) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/internal/Tokenizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.internal; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.StringTokenizer; 15 | 16 | /** 17 | * Simple string tokenizer. 18 | */ 19 | public class Tokenizer { 20 | public static List tokenize(String content, String delimiters) { 21 | StringTokenizer stringTokenizer = new StringTokenizer(content, delimiters); 22 | 23 | List tokens = new ArrayList<>(stringTokenizer.countTokens()); 24 | while (stringTokenizer.hasMoreTokens()) { 25 | String trimmedToken = stringTokenizer.nextToken().trim(); 26 | if (!trimmedToken.isEmpty()) { 27 | tokens.add(trimmedToken); 28 | } 29 | } 30 | 31 | return tokens; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/core/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | /** 12 | * Drain log pattern miner. 13 | *

14 | * This code comes from a modified work of the LogPai team by IBM engineers, 15 | * but it has been improved to fit the Java platform. 16 | * 17 | *

18 | * Use the builder method {@link io.github.bric3.drain.core.Drain#drainBuilder()} to configure an 19 | * instance. 20 | * 21 | *

22 | * Example use: 23 | *


24 |  * var drain = Drain.drainBuilder()
25 |  *                  .additionalDelimiters("_")
26 |  *                  .depth(4)
27 |  *                  .build();
28 |  * Files.lines(
29 |  *     Paths.get("file.log"),
30 |  *     StandardCharsets.UTF_8
31 |  * ).forEach(drain::parseLogMessage);
32 |  *
33 |  * // do something with clusters
34 |  * drain.clusters();
35 |  * 
36 | */ 37 | package io.github.bric3.drain.core; -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/core/DrainState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.core; 11 | 12 | import java.util.List; 13 | 14 | class DrainState { 15 | final int depth; 16 | final double similarityThreshold; 17 | final int maxChildPerNode; 18 | final String delimiters; 19 | final List clusters; 20 | final Node prefixTree; 21 | 22 | DrainState(int depth, 23 | double similarityThreshold, 24 | int maxChildPerNode, 25 | String delimiters, 26 | List clusters, 27 | Node prefixTree) { 28 | this.depth = depth; 29 | this.similarityThreshold = similarityThreshold; 30 | this.maxChildPerNode = maxChildPerNode; 31 | this.delimiters = delimiters; 32 | this.clusters = clusters; 33 | this.prefixTree = prefixTree; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /drain-java-jackson/src/test/java/io/github/bric3/drain/utils/TestPaths.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.utils; 11 | 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | 16 | public class TestPaths { 17 | public static Path get(String first, String... more) { 18 | Path subPath = Paths.get(first, more); 19 | Path resolved = Paths.get("build/resources/test").resolve(subPath); 20 | if (Files.exists(resolved)) { 21 | return resolved; 22 | } 23 | resolved = Paths.get("..").resolve(resolved); 24 | if (Files.exists(resolved)) { 25 | return resolved; 26 | } 27 | resolved = Paths.get("build").resolve(subPath); 28 | if (Files.exists(resolved)) { 29 | return resolved; 30 | } 31 | resolved = Paths.get("..").resolve(resolved); 32 | if (Files.exists(resolved)) { 33 | return resolved; 34 | } 35 | 36 | 37 | throw new IllegalStateException("Could not find " + subPath); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | // Options doc https://docs.renovatebot.com/configuration-options/ 2 | // validate with 3 | // docker run --mount type=bind,source=$(pwd)/.github/renovate.json5,target=/usr/src/app/renovate.json5,readonly -it renovate/renovate renovate-config-validator 4 | { 5 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 6 | "extends": [ 7 | "config:base" 8 | ], 9 | "labels": [ 10 | "dependency-update" 11 | ], 12 | // automerge minor deps 13 | "packageRules": [ 14 | { 15 | "description": "Automatically merge minor and patch-level updates", 16 | "matchUpdateTypes": [ 17 | // where version is: major.minor.patch 18 | "minor", 19 | "patch" 20 | ], 21 | "automerge": true, 22 | // Do not create a PR to avoid PR-related email spam, if tests succeed merge directly 23 | // otherwise make a PR if tests fail 24 | "automergeType": "branch" 25 | } 26 | ], 27 | "vulnerabilityAlerts": { 28 | "description": "Automatically merge vulnerability fixes", 29 | "labels": [ 30 | "vulnerability-fix" 31 | ], 32 | "automerge": true, 33 | }, 34 | "dependencyDashboard": true, 35 | "prConcurrentLimit": 10, 36 | "prHourlyLimit": 5, 37 | // Schedule the bot to run before morning 38 | "timezone": "UTC", 39 | "schedule": [ 40 | "before 9am" 41 | // "before 9am on monday" // once a week before monday morning 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /tailer/src/main/java/io/github/bric3/tailer/tail/TailFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | package io.github.bric3.tailer.tail; 12 | 13 | import io.github.bric3.tailer.config.Config; 14 | import io.github.bric3.tailer.config.FromLine; 15 | import io.github.bric3.tailer.file.MappedFileLineReader; 16 | 17 | import java.nio.channels.Channels; 18 | import java.nio.channels.WritableByteChannel; 19 | import java.nio.file.Path; 20 | 21 | public class TailFile { 22 | 23 | private static final WritableByteChannel STDOUT = Channels.newChannel(System.out); 24 | // System.out = PrintStream(BufferedOutputStream(FileOutputStream)) 25 | // Or if POSIX : Channels.newChannel(new FileOutputStream("/dev/stdout")) to enable the system to perform zero copy? 26 | private final Config config; 27 | 28 | public TailFile(Config config) { 29 | this.config = config; 30 | } 31 | 32 | public void tail(Path path, FromLine fromLine, boolean follow) { 33 | assert path != null; 34 | assert fromLine != null; 35 | 36 | new MappedFileLineReader(config, new MappedFileLineReader.ChannelSink(STDOUT)) 37 | .tailRead(path, fromLine, follow); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tailer/src/main/java/io/github/bric3/tailer/config/Config.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.tailer.config; 11 | 12 | import java.io.PrintStream; 13 | import java.nio.charset.Charset; 14 | import java.nio.charset.StandardCharsets; 15 | 16 | public class Config { 17 | public final boolean verbose; 18 | 19 | public final PrintStream out; 20 | public final PrintStream err; 21 | 22 | public final Charset charset; 23 | 24 | public final DrainConfig drain; 25 | 26 | public Config(boolean verbose) { 27 | this(verbose, "", 0); 28 | } 29 | 30 | public Config(boolean verbose, String parseAfterStr, int parseAfterCol) { 31 | this.verbose = verbose; 32 | this.drain = new DrainConfig(parseAfterStr, parseAfterCol); 33 | this.out = System.out; 34 | this.err = System.err; 35 | this.charset = StandardCharsets.UTF_8; 36 | } 37 | 38 | public static class DrainConfig { 39 | public final String parseAfterStr; 40 | public final int parseAfterCol; 41 | 42 | DrainConfig(String parseAfterStr, int parseAfterCol) { 43 | this.parseAfterStr = parseAfterStr; 44 | this.parseAfterCol = parseAfterCol; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/internal/Stopwatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.internal; 11 | 12 | /** 13 | * A simple stopwatch to measure time. 14 | */ 15 | public class Stopwatch { 16 | /** 17 | * @return a new started stopwatch 18 | */ 19 | public static Stopwatch createStarted() { 20 | return new Stopwatch().start(); 21 | } 22 | 23 | private long startTime; 24 | 25 | /** 26 | * Create a new stopwatch. 27 | */ 28 | public Stopwatch() { 29 | startTime = System.currentTimeMillis(); 30 | } 31 | 32 | /** 33 | * Starts the stopwatch (records the current time as start time). 34 | * 35 | * @return this stopwatch 36 | */ 37 | public Stopwatch start() { 38 | startTime = System.currentTimeMillis(); 39 | return this; 40 | } 41 | 42 | /** 43 | * @return the elapsed time with the millisecond unit 44 | */ 45 | @Override 46 | public String toString() { 47 | return elapsed() + " ms"; 48 | } 49 | 50 | /** 51 | * @return the elapsed time in milliseconds 52 | */ 53 | private long elapsed() { 54 | return System.currentTimeMillis() - startTime; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /drain-java-jackson/src/test/resources/Unity.log: -------------------------------------------------------------------------------- 1 | 2020.10.06 12:02:11: Received ServerInfo packet:{"Type":2,"PlayersOnline":18,"PlayersPlaying":12,"PublicCustomGames":2,"PlayersSearching":2,"MaintenanceIn":-1} 2 | 2020.10.06 12:02:14: Received KeepAlive packet:{"Type":-1} 3 | 2020.10.06 12:02:14: Sending Packet:{"Type":-1} 4 | 2020.10.06 12:02:16: Received ServerInfo packet:{"Type":2,"PlayersOnline":18,"PlayersPlaying":12,"PublicCustomGames":2,"PlayersSearching":2,"MaintenanceIn":-1} 5 | 2020.10.06 12:02:21: Received ServerInfo packet:{"Type":2,"PlayersOnline":18,"PlayersPlaying":12,"PublicCustomGames":2,"PlayersSearching":2,"MaintenanceIn":-1} 6 | 2020.10.06 12:02:24: Received KeepAlive packet:{"Type":-1} 7 | 2020.10.06 12:02:24: Sending Packet:{"Type":-1} 8 | 2020.10.06 12:02:26: Received ServerInfo packet:{"Type":2,"PlayersOnline":17,"PlayersPlaying":12,"PublicCustomGames":2,"PlayersSearching":2,"MaintenanceIn":-1} 9 | 2020.10.06 12:02:31: Received ServerInfo packet:{"Type":2,"PlayersOnline":17,"PlayersPlaying":12,"PublicCustomGames":2,"PlayersSearching":2,"MaintenanceIn":-1} 10 | 2020.10.06 12:02:34: Received KeepAlive packet:{"Type":-1} 11 | 2020.10.06 12:02:34: Sending Packet:{"Type":-1} 12 | 2020.10.06 12:02:36: Received ServerInfo packet:{"Type":2,"PlayersOnline":17,"PlayersPlaying":12,"PublicCustomGames":2,"PlayersSearching":2,"MaintenanceIn":-1} 13 | 2020.10.06 12:02:41: Received ServerInfo packet:{"Type":2,"PlayersOnline":17,"PlayersPlaying":12,"PublicCustomGames":2,"PlayersSearching":2,"MaintenanceIn":-1} 14 | 2020.10.06 12:02:44: Received KeepAlive packet:{"Type":-1} 15 | -------------------------------------------------------------------------------- /tailer/src/main/java/io/github/bric3/tailer/config/FromLine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.tailer.config; 11 | 12 | import picocli.CommandLine; 13 | 14 | public class FromLine { 15 | public boolean fromStart = false; 16 | public long number = 10; 17 | 18 | public static FromLine fromStart(long lineNumber) { 19 | var startFromLine = new FromLine(); 20 | startFromLine.fromStart = true; 21 | startFromLine.number = lineNumber; 22 | return startFromLine; 23 | } 24 | 25 | public static FromLine fromEnd(long lineNumber) { 26 | var startFromLine = new FromLine(); 27 | startFromLine.fromStart = false; 28 | startFromLine.number = lineNumber; 29 | return startFromLine; 30 | } 31 | 32 | public static class StartFromLineConverter implements CommandLine.ITypeConverter { 33 | @Override 34 | public FromLine convert(String value) { 35 | var result = new FromLine(); 36 | if (value.charAt(0) == '+') { 37 | result.fromStart = true; 38 | value = value.substring(1); 39 | } 40 | result.number = Long.parseLong(value); 41 | if (result.number < 0) { 42 | throw new CommandLine.TypeConversionException( 43 | "invalid number of lines '" + value + "': must be 0 or positive number." 44 | ); 45 | } 46 | return result; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tailer/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | plugins { 12 | application 13 | alias(libs.plugins.shadow) 14 | } 15 | 16 | description = "Tail application based on drain-java" 17 | 18 | dependencies { 19 | implementation(projects.drainJavaCore) 20 | implementation(projects.drainJavaJackson) 21 | implementation(libs.jsr305) 22 | implementation(libs.picocli) 23 | 24 | annotationProcessor(libs.picocli.codegen) 25 | 26 | testImplementation(libs.assertj.core) 27 | testImplementation(libs.junit.jupiter.api) 28 | testRuntimeOnly(libs.junit.jupiter.engine) 29 | testRuntimeOnly(libs.junit.platform.launcher) 30 | } 31 | 32 | application { 33 | mainClass.set("io.github.bric3.tailer.TailerMain") 34 | } 35 | 36 | val JAVA_VERSION = 21 37 | 38 | java { 39 | toolchain { 40 | languageVersion.set(JavaLanguageVersion.of(JAVA_VERSION)) 41 | } 42 | } 43 | 44 | tasks { 45 | compileJava { 46 | options.compilerArgs.add("-Aproject=${project.group}/${project.name}") 47 | } 48 | 49 | withType(JavaCompile::class) { 50 | options.release.set(JAVA_VERSION) 51 | } 52 | 53 | test { 54 | useJUnitPlatform() 55 | reports { 56 | junitXml.required.set(true) 57 | html.required.set(true) 58 | } 59 | } 60 | 61 | jar { 62 | manifest { 63 | attributes( 64 | mapOf( 65 | "Main-Class" to application.mainClass.get() 66 | ) 67 | ) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # 2 | # drain-java 3 | # 4 | # Copyright (c) 2021, Today - Brice Dutheil 5 | # 6 | # This Source Code Form is subject to the terms of the Mozilla Public 7 | # License, v. 2.0. If a copy of the MPL was not distributed with this 8 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | # 10 | 11 | [versions] 12 | jackson = "2.20.1" 13 | jackson-annotations = "2.20" 14 | picocli = "4.7.7" 15 | guava = "30.1.1-jre" 16 | 17 | assertj = "3.27.6" 18 | junit = "5.14.1" 19 | junit-launcher = "1.14.1" 20 | 21 | [libraries] 22 | 23 | jsr305 = { module = "com.google.code.findbugs:jsr305", version = "3.0.2" } 24 | 25 | picocli = { module = "info.picocli:picocli", version.ref = "picocli" } 26 | picocli-codegen = { module = "info.picocli:picocli-codegen", version.ref = "picocli" } 27 | 28 | jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jackson" } 29 | jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson-annotations" } 30 | jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version = "2.20.1" } 31 | 32 | assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" } 33 | 34 | junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } 35 | junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } 36 | junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-launcher" } 37 | 38 | [bundles] 39 | jackson = ["jackson-core", "jackson-annotations", "jackson-databind"] 40 | 41 | [plugins] 42 | download = { id = "de.undercouch.download", version = "5.6.0" } 43 | shadow = { id = "com.gradleup.shadow", version = "9.3.0" } 44 | versions = { id = "com.github.ben-manes.versions", version = "0.53.0" } 45 | license = { id = "com.github.hierynomus.license", version = "0.16.1" } 46 | gradle-extensions = { id = "com.github.vlsi.gradle-extensions", version = "3.0.1" } 47 | nebula-release = { id = "nebula.release", version = "21.0.0" } -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/core/Node.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.core; 11 | 12 | import java.util.*; 13 | 14 | class Node { 15 | final int depth; 16 | final Object key; 17 | private final HashMap keyToChildNode; 18 | private final List clusters; 19 | 20 | public Node(Object key, int depth) { 21 | this.key = key; 22 | this.depth = depth; 23 | this.keyToChildNode = new HashMap<>(); 24 | this.clusters = new ArrayList<>(); 25 | } 26 | 27 | Node(Object key, int depth, HashMap keyToChildNode, List clusters) { 28 | this.depth = depth; 29 | this.key = key; 30 | this.keyToChildNode = keyToChildNode; 31 | this.clusters = clusters; 32 | } 33 | 34 | public Node get(Object key) { 35 | return keyToChildNode.get(key); 36 | } 37 | 38 | public Node getOrCreateChild(Object key) { 39 | return keyToChildNode.computeIfAbsent( 40 | key, 41 | k -> new Node(k, depth + 1) 42 | ); 43 | } 44 | 45 | InternalLogCluster clusterOf(int tokenCount) { 46 | return clusters.get(tokenCount); 47 | } 48 | 49 | List clusters() { 50 | return clusters; 51 | } 52 | 53 | void appendCluster(InternalLogCluster cluster) { 54 | clusters.add(cluster); 55 | } 56 | 57 | public boolean contains(String key) { 58 | return keyToChildNode.containsKey(key); 59 | } 60 | 61 | public int childrenCount() { 62 | return keyToChildNode.size(); 63 | } 64 | 65 | Map childMappings() { 66 | return Collections.unmodifiableMap(new HashMap<>(keyToChildNode)); 67 | } 68 | 69 | @Override 70 | public boolean equals(Object o) { 71 | if (this == o) return true; 72 | if (o == null || getClass() != o.getClass()) return false; 73 | Node node = (Node) o; 74 | return depth == node.depth && Objects.equals(key, node.key) && Objects.equals(keyToChildNode, node.keyToChildNode) && Objects.equals(clusters, node.clusters); 75 | } 76 | 77 | @Override 78 | public int hashCode() { 79 | return Objects.hash(depth, key, keyToChildNode, clusters); 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return "Node{" + 85 | "depth=" + depth + 86 | ", key=" + key + 87 | ", keyToChildNode=" + keyToChildNode + 88 | ", clusters=" + clusters + 89 | '}'; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tailer/src/main/java/io/github/bric3/tailer/drain/DrainFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.tailer.drain; 11 | 12 | import io.github.bric3.drain.core.Drain; 13 | import io.github.bric3.drain.core.LogCluster; 14 | import io.github.bric3.drain.internal.Stopwatch; 15 | import io.github.bric3.tailer.config.Config; 16 | import io.github.bric3.tailer.config.FromLine; 17 | import io.github.bric3.tailer.file.MappedFileLineReader; 18 | 19 | import java.nio.file.Path; 20 | import java.util.Comparator; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import java.util.function.Consumer; 23 | 24 | public class DrainFile { 25 | 26 | private final Config config; 27 | 28 | public DrainFile(Config config) { 29 | this.config = config; 30 | } 31 | 32 | public void drain(Path file, FromLine fromLine, boolean follow) { 33 | assert file != null; 34 | assert fromLine != null; 35 | 36 | var drain = Drain.drainBuilder() 37 | .additionalDelimiters("_") 38 | .depth(4) 39 | .build(); 40 | 41 | var lineCounter = new AtomicInteger(); 42 | var stopwatch = Stopwatch.createStarted(); 43 | Consumer drainConsumer = l -> { 44 | lineCounter.incrementAndGet(); 45 | 46 | String content = preProcess(l); 47 | drain.parseLogMessage(content); 48 | if (config.verbose && lineCounter.get() % 10000 == 0) { 49 | config.out.printf("%4d clusters so far%n", drain.clusters().size()); 50 | } 51 | }; 52 | 53 | new MappedFileLineReader(config, new MappedFileLineReader.LineConsumer(drainConsumer, config.charset)) 54 | .tailRead(file, fromLine, follow); 55 | 56 | if (config.verbose) { 57 | config.out.printf("---- Done processing file. Total of %d lines, done in %s, %d clusters%n", 58 | lineCounter.get(), 59 | stopwatch, 60 | drain.clusters().size()); 61 | } 62 | drain.clusters() 63 | .stream() 64 | .sorted(Comparator.comparing(LogCluster::sightings).reversed()) 65 | .forEach(System.out::println); 66 | 67 | } 68 | 69 | private String preProcess(String line) { 70 | var parseAfterCol = config.drain.parseAfterCol; 71 | if (parseAfterCol > 0) { 72 | return line.substring(parseAfterCol); 73 | } 74 | 75 | var parseAfterStr = config.drain.parseAfterStr; 76 | if (!parseAfterStr.isEmpty()) { 77 | return line.substring(line.indexOf(parseAfterStr) + parseAfterStr.length()); 78 | } 79 | return line; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/vscode,gradle,intellij,java,eclipse,vim 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=vscode,gradle,intellij,java,eclipse,vim 4 | 5 | ### Eclipse ### 6 | .metadata 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | .recommenders 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # PyDev specific (Python IDE for Eclipse) 25 | *.pydevproject 26 | 27 | # CDT-specific (C/C++ Development Tooling) 28 | .cproject 29 | 30 | # CDT- autotools 31 | .autotools 32 | 33 | # Java annotation processor (APT) 34 | .factorypath 35 | 36 | # PDT-specific (PHP Development Tools) 37 | .buildpath 38 | 39 | # sbteclipse plugin 40 | .target 41 | 42 | # Tern plugin 43 | .tern-project 44 | 45 | # TeXlipse plugin 46 | .texlipse 47 | 48 | # STS (Spring Tool Suite) 49 | .springBeans 50 | 51 | # Code Recommenders 52 | .recommenders/ 53 | 54 | # Annotation Processing 55 | .apt_generated/ 56 | .apt_generated_test/ 57 | 58 | # Scala IDE specific (Scala & Java development for Eclipse) 59 | .cache-main 60 | .scala_dependencies 61 | .worksheet 62 | 63 | # Uncomment this line if you wish to ignore the project description file. 64 | # Typically, this file would be tracked if it contains build/dependency configurations: 65 | #.project 66 | 67 | ### Eclipse Patch ### 68 | # Spring Boot Tooling 69 | .sts4-cache/ 70 | 71 | ### Intellij ### 72 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 73 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 74 | 75 | # User-specific stuff 76 | .idea 77 | 78 | *.iml 79 | *.ipr 80 | 81 | # CMake 82 | cmake-build-*/ 83 | 84 | # File-based project format 85 | *.iws 86 | 87 | # IntelliJ 88 | out/ 89 | 90 | # Crashlytics plugin (for Android Studio and IntelliJ) 91 | com_crashlytics_export_strings.xml 92 | crashlytics.properties 93 | crashlytics-build.properties 94 | fabric.properties 95 | 96 | 97 | ### Java ### 98 | # Compiled class file 99 | *.class 100 | 101 | # Log file 102 | *.log 103 | 104 | # BlueJ files 105 | *.ctxt 106 | 107 | # Mobile Tools for Java (J2ME) 108 | .mtj.tmp/ 109 | 110 | # Package Files # 111 | *.jar 112 | *.war 113 | *.nar 114 | *.ear 115 | *.zip 116 | *.tar.gz 117 | *.rar 118 | 119 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 120 | hs_err_pid* 121 | 122 | ### Vim ### 123 | # Swap 124 | [._]*.s[a-v][a-z] 125 | !*.svg # comment out if you don't need vector files 126 | [._]*.sw[a-p] 127 | [._]s[a-rt-v][a-z] 128 | [._]ss[a-gi-z] 129 | [._]sw[a-p] 130 | 131 | # Session 132 | Session.vim 133 | Sessionx.vim 134 | 135 | # Temporary 136 | .netrwhist 137 | *~ 138 | # Auto-generated tag files 139 | tags 140 | # Persistent undo 141 | [._]*.un~ 142 | 143 | ### vscode ### 144 | .vscode/* 145 | !.vscode/settings.json 146 | !.vscode/tasks.json 147 | !.vscode/launch.json 148 | !.vscode/extensions.json 149 | *.code-workspace 150 | 151 | ### Gradle ### 152 | .gradle 153 | build/ 154 | 155 | # Ignore Gradle GUI config 156 | gradle-app.setting 157 | 158 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 159 | !gradle-wrapper.jar 160 | 161 | # Cache of project 162 | .gradletasknamecache 163 | 164 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 165 | # gradle/wrapper/gradle-wrapper.properties 166 | 167 | ### Gradle Patch ### 168 | **/build/ 169 | 170 | # End of https://www.toptal.com/developers/gitignore/api/vscode,gradle,intellij,java,eclipse,vim 171 | 172 | -------------------------------------------------------------------------------- /tailer/src/main/java/io/github/bric3/tailer/TailerMain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.tailer; 11 | 12 | import io.github.bric3.tailer.config.Config; 13 | import io.github.bric3.tailer.config.FromLine; 14 | import io.github.bric3.tailer.config.FromLine.StartFromLineConverter; 15 | import io.github.bric3.tailer.drain.DrainFile; 16 | import io.github.bric3.tailer.tail.TailFile; 17 | import picocli.CommandLine; 18 | import picocli.CommandLine.Command; 19 | import picocli.CommandLine.Option; 20 | import picocli.CommandLine.Parameters; 21 | 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | 25 | 26 | @Command( 27 | name = "tail", 28 | header = {"", "@|red tail - drain|@"}, 29 | description = "...", 30 | mixinStandardHelpOptions = true, 31 | version = { 32 | "Versioned Command 1.0", 33 | "Picocli " + picocli.CommandLine.VERSION, 34 | "JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})", 35 | "OS: ${os.name} ${os.version} ${os.arch}"} 36 | ) 37 | public class TailerMain implements Runnable { 38 | public static final int ERR_NO_FILEPATH = 1; 39 | public static final int ERR_IO_TAILING_FILE = 2; 40 | public static final int ERR_IO_WATCHING_FILE = 3; 41 | 42 | public static void main(String[] args) { 43 | System.exit(new CommandLine(new TailerMain()).execute(args)); 44 | } 45 | 46 | @Parameters(description = "log file", 47 | paramLabel = "FILE") 48 | Path file; 49 | 50 | @Option(names = {"-d", "--drain"}, 51 | description = "use DRAIN to extract log patterns") 52 | boolean drain; 53 | 54 | @Option(names = {"--parse-after-str"}, 55 | description = "when using DRAIN remove the left part of a log line up" + 56 | " to after the FIXED_STRING_SEPARATOR", 57 | paramLabel = "FIXED_STRING_SEPARATOR") 58 | String parseAfterStr = ""; 59 | 60 | @Option(names = {"--parser-after-col"}, 61 | description = "when using DRAIN remove the left part of a log line up to COLUMN", 62 | paramLabel = "COLUMN") 63 | int parseAfterCol = 0; 64 | 65 | @Option(names = {"-f", "--follow"}, 66 | description = "output appended data as the file grows") 67 | boolean follow; 68 | 69 | @Option(names = {"-n", "--lines"}, 70 | description = "output the last NUM lines, instead of the last 10;" + 71 | " or use -n 0 to output starting from beginning", 72 | converter = StartFromLineConverter.class, 73 | paramLabel = "[+]NUM", 74 | defaultValue = "10") 75 | FromLine fromLine; 76 | 77 | @Option(names = {"--verbose"}, 78 | description = "Verbose output, mostly for DRAIN or errors") 79 | boolean verbose; 80 | 81 | @Option(names = {"-e"}, 82 | description = "Experimental code", 83 | hidden = true) 84 | boolean experimental; 85 | 86 | @Override 87 | public void run() { 88 | if (!Files.isRegularFile(file)) { 89 | System.err.println("Expects a file path to tail!"); 90 | System.exit(ERR_NO_FILEPATH); 91 | } 92 | 93 | var config = new Config(verbose, parseAfterStr, parseAfterCol); 94 | 95 | if (drain) { 96 | new DrainFile(config).drain(file, fromLine, follow); 97 | } else { 98 | new TailFile(config).tail(file, fromLine, follow); 99 | } 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/core/InternalLogCluster.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.core; 11 | 12 | 13 | import javax.annotation.Nonnull; 14 | import java.util.*; 15 | 16 | /** 17 | * Log cluster. 18 | *

19 | * It represents a tokenized logs where similar tokens are 20 | * replaced by the marker {@link Drain#PARAM_MARKER}. 21 | * 22 | * @author brice.dutheil@gmail.com 23 | * @modifiedBy david.ohana@ibm.com, moshikh@il.ibm.com 24 | * @originalAuthor LogPAI team 25 | * @license MIT 26 | */ 27 | class InternalLogCluster implements LogCluster { 28 | private final UUID clusterId; 29 | private int sightings = 1; 30 | private List logTemplateTokens; 31 | 32 | InternalLogCluster(@Nonnull List logTemplateTokens) { 33 | this.clusterId = UUID.randomUUID(); 34 | this.logTemplateTokens = logTemplateTokens; 35 | } 36 | 37 | // for deserialization 38 | private InternalLogCluster(UUID clusterId, 39 | int sightings, 40 | List logTemplateTokens) { 41 | this.clusterId = clusterId; 42 | this.sightings = sightings; 43 | this.logTemplateTokens = logTemplateTokens; 44 | } 45 | 46 | /** 47 | * The cluster identifier 48 | * 49 | * @return cluster identifier. 50 | */ 51 | @Override 52 | public UUID clusterId() { 53 | return clusterId; 54 | } 55 | 56 | List internalTokens() { 57 | return logTemplateTokens; 58 | } 59 | 60 | /** 61 | * List of the tokens for this LogCLuster 62 | * 63 | * @return Tokens of this cluster 64 | */ 65 | @Override 66 | public List tokens() { 67 | return Collections.unmodifiableList(logTemplateTokens); 68 | } 69 | 70 | void updateTokens(List newTemplateTokens) { 71 | logTemplateTokens = newTemplateTokens; 72 | } 73 | 74 | void newSighting(List contentTokens) { 75 | List newTemplateTokens = updateTemplate(contentTokens, logTemplateTokens); 76 | if (!newTemplateTokens.equals(logTemplateTokens)) { 77 | updateTokens(newTemplateTokens); 78 | } 79 | 80 | sightings++; 81 | } 82 | 83 | @Nonnull 84 | List updateTemplate(@Nonnull List contentTokens, 85 | @Nonnull List templateTokens) { 86 | assert contentTokens.size() == templateTokens.size(); 87 | List newTemplate = new ArrayList(contentTokens.size()); 88 | 89 | for (int i = 0, tokensSize = contentTokens.size(); i < tokensSize; i++) { 90 | String contentToken = contentTokens.get(i); 91 | String templateToken = templateTokens.get(i); 92 | // TODO change to replace value at index 93 | if (contentToken.equals(templateToken)) { 94 | newTemplate.add(contentToken); 95 | } else { 96 | newTemplate.add(Drain.PARAM_MARKER); // replace contentToken by a marker 97 | } 98 | 99 | } 100 | 101 | return newTemplate; 102 | } 103 | 104 | /** 105 | * The number of times a log with this pattern has been seen. 106 | * 107 | * @return sightings of similar logs. 108 | */ 109 | @Override 110 | public int sightings() { 111 | return sightings; 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return String.format("%s (size %d): %s", 117 | clusterId, 118 | sightings, 119 | String.join(" ", logTemplateTokens)); 120 | } 121 | 122 | @Override 123 | public boolean equals(Object o) { 124 | if (this == o) return true; 125 | if (o == null || getClass() != o.getClass()) return false; 126 | InternalLogCluster that = (InternalLogCluster) o; 127 | return sightings == that.sightings && clusterId.equals(that.clusterId) && logTemplateTokens.equals(that.logTemplateTokens); 128 | } 129 | 130 | @Override 131 | public int hashCode() { 132 | return Objects.hash(clusterId, sightings, logTemplateTokens); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Master Build 2 | on: 3 | workflow_dispatch: 4 | 5 | push: 6 | branches: [ master ] 7 | tags: 8 | - "v[0-9]+.[0-9]+.[0-9]+" 9 | 10 | pull_request: 11 | branches: [ master ] 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | gradleValidation: 19 | name: Gradle Wrapper 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Fetch Sources 23 | uses: actions/checkout@v6 24 | 25 | - name: Gradle Wrapper Validation 26 | uses: gradle/actions/wrapper-validation@v5 27 | with: 28 | min-wrapper-count: 0 29 | 30 | build: 31 | name: Build ${{ matrix.tag }} 32 | runs-on: ${{ matrix.runner }} 33 | continue-on-error: ${{ matrix.ignore-errors }} 34 | strategy: 35 | matrix: 36 | os: ['linux'] 37 | arch: ['amd64'] 38 | runner: ['ubuntu-latest'] 39 | tag: ['linux-amd64'] 40 | ignore-errors: [false] 41 | include: 42 | - os: macos 43 | runner: macos-latest 44 | arch: amd64 45 | tag: darwin-amd64 46 | ignore-errors: true 47 | - os: macos 48 | runner: macos-14 49 | arch: aarch64 50 | tag: darwin-aarch64 51 | ignore-errors: true 52 | - os: windows 53 | runner: windows-latest 54 | arch: amd64 55 | tag: windows-amd64 56 | ignore-errors: true 57 | 58 | steps: 59 | - uses: actions/checkout@v6 60 | - name: Set up Git 61 | run: | 62 | git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" 63 | git config --global user.name "$GITHUB_ACTOR" 64 | - name: Setup JVM 65 | uses: actions/setup-java@v5 66 | with: 67 | distribution : zulu 68 | java-version: | 69 | 21 70 | 17 71 | - name : Setup Gradle 72 | uses : gradle/actions/setup-gradle@v5 73 | - run: ./gradlew build 74 | # - name: list build directory 75 | # if: ${{ always() }} 76 | # run: | 77 | # ls build 78 | 79 | - name: Upload Test Report 80 | uses: actions/upload-artifact@v6 81 | if: always() # always run even if the previous step fails 82 | with: 83 | name: junit-test-results-${{ matrix.tag }} 84 | path: '**/build/test-results/test/TEST-*.xml' 85 | retention-days: 1 86 | 87 | # This job will update the PR with the JUnit report 88 | # In order to be able to make the most of it this job in particular has 89 | # augmented permissions. 90 | junit-report: 91 | name: JUnit Report ${{ matrix.tag }} 92 | runs-on: ubuntu-latest 93 | strategy: 94 | matrix: 95 | tag: ['linux-amd64', 'darwin-amd64', 'darwin-aarch64', 'windows-amd64'] 96 | if: | 97 | success() || failure() 98 | needs: [ build ] 99 | permissions: 100 | checks: write # for mikepenz/action-junit-report 101 | 102 | steps: 103 | - name: Download Test Report 104 | uses: actions/download-artifact@v7 105 | with: 106 | name: junit-test-results-${{ matrix.tag }} 107 | - name: Publish Test Report 108 | uses: mikepenz/action-junit-report@v6 109 | with: 110 | check_name: Test Report - ${{ matrix.tag }} 111 | commit: ${{github.event.workflow_run.head_sha}} 112 | report_paths: '**/build/test-results/test/TEST-*.xml' 113 | 114 | publish: 115 | name: Publish snapshots 116 | runs-on: ubuntu-latest 117 | needs: build 118 | if: success() && github.event_name != 'pull_request' && github.ref == 'refs/heads/master' 119 | steps: 120 | - uses: actions/checkout@v6 121 | with: 122 | fetch-depth: 0 123 | - name: Setup JVM 124 | uses: actions/setup-java@v5 125 | with: 126 | distribution : zulu 127 | java-version: | 128 | 21 129 | 17 130 | - name : Setup Gradle 131 | uses : gradle/actions/setup-gradle@v5 132 | - run: ./gradlew snapshot -Ppublish.central=true 133 | env: 134 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} 135 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} 136 | ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.ORG_GRADLE_PROJECT_OSSRHUSERNAME }} 137 | ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.ORG_GRADLE_PROJECT_OSSRHPASSWORD }} 138 | -------------------------------------------------------------------------------- /drain-java-jackson/src/test/java/io/github/bric3/drain/core/DrainJsonSerializationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.core; 11 | 12 | import io.github.bric3.drain.utils.TestPaths; 13 | import org.junit.jupiter.api.Test; 14 | 15 | import java.io.IOException; 16 | import java.io.StringReader; 17 | import java.io.StringWriter; 18 | import java.nio.file.Files; 19 | import java.util.function.Function; 20 | import java.util.stream.Stream; 21 | 22 | import static java.nio.charset.StandardCharsets.UTF_8; 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | class DrainJsonSerializationTest { 26 | 27 | @Test 28 | void serde_should_result_in_same_state() throws IOException { 29 | Drain drain = initDrain("SSH.log", l -> l.substring(l.indexOf("]: ") + 3)); 30 | assertThat(drain.clusters()).hasSize(51); 31 | 32 | final Drain drainReloaded = serde(drain); 33 | 34 | assertThat(drainReloaded.depth).isEqualTo(drain.depth); 35 | assertThat(drainReloaded.similarityThreshold).isEqualTo(drain.similarityThreshold); 36 | assertThat(drainReloaded.delimiters).isEqualTo(drain.delimiters); 37 | assertThat(drainReloaded.maxChildPerNode).isEqualTo(drain.maxChildPerNode); 38 | assertThat(drainReloaded.clusters()).isEqualTo(drain.clusters()); 39 | assertThat(drainReloaded.prefixTree()).isEqualTo(drain.prefixTree()); 40 | } 41 | 42 | @Test 43 | void serde2_should_result_in_same_state() { 44 | Drain drain = Drain.drainBuilder() 45 | .additionalDelimiters("_") 46 | .depth(4) 47 | .build(); 48 | 49 | Stream.of("sent 550 bytes", 50 | "sent 110 bytes", 51 | "sent 800 bytes", 52 | "received 1000 bytes", 53 | "received 250 bytes", 54 | "sent 200 bytes" 55 | ).forEach(drain::parseLogMessage); 56 | assertThat(drain.clusters()).hasSize(2); 57 | 58 | 59 | final Drain drainReloaded = serde(drain); 60 | 61 | assertThat(drainReloaded.depth).isEqualTo(drain.depth); 62 | assertThat(drainReloaded.similarityThreshold).isEqualTo(drain.similarityThreshold); 63 | assertThat(drainReloaded.delimiters).isEqualTo(drain.delimiters); 64 | assertThat(drainReloaded.maxChildPerNode).isEqualTo(drain.maxChildPerNode); 65 | assertThat(drainReloaded.clusters()).isEqualTo(drain.clusters()); 66 | assertThat(drainReloaded.prefixTree()).isEqualTo(drain.prefixTree()); 67 | } 68 | 69 | @Test 70 | void drain_with_reloaded_state_can_resume_log_mining() throws IOException { 71 | Drain drain = initDrain("Unity.log", l -> l.substring(l.indexOf(": ") + 2)); 72 | 73 | Drain drainReloaded = serde(drain); 74 | assertThat(drainReloaded.clusters()).isEqualTo(drain.clusters()); 75 | 76 | // Adding log with same patterns should not create new clusters 77 | Stream.of( 78 | "Sending Packet:{\"Type\":-1}", 79 | "Received ServerInfo packet:{\"Type\":2,\"PlayersOnline\":17,\"PlayersPlaying\":12,\"PublicCustomGames\":2,\"PlayersSearching\":2,\"MaintenanceIn\":-1}", 80 | "Received ServerInfo packet:{\"Type\":2,\"PlayersOnline\":17,\"PlayersPlaying\":12,\"PublicCustomGames\":2,\"PlayersSearching\":2,\"MaintenanceIn\":-1}", 81 | "Received KeepAlive packet:{\"Type\":-1}" 82 | ).forEach(drainReloaded::parseLogMessage); 83 | assertThat(drainReloaded.clusters()).hasSize(drain.clusters().size()); 84 | 85 | // Adding a log with a different pattern should create a new cluster 86 | drainReloaded.parseLogMessage("Resolution changed: 2590x1600 windowed, Metal RecreateSurface[0x7ff491c541a0]: surface size 2588x1600"); 87 | assertThat(drainReloaded.clusters()).hasSize(drain.clusters().size() + 1); 88 | } 89 | 90 | private Drain initDrain(String logFile, Function normalizingFunction) throws IOException { 91 | Drain drain = Drain.drainBuilder() 92 | .additionalDelimiters("_") 93 | .depth(4) 94 | .build(); 95 | 96 | try (Stream lines = Files.lines(TestPaths.get(logFile), UTF_8)) { 97 | lines.map(normalizingFunction) 98 | .forEach(drain::parseLogMessage); 99 | } 100 | return drain; 101 | } 102 | 103 | private Drain serde(Drain drain) { 104 | final DrainJsonSerialization serde = new DrainJsonSerialization(); 105 | final StringWriter writer = new StringWriter(); 106 | serde.saveState(drain, writer); 107 | 108 | return serde.loadState(new StringReader(writer.toString())); 109 | } 110 | } -------------------------------------------------------------------------------- /tailer/src/test/java/io/github/bric3/tailer/file/MappedFileLineReaderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.tailer.file; 11 | 12 | import io.github.bric3.tailer.config.Config; 13 | import io.github.bric3.tailer.config.FromLine; 14 | import org.junit.jupiter.api.AfterEach; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.io.TempDir; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.IOException; 20 | import java.io.UncheckedIOException; 21 | import java.nio.ByteBuffer; 22 | import java.nio.CharBuffer; 23 | import java.nio.channels.Channels; 24 | import java.nio.channels.FileChannel; 25 | import java.nio.channels.WritableByteChannel; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.nio.file.Paths; 29 | import java.nio.file.StandardOpenOption; 30 | import java.util.Random; 31 | import java.util.concurrent.Executors; 32 | import java.util.concurrent.ScheduledExecutorService; 33 | 34 | import static java.nio.charset.StandardCharsets.UTF_8; 35 | import static java.nio.file.StandardOpenOption.READ; 36 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 37 | import static java.util.concurrent.TimeUnit.SECONDS; 38 | import static org.assertj.core.api.Assertions.assertThat; 39 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 40 | 41 | class MappedFileLineReaderTest { 42 | private final Path resourceDirectory = Paths.get("src", "test", "resources"); 43 | 44 | private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); 45 | 46 | @Test 47 | void should_watch_with_line_reader(@TempDir Path tmpDir) throws IOException { 48 | var path = Files.createTempFile(tmpDir, "test", "log"); 49 | 50 | var lineAppender = new LineAppender(path); 51 | var config = new Config(true); 52 | 53 | try (var r = new MappedFileLineReader(config, new MappedFileLineReader.LineConsumer(line -> {}, UTF_8))) { 54 | var future = scheduler.scheduleAtFixedRate(lineAppender, 0, 400, MILLISECONDS); 55 | scheduler.schedule(() -> future.cancel(false), 4, SECONDS); 56 | scheduler.schedule(r::close, 10, SECONDS); 57 | 58 | r.tailRead(path, FromLine.fromStart(0), true); 59 | 60 | assertThat(r.totalReadBytes()).isEqualTo(lineAppender.writtenBytes); 61 | } 62 | } 63 | 64 | @Test 65 | void should_watch_with_channel_sink(@TempDir Path tmpDir) throws IOException { 66 | var path = Files.createTempFile(tmpDir, "test", "log"); 67 | var lineAppender = new LineAppender(path); 68 | var config = new Config(true); 69 | 70 | var out = new ByteArrayOutputStream(); 71 | try (var r = new MappedFileLineReader(config, new MappedFileLineReader.ChannelSink(Channels.newChannel(out)))) { 72 | var future = scheduler.scheduleAtFixedRate(lineAppender, 0, 400, MILLISECONDS); 73 | scheduler.schedule(() -> future.cancel(false), 4, SECONDS); 74 | scheduler.schedule(r::close, 10, SECONDS); 75 | 76 | r.tailRead(path, FromLine.fromStart(0), true); 77 | 78 | assertThat(r.totalReadBytes()).isEqualTo(lineAppender.writtenBytes); 79 | assertThat(path.toFile()).hasBinaryContent(out.toByteArray()); 80 | } 81 | } 82 | 83 | @AfterEach 84 | void tearDown() { 85 | scheduler.shutdown(); 86 | } 87 | 88 | @Test 89 | void find_start_position_given_last_lines() throws IOException { 90 | try (var channel = FileChannel.open(resourceDirectory.resolve("3-lines.txt"), READ)) { 91 | var r = new MappedFileLineReader(new Config(true), MappedFileLineReader.IOReadAction.NO_OP); 92 | 93 | assertThat(r.findTailStartPosition(channel, FromLine.fromEnd(10))).isEqualTo(0); 94 | assertThat(r.findTailStartPosition(channel, FromLine.fromEnd(2))).isEqualTo(42); 95 | assertThat(r.findTailStartPosition(channel, FromLine.fromEnd(0))).isEqualTo(183); 96 | assertThat(r.findTailStartPosition(channel, FromLine.fromStart(0))).isEqualTo(0); 97 | assertThat(r.findTailStartPosition(channel, FromLine.fromStart(2))).isEqualTo(181); 98 | assertThat(r.findTailStartPosition(channel, FromLine.fromStart(10))).isEqualTo(183); 99 | } 100 | } 101 | 102 | @Test 103 | void can_read_from_position() throws IOException { 104 | try (var channel = FileChannel.open(resourceDirectory.resolve("3-lines.txt"), READ)) { 105 | var sink = new MappedFileLineReader.ChannelSink(TestSink.nullSink()); 106 | assertThat(sink.apply(channel, 0)).isEqualTo(183); 107 | assertThat(sink.apply(channel, 41)).isEqualTo(142); 108 | } 109 | } 110 | 111 | @Test 112 | void cannot_read_from_negative_position() throws IOException { 113 | try (var channel = FileChannel.open(resourceDirectory.resolve("3-lines.txt"), READ)) { 114 | var sink = new MappedFileLineReader.ChannelSink(TestSink.nullSink()); 115 | assertThatExceptionOfType(AssertionError.class).isThrownBy( 116 | () -> sink.apply(channel, -1) 117 | ); 118 | } 119 | } 120 | 121 | static class LineAppender implements Runnable { 122 | Path path; 123 | int lineCounter = 0; 124 | private int writtenBytes = 0; 125 | 126 | public LineAppender(Path path) { 127 | this.path = path; 128 | } 129 | 130 | @Override 131 | public void run() { 132 | var howManyLines = new Random().nextInt(10); 133 | 134 | var sb = new StringBuilder(); 135 | for (int i = 0; i <= howManyLines; i++) { 136 | sb.append("line ").append(lineCounter++).append("\n"); 137 | } 138 | 139 | 140 | try { 141 | var encoded = UTF_8.encode(CharBuffer.wrap(sb)); 142 | writtenBytes += encoded.capacity(); 143 | Files.write(path, encoded.array(), StandardOpenOption.APPEND); 144 | } catch (IOException e) { 145 | throw new UncheckedIOException(e); 146 | } 147 | } 148 | } 149 | 150 | private static class TestSink implements WritableByteChannel { 151 | 152 | int writenBytes = 0; 153 | 154 | static TestSink nullSink() { 155 | return new TestSink(); 156 | } 157 | 158 | @Override 159 | public boolean isOpen() { 160 | return true; 161 | } 162 | 163 | @Override 164 | public void close() { 165 | 166 | } 167 | 168 | @Override 169 | public int write(ByteBuffer src) { 170 | var remaining = src.remaining(); 171 | writenBytes += remaining; 172 | return remaining; 173 | } 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = drain java 2 | 3 | image:https://github.com/bric3/drain-java/actions/workflows/gradle.yml/badge.svg[Java CI with Gradle,link=https://github.com/bric3/drain-java/actions/workflows/gradle.yml] 4 | 5 | == Introduction 6 | 7 | drain-java is a continuous _log template miner_, for each log message it extracts 8 | tokens and group them into _clusters of tokens_. As new log messages are added, 9 | drain-java will identify similar token and update the cluster with the new template, 10 | or simply create a new token cluster. Each time a cluster is matched a counter is 11 | incremented. 12 | 13 | These clusters are stored in prefix tree, which is somewhat similar to a trie, but 14 | here the tree as a fixed depth in order to avoid long tree traversal. 15 | In avoiding deep trees this also helps to keep it balance. 16 | 17 | == Usage 18 | 19 | First, https://foojay.io/almanac/jdk-11/[Java 11] is required to run drain-java. 20 | 21 | === As a dependency 22 | 23 | You can consume drain-java as a dependency in your project `io.github.bric3.drain:drain-java-core`, 24 | currently only https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/bric3/drain/[snapshots] 25 | are available by adding this repository. 26 | 27 | [source, kotlin] 28 | ---- 29 | repositories { 30 | maven { 31 | url("https://oss.sonatype.org/content/repositories/snapshots/") 32 | } 33 | } 34 | ---- 35 | 36 | === From command line 37 | 38 | Since this tool is not yet released the tool needs to be built locally. 39 | Also, the built jar is not yet super user-friendly. Since it's not a finished 40 | product, anything could change. 41 | 42 | .Example usage 43 | [source, shell] 44 | ---- 45 | $ ./gradlew build 46 | $ java -jar tailer/build/libs/tailer-0.1.0-SNAPSHOT-all.jar -h 47 | 48 | tail - drain 49 | Usage: tail [-dfhV] [--verbose] [-n=NUM] 50 | [--parse-after-str=FIXED_STRING_SEPARATOR] 51 | [--parser-after-col=COLUMN] FILE 52 | ... 53 | FILE log file 54 | -d, --drain use DRAIN to extract log patterns 55 | -f, --follow output appended data as the file grows 56 | -h, --help Show this help message and exit. 57 | -n, --lines=NUM output the last NUM lines, instead of the last 10; or use 58 | -n 0 to output starting from beginning 59 | --parse-after-str=FIXED_STRING_SEPARATOR 60 | when using DRAIN remove the left part of a log line up to 61 | after the FIXED_STRING_SEPARATOR 62 | --parser-after-col=COLUMN 63 | when using DRAIN remove the left part of a log line up to 64 | COLUMN 65 | -V, --version Print version information and exit. 66 | --verbose Verbose output, mostly for DRAIN or errors 67 | $ java -jar tailer/build/libs/tailer-0.1.0-SNAPSHOT-all.jar --version 68 | Versioned Command 1.0 69 | Picocli 4.6.3 70 | JVM: 19 (Amazon.com Inc. OpenJDK 64-Bit Server VM 19+36-FR) 71 | OS: Mac OS X 12.6 x86_64 72 | ---- 73 | 74 | By default, the tool act similarly to `tail`, and it will output the file to the stdout. 75 | The tool can _follow_ a file if the `--follow` option is passed. 76 | However, when run with the `--drain` this tool will classify log lines using DRAIN, and will 77 | output identified clusters. 78 | Note that this tool doesn't handle multiline log messages (like logs that contains a stacktrace). 79 | 80 | On the SSH log data set we can use it this way. 81 | 82 | [source, shell] 83 | ---- 84 | $ java -jar build/libs/drain-java-1.0-SNAPSHOT-all.jar \ 85 | -d \ <1> 86 | -n 0 \ <2> 87 | --parse-after-str "]: " <3> 88 | build/resources/test/SSH.log <4> 89 | ---- 90 | <1> Identify patterns in the log 91 | <2> Starts from the beginning of the file (otherwise it starts from the last 10 lines) 92 | <3> Remove the left part of log line (`Dec 10 06:55:46 LabSZ sshd[24200]: `), ie effectively 93 | ignoring some variable elements like the time. 94 | <4> The log file 95 | 96 | .log pattern clusters and their occurences 97 | [source] 98 | -------- 99 | ---- Done processing file. Total of 655147 lines, done in 1.588 s, 51 clusters <1> 100 | 0010 (size 140768): Failed password for <*> from <*> port <*> ssh2 <2> 101 | 0009 (size 140701): pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*> <*> 102 | 0007 (size 68958): Connection closed by <*> [preauth] 103 | 0008 (size 46642): Received disconnect from <*> 11: <*> <*> <*> 104 | 0014 (size 37963): PAM service(sshd) ignoring max retries; <*> > 3 105 | 0012 (size 37298): Disconnecting: Too many authentication failures for <*> [preauth] 106 | 0013 (size 37029): PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*> <*> 107 | 0011 (size 36967): message repeated <*> times: [ Failed password for <*> from <*> port <*> ssh2] 108 | 0006 (size 20241): Failed <*> for invalid user <*> from <*> port <*> ssh2 109 | 0004 (size 19852): pam unix(sshd:auth): check pass; user unknown 110 | 0001 (size 18909): reverse mapping checking getaddrinfo for <*> <*> failed - POSSIBLE BREAK-IN ATTEMPT! 111 | 0002 (size 14551): Invalid user <*> from <*> 112 | 0003 (size 14551): input userauth request: invalid user <*> [preauth] 113 | 0005 (size 14356): pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*> 114 | 0018 (size 1289): PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*> 115 | 0024 (size 952): fatal: Read from socket failed: Connection reset by peer [preauth] 116 | ... 117 | -------- 118 | <1> 51 _types_ of logs were identified from 655147 lines in 1.588s 119 | <2> There was `140768` similar log messages with this pattern, with `3` positions 120 | where the token is identified as parameter `<*>`. 121 | 122 | On the same dataset, the java implementation performed roughly around 10 times faster. 123 | As my implementation does not yet have masking, mask configuration was removed in the 124 | Drain3 implementation. 125 | 126 | === From Java 127 | 128 | This tool is not yet intended to be used as a library, but for the curious 129 | the DRAIN algorythm can be used this way: 130 | 131 | .Minimal DRAIN example 132 | [source, java] 133 | ---- 134 | var drain = Drain.drainBuilder() 135 | .additionalDelimiters("_") 136 | .depth(4) 137 | .build() 138 | Files.lines(Paths.get("build/resources/test/SSH.log"), 139 | StandardCharsets.UTF_8) 140 | .forEach(drain::parseLogMessage); 141 | 142 | // do something with clusters 143 | drain.clusters(); 144 | ---- 145 | 146 | 147 | 148 | == Status 149 | 150 | Pieces of puzzle are coming in no particular order, I first bootstrapped the code from a simple Java 151 | file. Then I wrote in Java an implementation of Drain. Now here's what I would like to do. 152 | 153 | .Todo 154 | - [ ] More unit tests 155 | - [x] Wire things together 156 | - [ ] More documentation 157 | - [x] Implement _tail follow_ mode (currently in drain mode the whole file is read and stops once finished) 158 | - [ ] In follow drain mode dump clusters on forced exit (e.g. for example when hitting `ctrl`+`c`) 159 | - [x] Start reading from the last x lines (like `tail -n 30`) 160 | - [ ] Implement log masking (e.g. log contain an email, or an IP address which may be considered as private data) 161 | 162 | .For later 163 | - [ ] Json message field extraction 164 | - [ ] How to handle prefixes : Dates, log level, etc. ; possibly using masking 165 | - [ ] Investigate marker with specific behavior, e.g. log level severity 166 | - [ ] Investigate log with stacktraces (likely multiline) 167 | - [ ] Improve handling of very long lines 168 | - [ ] Logback appender with micrometer counter 169 | 170 | == Motivation 171 | 172 | I was inspired by a https://sayr.us/log-pattern-recognition/logmine/[blog article from one of my colleague on LogMine], 173 | -- many thanks to him for doing the initial research and explaining concepts --, we were both impressed by the log 174 | pattern extraction of https://docs.datadoghq.com/logs/explorer/patterns/[Datadog's Log explorer], his blog post 175 | sparked my interest. 176 | 177 | After some discussion together, we saw that Drain was a bit superior to LogMine. 178 | Googling Drain in Java didn't yield any result, although I certainly didn't search exhaustively, 179 | but regardless this triggered the idea to implement this algorithm in Java. 180 | 181 | == References 182 | 183 | The Drain port is mostly a port of https://github.com/IBM/Drain3[Drain3] 184 | done by IBM folks (_David Ohana_, _Moshik Hershcovitch_). IBM's Drain3 is a fork of the 185 | https://github.com/logpai/logparser[original work] done by the LogPai team based on the paper of 186 | _Pinjia He_, _Jieming Zhu_, _Zibin Zheng_, and _Michael R. Lyu_. 187 | 188 | _I didn't follow up on other contributors of these projects, reach out if you think you have been omitted._ 189 | 190 | 191 | For reference here's the linked I looked at: 192 | 193 | * https://logparser.readthedocs.io/ 194 | * https://github.com/logpai/logparser 195 | * https://github.com/IBM/Drain3 196 | * https://jiemingzhu.github.io/pub/pjhe_icws2017.pdf 197 | (a copy of this publication accessible link:doc/pjhe_icws2017.pdf[there]) 198 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /tailer/src/main/java/io/github/bric3/tailer/file/MappedFileLineReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.tailer.file; 11 | 12 | import io.github.bric3.tailer.TailerMain; 13 | import io.github.bric3.tailer.config.Config; 14 | import io.github.bric3.tailer.config.FromLine; 15 | 16 | import java.io.BufferedReader; 17 | import java.io.Closeable; 18 | import java.io.IOException; 19 | import java.io.UncheckedIOException; 20 | import java.nio.channels.Channels; 21 | import java.nio.channels.FileChannel; 22 | import java.nio.channels.WritableByteChannel; 23 | import java.nio.charset.Charset; 24 | import java.nio.file.*; 25 | import java.util.Objects; 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.concurrent.atomic.AtomicBoolean; 28 | import java.util.function.Consumer; 29 | 30 | public class MappedFileLineReader implements Closeable { 31 | 32 | private final IOReadAction readAction; 33 | private final Config config; 34 | 35 | private final AtomicBoolean closed; 36 | private final int wsPollTimeoutMs; 37 | private long totalReadBytes; 38 | 39 | public MappedFileLineReader(Config config, IOReadAction readAction) { 40 | this.readAction = readAction; 41 | this.config = config; 42 | this.wsPollTimeoutMs = 100; 43 | this.closed = new AtomicBoolean(false); 44 | } 45 | 46 | public void tailRead(Path path, FromLine tailFromLine, boolean follow) { 47 | assert path != null; 48 | assert tailFromLine != null; 49 | 50 | 51 | try (var ws = FileSystems.getDefault().newWatchService(); 52 | var sourceChannel = FileChannel.open(path, StandardOpenOption.READ)) { 53 | var startPosition = findTailStartPosition(sourceChannel, tailFromLine); 54 | if (config.verbose) { 55 | config.out.printf("Reading file from position : %d%n", startPosition); 56 | } 57 | 58 | var position = startPosition; 59 | var readBytes = readAction.apply(sourceChannel, startPosition); 60 | totalReadBytes += readBytes; 61 | position += readBytes; 62 | if (config.verbose) { 63 | config.out.printf("Read: %d -> %d (%d bytes)%n", 64 | startPosition, 65 | position, 66 | position - startPosition); 67 | } 68 | 69 | if (follow) { 70 | path.getParent().register(ws, StandardWatchEventKinds.ENTRY_MODIFY); 71 | while (!closed.get()) { 72 | WatchKey wk; 73 | try { 74 | wk = ws.poll(wsPollTimeoutMs, TimeUnit.MILLISECONDS); 75 | } catch (InterruptedException e) { 76 | if (config.verbose) { 77 | e.printStackTrace(config.err); 78 | } 79 | Thread.currentThread().interrupt(); 80 | return; 81 | } 82 | if (wk == null) { 83 | continue; 84 | } 85 | 86 | for (WatchEvent event : wk.pollEvents()) { 87 | if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY 88 | && Objects.equals(event.context(), path.getFileName())) { 89 | var previousPosition = position; 90 | readBytes = readAction.apply(sourceChannel, position); 91 | totalReadBytes += readBytes; 92 | position += readBytes; 93 | if (config.verbose) { 94 | config.out.printf("Read: %d -> %d (%d bytes)%n", 95 | previousPosition, 96 | position, 97 | position - previousPosition); 98 | } 99 | } 100 | } 101 | var valid = wk.reset(); 102 | if (!valid) { 103 | break; // exit 104 | } 105 | } 106 | } 107 | 108 | totalReadBytes = position - startPosition; 109 | if (config.verbose) { 110 | config.out.printf("Total read: %d -> %d (%d bytes)%n", 111 | startPosition, 112 | position, 113 | totalReadBytes); 114 | } 115 | 116 | } catch (IOException e) { 117 | if (config.verbose) { 118 | e.printStackTrace(config.err); 119 | } 120 | System.exit(TailerMain.ERR_IO_TAILING_FILE); 121 | } 122 | } 123 | 124 | public long totalReadBytes() { 125 | return totalReadBytes; 126 | } 127 | 128 | public void close() { 129 | closed.set(true); 130 | } 131 | 132 | public static class LineConsumer implements IOReadAction { 133 | private final Consumer stringConsumer; 134 | private final Charset charset; 135 | 136 | public LineConsumer(Consumer stringConsumer, Charset charset) { 137 | this.stringConsumer = stringConsumer; 138 | this.charset = charset; 139 | } 140 | 141 | @Override 142 | public long apply(FileChannel fileChannel, long startPosition) throws IOException { 143 | return readByLines(fileChannel, 144 | startPosition, 145 | stringConsumer); 146 | } 147 | 148 | private long readByLines(FileChannel sourceChannel, long startPosition, Consumer stringConsumer) throws IOException { 149 | var reader = Channels.newReader(sourceChannel, charset); // investigate decoder customization 150 | var br = new BufferedReader(reader); // handles new lines and EOF 151 | 152 | sourceChannel.position(startPosition); // avoid reading the file if unnecessary 153 | br.lines() 154 | .onClose(() -> { 155 | try { 156 | br.close(); 157 | } catch (IOException ex) { 158 | throw new UncheckedIOException(ex); 159 | } 160 | }) 161 | .forEach(stringConsumer); 162 | 163 | return sourceChannel.position() - startPosition; 164 | } 165 | } 166 | 167 | public static class ChannelSink implements IOReadAction { 168 | private final WritableByteChannel sink; 169 | 170 | public ChannelSink(WritableByteChannel sink) { 171 | this.sink = sink; 172 | } 173 | 174 | 175 | @Override 176 | public long apply(FileChannel fileChannel, long startPosition) throws IOException { 177 | return tail(fileChannel, startPosition, sink); 178 | } 179 | 180 | private long tail(FileChannel pathChannel, 181 | long startPosition, 182 | WritableByteChannel sink) throws IOException { 183 | assert pathChannel != null && sink != null; 184 | assert startPosition >= 0; 185 | var fileSize = pathChannel.size(); 186 | 187 | return pathChannel.transferTo(startPosition, fileSize, sink); 188 | } 189 | } 190 | 191 | long findTailStartPosition(FileChannel channel, FromLine fromLine) throws IOException { 192 | // straw man find start position 193 | // this implementation hasn't been tested with two char line endings (CR (0x0D) and LF (0x0A)) 194 | var buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 195 | 196 | if (!fromLine.fromStart) { 197 | if (fromLine.number == 0) { 198 | return channel.size(); 199 | } 200 | 201 | // go to end 202 | buffer.position((int) channel.size()); 203 | 204 | long lineCounter = 0; 205 | for (long i = channel.size() - 1; i >= 0; i--) { 206 | char c = (char) buffer.get((int) i); 207 | 208 | if (c == '\n') { // on newline 209 | if (lineCounter == fromLine.number) { 210 | return i + 1; 211 | } 212 | lineCounter++; 213 | } 214 | } 215 | return 0; 216 | } else { 217 | if (fromLine.number == 0) { 218 | return 0; 219 | } 220 | 221 | long lineCounter = 0; 222 | for (long i = 0, channelSize = channel.size(); i < channelSize; i++) { 223 | char c = (char) buffer.get((int) i); 224 | 225 | if (c == '\n') { // on newline 226 | if (lineCounter == fromLine.number) { 227 | return i - 1; 228 | } 229 | lineCounter++; 230 | } 231 | } 232 | return channel.size(); 233 | } 234 | } 235 | 236 | 237 | public interface IOReadAction { 238 | IOReadAction NO_OP = (c, s) -> 0; 239 | 240 | long apply(FileChannel fileChannel, long startPosition) throws IOException; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /drain-java-jackson/src/main/java/io/github/bric3/drain/core/DrainJsonSerialization.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.core; 11 | 12 | import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; 13 | import com.fasterxml.jackson.annotation.JsonProperty; 14 | import com.fasterxml.jackson.annotation.PropertyAccessor; 15 | import com.fasterxml.jackson.core.JsonGenerator; 16 | import com.fasterxml.jackson.core.JsonParser; 17 | import com.fasterxml.jackson.core.ObjectCodec; 18 | import com.fasterxml.jackson.databind.*; 19 | import com.fasterxml.jackson.databind.json.JsonMapper; 20 | import com.fasterxml.jackson.databind.module.SimpleModule; 21 | 22 | import java.io.IOException; 23 | import java.io.Reader; 24 | import java.io.UncheckedIOException; 25 | import java.io.Writer; 26 | import java.util.*; 27 | import java.util.function.Function; 28 | import java.util.stream.Collectors; 29 | 30 | /** 31 | * Simple and Drain state serialization mechanism. 32 | * 33 | * DISCLAIMER This tool allows to (de)serialize the Drain state. 34 | * But it requires to be in the same package as the Drain class. 35 | * This is not good solution so be advised that the qualified name of this API 36 | * might to change. 37 | * 38 | * Example use: 39 | *


 40 |  *     Drain drain = ...
 41 |  *     Writer writer = ...
 42 |  *     DrainJsonSerialization serde = new DrainJsonSerialization();
 43 |  *
 44 |  *     serde.saveState(drain, writer);
 45 |  * 
46 | * 47 | *

 48 |  *     Reader reader = ...
 49 |  *     DrainJsonSerialization serde = new DrainJsonSerialization();
 50 |  *
 51 |  *     Drain drain = serde.loadState(reader);
 52 |  * 
53 | * 54 | * @author brice.dutheil@gmail.com 55 | */ 56 | public class DrainJsonSerialization { 57 | 58 | public static final JsonMapper JSON_MAPPER = 59 | JsonMapper.builder() 60 | .addModule(new SimpleModule() 61 | .addSerializer(Drain.class, new DrainSerializer()) 62 | .addDeserializer(Drain.class, new DrainDeserializer()) 63 | .addSerializer(Node.class, new TreeNodeSerializer()) 64 | .addDeserializer(Node.class, new TreeNodeDeserializer())) 65 | .visibility(PropertyAccessor.FIELD, Visibility.ANY) 66 | .addMixIn(InternalLogCluster.class, LogClusterMixin.class) 67 | .build(); 68 | 69 | /** 70 | * Drain-object exporting functionality which saves a drain model 71 | * in a json file at given path. 72 | */ 73 | public void saveState(Drain drain, Writer writer) { 74 | try { 75 | JSON_MAPPER.writerWithDefaultPrettyPrinter() 76 | .writeValue(writer, drain); 77 | } catch (IOException e) { 78 | throw new UncheckedIOException(e); 79 | } 80 | } 81 | 82 | public Drain loadState(Reader reader) { 83 | try { 84 | return JSON_MAPPER.reader() 85 | .withAttribute(ClustersRef.class, new ClustersRef()) 86 | .readValue(reader, Drain.class); 87 | } catch (IOException e) { 88 | throw new UncheckedIOException(e); 89 | } 90 | } 91 | 92 | private static class TreeNodeSerializer extends JsonSerializer { 93 | @Override 94 | public void serialize(Node value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 95 | gen.writeStartObject(); 96 | gen.writeNumberField("depth", value.depth); 97 | gen.writeObjectField("key", value.key); 98 | gen.writeObjectField("children", value.childMappings()); 99 | gen.writeArrayFieldStart("clusters"); 100 | 101 | for (InternalLogCluster c : value.clusters()) { 102 | gen.writeString(ClustersRef.toRef(c.clusterId())); 103 | } 104 | gen.writeEndArray(); 105 | gen.writeEndObject(); 106 | } 107 | 108 | @Override 109 | public Class handledType() { 110 | return Node.class; 111 | } 112 | } 113 | 114 | private static class DrainSerializer extends JsonSerializer { 115 | @Override 116 | public void serialize(Drain value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 117 | gen.writeStartObject(); 118 | gen.writeNumberField("effective-depth", value.depth); 119 | gen.writeNumberField("similarity-threshold", value.similarityThreshold); 120 | gen.writeNumberField("max-child-per-node", value.maxChildPerNode); 121 | gen.writeStringField("delimiters", value.delimiters); 122 | gen.writeObjectField("clusters", value.clusters()); 123 | gen.writeObjectField("prefix-tree", value.prefixTree()); 124 | gen.writeEndObject(); 125 | } 126 | 127 | @Override 128 | public Class handledType() { 129 | return Drain.class; 130 | } 131 | } 132 | 133 | private static class DrainDeserializer extends JsonDeserializer { 134 | @Override 135 | public Drain deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 136 | ObjectCodec codec = p.getCodec(); 137 | JsonNode jsonNode = codec.readTree(p); 138 | 139 | final ArrayList clusters = codec.readValue( 140 | codec.treeAsTokens(jsonNode.get("clusters")), 141 | ctxt.getTypeFactory().constructCollectionType(ArrayList.class, InternalLogCluster.class)); 142 | 143 | ((ClustersRef) ctxt.getAttribute(ClustersRef.class)).hold(clusters); 144 | 145 | return new Drain(new DrainState( 146 | jsonNode.get("effective-depth").asInt(), 147 | jsonNode.get("similarity-threshold").asDouble(), 148 | jsonNode.get("max-child-per-node").asInt(), 149 | jsonNode.get("delimiters").asText(), 150 | clusters, 151 | ctxt.readValue(codec.treeAsTokens(jsonNode.get("prefix-tree")), Node.class) 152 | )); 153 | } 154 | 155 | @Override 156 | public Class handledType() { 157 | return Drain.class; 158 | } 159 | } 160 | 161 | private static class TreeNodeDeserializer extends JsonDeserializer { 162 | @Override 163 | public Node deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 164 | final ClustersRef clustersRef = (ClustersRef) ctxt.getAttribute(ClustersRef.class); 165 | final ObjectCodec codec = p.getCodec(); 166 | final JsonNode jsonNode = codec.readTree(p); 167 | 168 | final ArrayList nodeClusters = new ArrayList<>(); 169 | for (JsonNode clusterId : jsonNode.get("clusters")) { 170 | nodeClusters.add(clustersRef.get(clusterId.textValue())); 171 | } 172 | 173 | final int depth = jsonNode.get("depth").asInt(); 174 | 175 | final JsonParser jsonParser = codec.treeAsTokens(jsonNode.get("children")); 176 | // This json parser starts with JsonTokenId.ID_NO_TOKEN, 177 | // the map deserializer expects the parser to have already 178 | // advanced to the first token otherwise this fails with 179 | // "Unexpected end-of-input ...", to avoid that this parser 180 | // is advanced to the first token 181 | jsonParser.nextToken(); 182 | final HashMap children = ctxt.readValue( 183 | jsonParser, 184 | ctxt.getTypeFactory() 185 | .constructMapType(HashMap.class, 186 | depth == 0 ? int.class : String.class, 187 | Node.class) 188 | ); 189 | 190 | Object key = depth == 1 ? 191 | Integer.valueOf(jsonNode.get("key").asInt()) : 192 | jsonNode.get("key").asText(); 193 | 194 | return new Node( 195 | key, 196 | depth, 197 | children, 198 | nodeClusters 199 | ); 200 | } 201 | } 202 | 203 | 204 | static class ClustersRef { 205 | private List clusters; 206 | private Map clusterIndex; 207 | 208 | public static String toRef(UUID clusterId) { 209 | return "clusterId-" + clusterId; 210 | } 211 | 212 | public void hold(List clusters) { 213 | this.clusters = clusters; 214 | this.clusterIndex = clusters.stream().collect(Collectors.toMap( 215 | logCluster -> toRef(logCluster.clusterId()), 216 | Function.identity() 217 | )); 218 | } 219 | 220 | public InternalLogCluster get(String clusterId) { 221 | final InternalLogCluster logCluster = clusterIndex.get(clusterId); 222 | assert logCluster != null : "id:" + clusterId + " size:" + clusters.size() + "\n" + clusters; 223 | return logCluster; 224 | } 225 | } 226 | 227 | private static class LogClusterMixin { 228 | public LogClusterMixin( 229 | @JsonProperty("clusterId") UUID clusterId, 230 | @JsonProperty("sightings") int sightings, 231 | @JsonProperty("logTemplateTokens") List logTemplateTokens) { 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /drain-java-core/src/main/java/io/github/bric3/drain/core/Drain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.core; 11 | 12 | import io.github.bric3.drain.internal.Tokenizer; 13 | 14 | import javax.annotation.Nonnull; 15 | import javax.annotation.Nullable; 16 | import java.util.ArrayList; 17 | import java.util.Collections; 18 | import java.util.List; 19 | 20 | /** 21 | * Drain log pattern miner. 22 | *

23 | * This code comes from a modified work of the LogPai team by IBM engineers, 24 | * but it has been improved to fit the Java platform. 25 | *

26 | *

27 | * Use the builder method {@link #drainBuilder()} to configure an 28 | * instance. 29 | *

30 | * 31 | *

32 | * Example use: 33 | *


 34 |  * var drain = Drain.drainBuilder()
 35 |  *                  .additionalDelimiters("_")
 36 |  *                  .depth(4)
 37 |  *                  .build();
 38 |  * Files.lines(
 39 |  *     Paths.get("file.log"),
 40 |  *     StandardCharsets.UTF_8
 41 |  * ).forEach(drain::parseLogMessage);
 42 |  *
 43 |  * // do something with clusters
 44 |  * drain.clusters();
 45 |  * 
46 | * 47 | *

48 | * Note this implementation is not thread safe. 49 | * 50 | * @author brice.dutheil@gmail.com 51 | * @modifiedBy david.ohana@ibm.com, moshikh@il.ibm.com 52 | * @originalAuthor LogPAI team 53 | * @license MIT 54 | */ 55 | public class Drain { 56 | /** 57 | * Marker for similar tokens 58 | */ 59 | public static final String PARAM_MARKER = "<*>"; 60 | private static final int ROOT_AND_LEAF_LEVELS = 2; 61 | 62 | /** 63 | * Depth of all leaf nodes. 64 | * 65 | * These are the nodes that contain the log clusters. 66 | */ 67 | final int depth; 68 | 69 | /** 70 | * Similarity threshold. 71 | */ 72 | final double similarityThreshold; 73 | 74 | /** 75 | * Maximum number of child nodes per node 76 | */ 77 | final int maxChildPerNode; 78 | 79 | /** 80 | * Delimiters to apply when splitting log messages into words. 81 | * 82 | * In addition to whitespaces. 83 | */ 84 | final String delimiters; 85 | 86 | /** 87 | * All log clusters. 88 | */ 89 | private final List clusters; 90 | 91 | private final Node root; 92 | 93 | private Drain(int depth, 94 | double similarityThreshold, 95 | int maxChildPerNode, 96 | String additionalDelimiters) { 97 | this.depth = depth - ROOT_AND_LEAF_LEVELS; 98 | this.similarityThreshold = similarityThreshold; 99 | this.maxChildPerNode = maxChildPerNode; 100 | this.delimiters = " " + additionalDelimiters; 101 | root = new Node("(ROOT)", 0); 102 | clusters = new ArrayList<>(); 103 | } 104 | 105 | Drain(DrainState state) { 106 | this.depth = state.depth; 107 | this.similarityThreshold = state.similarityThreshold; 108 | this.maxChildPerNode = state.maxChildPerNode; 109 | this.delimiters = state.delimiters; 110 | this.clusters = state.clusters; 111 | this.root = state.prefixTree; 112 | } 113 | 114 | /** 115 | * Parse log message. 116 | * 117 | * Classify the log message to a cluster. 118 | * 119 | * @param message The log message content 120 | */ 121 | public void parseLogMessage(@Nonnull String message) { 122 | // sprint message by delimiter / whitespaces 123 | List contentTokens = Tokenizer.tokenize(message, delimiters); 124 | 125 | // Search the prefix tree 126 | InternalLogCluster matchCluster = treeSearch(contentTokens); 127 | 128 | if (matchCluster == null) { 129 | // create cluster if it doesn't exists, using log content tokens as template tokens 130 | matchCluster = new InternalLogCluster(contentTokens); 131 | clusters.add(matchCluster); 132 | addLogClusterToPrefixTree(matchCluster); 133 | } else { 134 | // add the log to an existing cluster 135 | matchCluster.newSighting(contentTokens); 136 | } 137 | } 138 | 139 | /** 140 | * Search a matching log cluster given a log message. 141 | * 142 | * @param message The log message content 143 | * @return The matching log cluster or null if no match 144 | */ 145 | public LogCluster searchLogMessage(@Nonnull String message) { 146 | // sprint message by delimiter / whitespaces 147 | List contentTokens = Tokenizer.tokenize(message, delimiters); 148 | 149 | // Search the prefix tree 150 | LogCluster matchCluster = treeSearch(contentTokens); 151 | return matchCluster; 152 | } 153 | 154 | 155 | private @Nullable 156 | InternalLogCluster treeSearch(@Nonnull List logTokens) { 157 | 158 | // at first level, children are grouped by token (word) count 159 | int tokensCount = logTokens.size(); 160 | Node node = this.root.get(tokensCount); 161 | 162 | // the prefix tree is empty 163 | if (node == null) { 164 | return null; 165 | } 166 | 167 | // handle case of empty log string - return the single cluster in that group 168 | if (tokensCount == 0) { 169 | return node.clusterOf(0); 170 | } 171 | 172 | // find the leaf node for this log 173 | // a path of nodes matching the first N tokens (N=tree depth) 174 | int currentDepth = 1; 175 | for (String token : logTokens) { 176 | // if max depth reached or last parseable token, bail out 177 | boolean atMaxDepth = currentDepth == this.depth; 178 | boolean isLastToken = currentDepth == tokensCount; 179 | if (atMaxDepth || isLastToken) { 180 | break; 181 | } 182 | 183 | // descend 184 | Node nextNode = node.get(token); 185 | // if null try get from generic pattern 186 | if (nextNode == null) { 187 | nextNode = node.get(PARAM_MARKER); 188 | } 189 | // if the node don't exists yet, the cluster don't exists yet 190 | if (nextNode == null) { 191 | return null; 192 | } 193 | node = nextNode; 194 | currentDepth++; 195 | } 196 | 197 | return fastMatch(node.clusters(), logTokens); 198 | } 199 | 200 | private @Nullable 201 | InternalLogCluster fastMatch(@Nonnull List clusters, 202 | @Nonnull List logTokens) { 203 | InternalLogCluster matchedCluster = null; 204 | 205 | double maxSimilarity = -1; 206 | int maxParamCount = -1; 207 | InternalLogCluster maxCluster = null; 208 | 209 | for (InternalLogCluster cluster : clusters) { 210 | SeqDistance seqDistance = computeSeqDistance(cluster.internalTokens(), logTokens); 211 | if (seqDistance.similarity > maxSimilarity 212 | || (seqDistance.similarity == maxSimilarity 213 | && seqDistance.paramCount > maxParamCount)) { 214 | maxSimilarity = seqDistance.similarity; 215 | maxParamCount = seqDistance.paramCount; 216 | maxCluster = cluster; 217 | } 218 | } 219 | 220 | if (maxSimilarity >= this.similarityThreshold) { 221 | matchedCluster = maxCluster; 222 | } 223 | 224 | return matchedCluster; 225 | } 226 | 227 | private static class SeqDistance { 228 | 229 | final double similarity; 230 | final int paramCount; 231 | SeqDistance(double similarity, int paramCount) { 232 | this.similarity = similarity; 233 | this.paramCount = paramCount; 234 | } 235 | 236 | } 237 | 238 | static @Nonnull 239 | SeqDistance computeSeqDistance(@Nonnull List templateTokens, 240 | @Nonnull List logTokens) { 241 | assert templateTokens.size() == logTokens.size(); 242 | 243 | int similarTokens = 0; 244 | int paramCount = 0; 245 | 246 | for (int i = 0, tokensSize = templateTokens.size(); i < tokensSize; i++) { 247 | String token = templateTokens.get(i); 248 | String currentToken = logTokens.get(i); 249 | 250 | if (token.equals(PARAM_MARKER)) { 251 | paramCount++; 252 | continue; 253 | } 254 | if (token.equals(currentToken)) { 255 | similarTokens++; 256 | } 257 | } 258 | 259 | double similarity = (double) similarTokens / templateTokens.size(); 260 | return new SeqDistance(similarity, paramCount); 261 | } 262 | 263 | private void addLogClusterToPrefixTree(@Nonnull InternalLogCluster newLogCluster) { 264 | int tokensCount = newLogCluster.internalTokens().size(); 265 | 266 | Node node = this.root.getOrCreateChild(tokensCount); 267 | 268 | // handle case of empty log message 269 | if (tokensCount == 0) { 270 | node.appendCluster(newLogCluster); 271 | return; 272 | } 273 | 274 | 275 | int currentDepth = 1; 276 | for (String token : newLogCluster.internalTokens()) { 277 | 278 | // Add current log cluster to the leaf node 279 | boolean atMaxDepth = currentDepth == this.depth; 280 | boolean isLastToken = currentDepth == tokensCount; 281 | if (atMaxDepth || isLastToken) { 282 | node.appendCluster(newLogCluster); 283 | break; 284 | } 285 | 286 | // If token not matched in this layer of existing tree. 287 | // TODO see improvements are possible 288 | if (!node.contains(token)) { 289 | if (!hasNumber(token)) { 290 | if (node.contains(PARAM_MARKER)) { 291 | if (node.childrenCount() < maxChildPerNode) { 292 | node = node.getOrCreateChild(token); 293 | } else { 294 | node = node.get(PARAM_MARKER); 295 | } 296 | } else { 297 | if (node.childrenCount() + 1 <= maxChildPerNode) { 298 | node = node.getOrCreateChild(token); 299 | } else if (node.childrenCount() + 1 == maxChildPerNode) { 300 | node = node.getOrCreateChild(PARAM_MARKER); 301 | } else { 302 | node = node.get(PARAM_MARKER); 303 | } 304 | } 305 | } else { 306 | if (!node.contains(PARAM_MARKER)) { 307 | node = node.getOrCreateChild(PARAM_MARKER); 308 | } else { 309 | node = node.get(PARAM_MARKER); 310 | } 311 | } 312 | } else { 313 | node = node.get(token); 314 | } 315 | currentDepth++; 316 | } 317 | } 318 | 319 | 320 | private static boolean hasNumber(@Nonnull String s) { 321 | return s.chars().anyMatch(Character::isDigit); 322 | } 323 | 324 | /** 325 | * Returns a list of the Log clusters. 326 | * 327 | * @return Non modifiable list of current clusters. 328 | */ 329 | public List clusters() { 330 | return Collections.unmodifiableList(new ArrayList<>(clusters)); 331 | } 332 | 333 | Node prefixTree() { 334 | return root; 335 | } 336 | 337 | 338 | /** 339 | * Drain builder. 340 | * 341 | * Used like this: 342 | *


343 |      *     Drain.drainBuilder()
344 |      *          .additionalDelimiters("_")
345 |      *          .depth(4)
346 |      *          .build()
347 |      * 
348 | * 349 | * @return a drain builder 350 | */ 351 | public static DrainBuilder drainBuilder() { 352 | return new DrainBuilder(); 353 | } 354 | 355 | /** 356 | * Builder for {@link Drain} 357 | */ 358 | public static class DrainBuilder { 359 | private int depth = 4; 360 | private String additionalDelimiters = ""; 361 | private double similarityThreshold = 0.4d; 362 | private int maxChildPerNode = 100; 363 | 364 | /** 365 | * Depth of all leaf nodes. 366 | * 367 | * How many level to reach the nodes that contain log clusters. 368 | * 369 | * The default value is 4, the minimum value is 3. 370 | * 371 | * @param depth The depth of all leaf nodes. 372 | * @return this 373 | */ 374 | public DrainBuilder depth(int depth) { 375 | assert depth > 2; 376 | this.depth = depth; 377 | return this; 378 | } 379 | 380 | /** 381 | * Additional delimiters. 382 | * 383 | * Additionally to the whitespace, also use additional delimiting 384 | * characters to to split the log message into tokens. This value 385 | * is empty by default. 386 | * 387 | * @param additionalDelimiters THe Additional delimiters. 388 | * @return this 389 | */ 390 | public DrainBuilder additionalDelimiters(String additionalDelimiters) { 391 | assert additionalDelimiters != null; 392 | this.additionalDelimiters = additionalDelimiters; 393 | return this; 394 | } 395 | 396 | /** 397 | * Similarity threshold. 398 | * 399 | * The similarity threshold applies to each token of a log message, 400 | * if the percentage of similar tokens is below this number, then 401 | * a new log cluster will be created. 402 | * 403 | * Default value is 0.4. 404 | * 405 | * @param similarityThreshold The similarity threshold 406 | * @return this 407 | */ 408 | public DrainBuilder similarityThreshold(double similarityThreshold) { 409 | assert similarityThreshold > 0.1d; 410 | this.similarityThreshold = similarityThreshold; 411 | return this; 412 | } 413 | 414 | /** 415 | * Max number of children of an internal node. 416 | * 417 | * Limit the number of children nodes, if this value is too low 418 | * and log messages are too versatile then many logs will be 419 | * classified under the generic param marker. 420 | * 421 | * Default value is 100. 422 | * 423 | * @param maxChildPerNode Max number of children of an internal node 424 | * @return this 425 | */ 426 | public DrainBuilder maxChildPerNode(int maxChildPerNode) { 427 | assert maxChildPerNode >= 2; 428 | this.maxChildPerNode = maxChildPerNode; 429 | return this; 430 | } 431 | 432 | /** 433 | * Build a non thread safe instance of Drain. 434 | * 435 | * @return A {@see Drain} instance 436 | */ 437 | public Drain build() { 438 | return new Drain(depth, 439 | similarityThreshold, 440 | maxChildPerNode, 441 | additionalDelimiters); 442 | } 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /drain-java-core/src/test/java/io/github/bric3/drain/core/DrainBulkTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * drain-java 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.drain.core; 11 | 12 | import io.github.bric3.drain.internal.Stopwatch; 13 | import io.github.bric3.drain.utils.TestPaths; 14 | import org.junit.jupiter.api.Test; 15 | 16 | import java.io.IOException; 17 | import java.nio.charset.StandardCharsets; 18 | import java.nio.file.Files; 19 | import java.util.Comparator; 20 | import java.util.List; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import java.util.stream.Collectors; 23 | import java.util.stream.Stream; 24 | 25 | import static org.assertj.core.api.Assertions.assertThat; 26 | 27 | class DrainBulkTest { 28 | 29 | @Test 30 | void smokeTest() throws IOException { 31 | Drain drain = Drain.drainBuilder() 32 | .additionalDelimiters("_") 33 | .depth(4) 34 | .build(); 35 | 36 | AtomicInteger lineCounter = new AtomicInteger(); 37 | 38 | Stopwatch stopwatch = Stopwatch.createStarted(); 39 | try (Stream lines = Files.lines(TestPaths.get("SSH.log"), StandardCharsets.UTF_8)) { 40 | lines.peek(__ -> lineCounter.incrementAndGet()) 41 | .map(l -> l.substring(l.indexOf("]: ") + 3)) // removes this part: "Dec 10 06:55:46 LabSZ sshd[24200]: " 42 | .forEach(content -> { 43 | drain.parseLogMessage(content); 44 | if (lineCounter.get() % 10000 == 0) { 45 | System.out.printf("%4d clusters so far%n", drain.clusters().size()); 46 | } 47 | }); 48 | } 49 | 50 | 51 | System.out.printf("---- Done processing file. Total of %d lines, done in %s, %d clusters%n", 52 | lineCounter.get(), 53 | stopwatch, 54 | drain.clusters().size()); 55 | 56 | assertThat(drain.clusters()).hasSize(51); 57 | 58 | List sortedClusters = drain.clusters() 59 | .stream() 60 | .sorted(Comparator.comparing(LogCluster::sightings).reversed()) 61 | .collect(Collectors.toList()); 62 | 63 | // sortedClusters.forEach(System.out::println); 64 | 65 | // based on cluster found in https://zenodo.org/record/3227177/files/SSH.tar.gz 66 | assertCluster(sortedClusters, 0, 140768, "Failed password for <*> from <*> port <*> ssh2"); 67 | assertCluster(sortedClusters, 1, 140701, "pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*> <*>"); 68 | assertCluster(sortedClusters, 2, 68958, "Connection closed by <*> [preauth]"); 69 | assertCluster(sortedClusters, 3, 46642, "Received disconnect from <*> 11: <*> <*> <*>"); 70 | assertCluster(sortedClusters, 4, 37963, "PAM service(sshd) ignoring max retries; <*> > 3"); 71 | assertCluster(sortedClusters, 5, 37298, "Disconnecting: Too many authentication failures for <*> [preauth]"); 72 | assertCluster(sortedClusters, 6, 37029, "PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*> <*>"); 73 | assertCluster(sortedClusters, 7, 36967, "message repeated <*> times: [ Failed password for <*> from <*> port <*> ssh2]"); 74 | assertCluster(sortedClusters, 8, 20241, "Failed <*> for invalid user <*> from <*> port <*> ssh2"); 75 | assertCluster(sortedClusters, 9, 19852, "pam unix(sshd:auth): check pass; user unknown"); 76 | assertCluster(sortedClusters, 10, 18909, "reverse mapping checking getaddrinfo for <*> <*> failed - POSSIBLE BREAK-IN ATTEMPT!"); 77 | assertCluster(sortedClusters, 11, 14551, "Invalid user <*> from <*>"); 78 | assertCluster(sortedClusters, 12, 14551, "input userauth request: invalid user <*> [preauth]"); 79 | assertCluster(sortedClusters, 13, 14356, "pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*>"); 80 | assertCluster(sortedClusters, 14, 1289, "PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*>"); 81 | assertCluster(sortedClusters, 15, 952, "fatal: Read from socket failed: Connection reset by peer [preauth]"); 82 | assertCluster(sortedClusters, 16, 930, "error: Received disconnect from 103.99.0.122: 14: No more user authentication methods available. [preauth]"); 83 | assertCluster(sortedClusters, 17, 838, "Did not receive identification string from <*>"); 84 | assertCluster(sortedClusters, 18, 592, "Received disconnect from <*> 11: Closed due to user request. [preauth]"); 85 | assertCluster(sortedClusters, 19, 497, "Address <*> maps to <*> but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!"); 86 | assertCluster(sortedClusters, 20, 182, "Accepted password for <*> from <*> port <*> ssh2"); 87 | assertCluster(sortedClusters, 21, 182, "pam unix(sshd:session): session opened for user <*> by (uid=0)"); 88 | assertCluster(sortedClusters, 22, 182, "pam unix(sshd:session): session closed for user <*>"); 89 | assertCluster(sortedClusters, 23, 177, "error: Received disconnect from <*> 3: com.jcraft.jsch.JSchException: Auth <*> [preauth]"); 90 | assertCluster(sortedClusters, 24, 108, "Received disconnect from <*> 11: [preauth]"); 91 | assertCluster(sortedClusters, 25, 92, "Received disconnect from 139.59.209.18: 11: Normal Shutdown, Thank you for playing [preauth]"); 92 | assertCluster(sortedClusters, 26, 87, "Received disconnect from <*> 11: <*> <*> <*> [preauth]"); 93 | assertCluster(sortedClusters, 27, 60, "Received disconnect from <*> 11: disconnect [preauth]"); 94 | assertCluster(sortedClusters, 28, 30, "Invalid user <*> <*> from <*>"); 95 | assertCluster(sortedClusters, 29, 30, "input userauth request: invalid user <*> <*> [preauth]"); 96 | assertCluster(sortedClusters, 30, 30, "Failed password for invalid user <*> <*> from <*> port <*> ssh2"); 97 | assertCluster(sortedClusters, 31, 13, "Invalid user from <*>"); 98 | assertCluster(sortedClusters, 32, 13, "input userauth request: invalid user [preauth]"); 99 | assertCluster(sortedClusters, 33, 13, "Failed <*> for invalid user from <*> port <*> ssh2"); 100 | assertCluster(sortedClusters, 34, 8, "Bad protocol version identification <*> <*> <*> from <*> port <*>"); 101 | assertCluster(sortedClusters, 35, 7, "Bad protocol version identification <*> <*> from <*> port <*>"); 102 | assertCluster(sortedClusters, 36, 7, "Bad protocol version identification <*> from <*> port <*>"); 103 | assertCluster(sortedClusters, 37, 6, "fatal: no hostkey alg [preauth]"); 104 | assertCluster(sortedClusters, 38, 6, "error: Received disconnect from <*> <*> <*> <*> <*> <*> [preauth]"); 105 | assertCluster(sortedClusters, 39, 6, "error: connect to <*> port 22: failed."); 106 | assertCluster(sortedClusters, 40, 6, "Server listening on <*> port <*>"); 107 | assertCluster(sortedClusters, 41, 3, "fatal: Write failed: Connection reset by peer [preauth]"); 108 | assertCluster(sortedClusters, 42, 3, "error: Received disconnect from 195.154.45.62: 3: com.jcraft.jsch.JSchException: timeout in waiting for rekeying process. [preauth]"); 109 | assertCluster(sortedClusters, 43, 3, "Received disconnect from <*> 11: Disconnect requested by Windows SSH Client."); 110 | assertCluster(sortedClusters, 44, 2, "error: Received disconnect from 191.96.249.68: 13: User request [preauth]"); 111 | assertCluster(sortedClusters, 45, 2, "error: Received disconnect from 212.83.176.1: 3: org.vngx.jsch.userauth.AuthCancelException: User authentication canceled by user [preauth]"); 112 | assertCluster(sortedClusters, 46, 1, "Bad packet length 1819045217. [preauth]"); 113 | assertCluster(sortedClusters, 47, 1, "Disconnecting: Packet corrupt [preauth]"); 114 | assertCluster(sortedClusters, 48, 1, "Received disconnect from 67.160.100.130: 11:"); 115 | assertCluster(sortedClusters, 49, 1, "Corrupted MAC on input. [preauth]"); 116 | assertCluster(sortedClusters, 50, 1, "syslogin perform logout: logout() returned an error"); 117 | } 118 | 119 | @Test 120 | void can_find_a_log() throws IOException { 121 | Drain drain = Drain.drainBuilder() 122 | .additionalDelimiters("_") 123 | .depth(4) 124 | .build(); 125 | 126 | try (Stream lines = Files.lines(TestPaths.get("SSH.log"), StandardCharsets.UTF_8)) { 127 | lines.map(l -> l.substring(l.indexOf("]: ") + 3)) // removes this part: "Dec 10 06:55:46 LabSZ sshd[24200]: " 128 | .forEach(drain::parseLogMessage); 129 | } 130 | 131 | LogCluster logCluster = drain.searchLogMessage("Received disconnect from 202.100.179.208: 11: Bye Bye [preauth]"); 132 | assertCluster(logCluster, 46642, "Received disconnect from <*> 11: <*> <*> <*>"); 133 | } 134 | 135 | private void assertCluster(List sortedClusters, int i, int sightings, String tokens) { 136 | assertCluster(sortedClusters.get(i), sightings, tokens); 137 | } 138 | 139 | private static void assertCluster(LogCluster logCluster, int sightings, String tokens) { 140 | assertThat(logCluster.sightings()).isEqualTo(sightings); 141 | assertThat(String.join(" ", logCluster.tokens())).isEqualTo(tokens); 142 | } 143 | } 144 | /* 145 | Python without masking: 146 | ======================= 147 | 148 | --- Done processing file. Total of 655147 lines, rate 52853.2 lines/sec, 51 clusters 149 | A0010 (size 140768): Failed password for <*> from <*> port <*> ssh2 150 | A0009 (size 140701): pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*> <*> 151 | A0007 (size 68958): Connection closed by <*> [preauth] 152 | A0008 (size 46642): Received disconnect from <*> 11: <*> <*> <*> 153 | A0014 (size 37963): PAM service(sshd) ignoring max retries; <*> > 3 154 | A0012 (size 37298): Disconnecting: Too many authentication failures for <*> [preauth] 155 | A0013 (size 37029): PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*> <*> 156 | A0011 (size 36967): message repeated <*> times: [ Failed password for <*> from <*> port <*> ssh2] 157 | A0006 (size 20241): Failed <*> for invalid user <*> from <*> port <*> ssh2 158 | A0004 (size 19852): pam unix(sshd:auth): check pass; user unknown 159 | A0001 (size 18909): reverse mapping checking getaddrinfo for <*> <*> failed - POSSIBLE BREAK-IN ATTEMPT! 160 | A0002 (size 14551): Invalid user <*> from <*> 161 | A0003 (size 14551): input userauth request: invalid user <*> [preauth] 162 | A0005 (size 14356): pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*> 163 | A0018 (size 1289): PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*> 164 | A0024 (size 952): fatal: Read from socket failed: Connection reset by peer [preauth] 165 | A0019 (size 930): error: Received disconnect from 103.99.0.122: 14: No more user authentication methods available. [preauth] 166 | A0015 (size 838): Did not receive identification string from <*> 167 | A0017 (size 592): Received disconnect from <*> 11: Closed due to user request. [preauth] 168 | A0031 (size 497): Address <*> maps to <*> but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT! 169 | A0020 (size 182): Accepted password for <*> from <*> port <*> ssh2 170 | A0021 (size 182): pam unix(sshd:session): session opened for user <*> by (uid=0) 171 | A0022 (size 182): pam unix(sshd:session): session closed for user <*> 172 | A0016 (size 177): error: Received disconnect from <*> 3: com.jcraft.jsch.JSchException: Auth <*> [preauth] 173 | A0032 (size 108): Received disconnect from <*> 11: [preauth] 174 | A0050 (size 92): Received disconnect from 139.59.209.18: 11: Normal Shutdown, Thank you for playing [preauth] 175 | A0042 (size 87): Received disconnect from <*> 11: <*> <*> <*> [preauth] 176 | A0047 (size 60): Received disconnect from <*> 11: disconnect [preauth] 177 | A0028 (size 30): Invalid user <*> <*> from <*> 178 | A0029 (size 30): input userauth request: invalid user <*> <*> [preauth] 179 | A0030 (size 30): Failed password for invalid user <*> <*> from <*> port <*> ssh2 180 | A0036 (size 13): Invalid user from <*> 181 | A0037 (size 13): input userauth request: invalid user [preauth] 182 | A0038 (size 13): Failed <*> for invalid user from <*> port <*> ssh2 183 | A0035 (size 8): Bad protocol version identification <*> <*> <*> from <*> port <*> 184 | A0034 (size 7): Bad protocol version identification <*> <*> from <*> port <*> 185 | A0039 (size 7): Bad protocol version identification <*> from <*> port <*> 186 | A0033 (size 6): fatal: no hostkey alg [preauth] 187 | A0040 (size 6): error: Received disconnect from <*> <*> <*> <*> <*> <*> [preauth] 188 | A0044 (size 6): error: connect to <*> port 22: failed. 189 | A0048 (size 6): Server listening on <*> port <*> 190 | A0023 (size 3): fatal: Write failed: Connection reset by peer [preauth] 191 | A0043 (size 3): error: Received disconnect from 195.154.45.62: 3: com.jcraft.jsch.JSchException: timeout in waiting for rekeying process. [preauth] 192 | A0049 (size 3): Received disconnect from <*> 11: Disconnect requested by Windows SSH Client. 193 | A0025 (size 2): error: Received disconnect from 191.96.249.68: 13: User request [preauth] 194 | A0046 (size 2): error: Received disconnect from 212.83.176.1: 3: org.vngx.jsch.userauth.AuthCancelException: User authentication canceled by user [preauth] 195 | A0026 (size 1): Bad packet length 1819045217. [preauth] 196 | A0027 (size 1): Disconnecting: Packet corrupt [preauth] 197 | A0041 (size 1): Received disconnect from 67.160.100.130: 11: 198 | A0045 (size 1): Corrupted MAC on input. [preauth] 199 | A0051 (size 1): syslogin perform logout: logout() returned an error 200 | 201 | Java implementation: 202 | ==================== 203 | 204 | ---- Done processing file. Total of 655147 lines, done in 1.588 s, 51 clusters 205 | 0010 (size 140768): Failed password for <*> from <*> port <*> ssh2 206 | 0009 (size 140701): pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*> <*> 207 | 0007 (size 68958): Connection closed by <*> [preauth] 208 | 0008 (size 46642): Received disconnect from <*> 11: <*> <*> <*> 209 | 0014 (size 37963): PAM service(sshd) ignoring max retries; <*> > 3 210 | 0012 (size 37298): Disconnecting: Too many authentication failures for <*> [preauth] 211 | 0013 (size 37029): PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*> <*> 212 | 0011 (size 36967): message repeated <*> times: [ Failed password for <*> from <*> port <*> ssh2] 213 | 0006 (size 20241): Failed <*> for invalid user <*> from <*> port <*> ssh2 214 | 0004 (size 19852): pam unix(sshd:auth): check pass; user unknown 215 | 0001 (size 18909): reverse mapping checking getaddrinfo for <*> <*> failed - POSSIBLE BREAK-IN ATTEMPT! 216 | 0002 (size 14551): Invalid user <*> from <*> 217 | 0003 (size 14551): input userauth request: invalid user <*> [preauth] 218 | 0005 (size 14356): pam unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= <*> 219 | 0018 (size 1289): PAM <*> more authentication <*> logname= uid=0 euid=0 tty=ssh ruser= <*> 220 | 0024 (size 952): fatal: Read from socket failed: Connection reset by peer [preauth] 221 | 0019 (size 930): error: Received disconnect from 103.99.0.122: 14: No more user authentication methods available. [preauth] 222 | 0015 (size 838): Did not receive identification string from <*> 223 | 0017 (size 592): Received disconnect from <*> 11: Closed due to user request. [preauth] 224 | 0031 (size 497): Address <*> maps to <*> but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT! 225 | 0020 (size 182): Accepted password for <*> from <*> port <*> ssh2 226 | 0021 (size 182): pam unix(sshd:session): session opened for user <*> by (uid=0) 227 | 0022 (size 182): pam unix(sshd:session): session closed for user <*> 228 | 0016 (size 177): error: Received disconnect from <*> 3: com.jcraft.jsch.JSchException: Auth <*> [preauth] 229 | 0032 (size 108): Received disconnect from <*> 11: [preauth] 230 | 0050 (size 92): Received disconnect from 139.59.209.18: 11: Normal Shutdown, Thank you for playing [preauth] 231 | 0042 (size 87): Received disconnect from <*> 11: <*> <*> <*> [preauth] 232 | 0047 (size 60): Received disconnect from <*> 11: disconnect [preauth] 233 | 0028 (size 30): Invalid user <*> <*> from <*> 234 | 0029 (size 30): input userauth request: invalid user <*> <*> [preauth] 235 | 0030 (size 30): Failed password for invalid user <*> <*> from <*> port <*> ssh2 236 | 0036 (size 13): Invalid user from <*> 237 | 0037 (size 13): input userauth request: invalid user [preauth] 238 | 0038 (size 13): Failed <*> for invalid user from <*> port <*> ssh2 239 | 0035 (size 8): Bad protocol version identification <*> <*> <*> from <*> port <*> 240 | 0034 (size 7): Bad protocol version identification <*> <*> from <*> port <*> 241 | 0039 (size 7): Bad protocol version identification <*> from <*> port <*> 242 | 0033 (size 6): fatal: no hostkey alg [preauth] 243 | 0040 (size 6): error: Received disconnect from <*> <*> <*> <*> <*> <*> [preauth] 244 | 0044 (size 6): error: connect to <*> port 22: failed. 245 | 0048 (size 6): Server listening on <*> port <*> 246 | 0023 (size 3): fatal: Write failed: Connection reset by peer [preauth] 247 | 0043 (size 3): error: Received disconnect from 195.154.45.62: 3: com.jcraft.jsch.JSchException: timeout in waiting for rekeying process. [preauth] 248 | 0049 (size 3): Received disconnect from <*> 11: Disconnect requested by Windows SSH Client. 249 | 0025 (size 2): error: Received disconnect from 191.96.249.68: 13: User request [preauth] 250 | 0046 (size 2): error: Received disconnect from 212.83.176.1: 3: org.vngx.jsch.userauth.AuthCancelException: User authentication canceled by user [preauth] 251 | 0026 (size 1): Bad packet length 1819045217. [preauth] 252 | 0027 (size 1): Disconnecting: Packet corrupt [preauth] 253 | 0041 (size 1): Received disconnect from 67.160.100.130: 11: 254 | 0045 (size 1): Corrupted MAC on input. [preauth] 255 | 0051 (size 1): syslogin perform logout: logout() returned an error 256 | */ 257 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 4 5 | indent_style = space 6 | insert_final_newline = false 7 | max_line_length = 120 8 | tab_width = 4 9 | ij_continuation_indent_size = 8 10 | ij_formatter_off_tag = @formatter:off 11 | ij_formatter_on_tag = @formatter:on 12 | ij_formatter_tags_enabled = true 13 | ij_smart_tabs = false 14 | ij_visual_guides = none 15 | ij_wrap_on_typing = false 16 | 17 | [*.css] 18 | ij_css_align_closing_brace_with_properties = false 19 | ij_css_blank_lines_around_nested_selector = 1 20 | ij_css_blank_lines_between_blocks = 1 21 | ij_css_block_comment_add_space = false 22 | ij_css_brace_placement = end_of_line 23 | ij_css_enforce_quotes_on_format = false 24 | ij_css_hex_color_long_format = false 25 | ij_css_hex_color_lower_case = false 26 | ij_css_hex_color_short_format = false 27 | ij_css_hex_color_upper_case = false 28 | ij_css_keep_blank_lines_in_code = 2 29 | ij_css_keep_indents_on_empty_lines = false 30 | ij_css_keep_single_line_blocks = false 31 | ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 32 | ij_css_space_after_colon = true 33 | ij_css_space_before_opening_brace = true 34 | ij_css_use_double_quotes = true 35 | ij_css_value_alignment = do_not_align 36 | 37 | [*.feature] 38 | indent_size = 2 39 | ij_gherkin_keep_indents_on_empty_lines = false 40 | 41 | [*.less] 42 | indent_size = 2 43 | ij_less_align_closing_brace_with_properties = false 44 | ij_less_blank_lines_around_nested_selector = 1 45 | ij_less_blank_lines_between_blocks = 1 46 | ij_less_block_comment_add_space = false 47 | ij_less_brace_placement = 0 48 | ij_less_enforce_quotes_on_format = false 49 | ij_less_hex_color_long_format = false 50 | ij_less_hex_color_lower_case = false 51 | ij_less_hex_color_short_format = false 52 | ij_less_hex_color_upper_case = false 53 | ij_less_keep_blank_lines_in_code = 2 54 | ij_less_keep_indents_on_empty_lines = false 55 | ij_less_keep_single_line_blocks = false 56 | ij_less_line_comment_add_space = false 57 | ij_less_line_comment_at_first_column = false 58 | ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 59 | ij_less_space_after_colon = true 60 | ij_less_space_before_opening_brace = true 61 | ij_less_use_double_quotes = true 62 | ij_less_value_alignment = 0 63 | 64 | [*.proto] 65 | indent_size = 2 66 | tab_width = 2 67 | ij_continuation_indent_size = 4 68 | ij_protobuf_keep_blank_lines_in_code = 2 69 | ij_protobuf_keep_indents_on_empty_lines = false 70 | ij_protobuf_keep_line_breaks = true 71 | ij_protobuf_space_after_comma = true 72 | ij_protobuf_space_before_comma = false 73 | ij_protobuf_spaces_around_assignment_operators = true 74 | ij_protobuf_spaces_within_braces = false 75 | ij_protobuf_spaces_within_brackets = false 76 | 77 | [*.rs] 78 | max_line_length = 100 79 | ij_continuation_indent_size = 4 80 | ij_rust_align_multiline_chained_methods = false 81 | ij_rust_align_multiline_parameters = true 82 | ij_rust_align_multiline_parameters_in_calls = true 83 | ij_rust_align_ret_type = true 84 | ij_rust_align_type_params = false 85 | ij_rust_align_where_bounds = true 86 | ij_rust_align_where_clause = false 87 | ij_rust_allow_one_line_match = false 88 | ij_rust_block_comment_at_first_column = false 89 | ij_rust_indent_where_clause = true 90 | ij_rust_keep_blank_lines_in_code = 2 91 | ij_rust_keep_blank_lines_in_declarations = 2 92 | ij_rust_keep_indents_on_empty_lines = false 93 | ij_rust_keep_line_breaks = true 94 | ij_rust_line_comment_add_space = true 95 | ij_rust_line_comment_at_first_column = false 96 | ij_rust_min_number_of_blanks_between_items = 1 97 | ij_rust_preserve_punctuation = false 98 | ij_rust_spaces_around_assoc_type_binding = false 99 | 100 | [*.sass] 101 | indent_size = 2 102 | ij_sass_align_closing_brace_with_properties = false 103 | ij_sass_blank_lines_around_nested_selector = 1 104 | ij_sass_blank_lines_between_blocks = 1 105 | ij_sass_brace_placement = 0 106 | ij_sass_enforce_quotes_on_format = false 107 | ij_sass_hex_color_long_format = false 108 | ij_sass_hex_color_lower_case = false 109 | ij_sass_hex_color_short_format = false 110 | ij_sass_hex_color_upper_case = false 111 | ij_sass_keep_blank_lines_in_code = 2 112 | ij_sass_keep_indents_on_empty_lines = false 113 | ij_sass_keep_single_line_blocks = false 114 | ij_sass_line_comment_add_space = false 115 | ij_sass_line_comment_at_first_column = false 116 | ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 117 | ij_sass_space_after_colon = true 118 | ij_sass_space_before_opening_brace = true 119 | ij_sass_use_double_quotes = true 120 | ij_sass_value_alignment = 0 121 | 122 | [*.scss] 123 | indent_size = 2 124 | ij_scss_align_closing_brace_with_properties = false 125 | ij_scss_blank_lines_around_nested_selector = 1 126 | ij_scss_blank_lines_between_blocks = 1 127 | ij_scss_block_comment_add_space = false 128 | ij_scss_brace_placement = 0 129 | ij_scss_enforce_quotes_on_format = false 130 | ij_scss_hex_color_long_format = false 131 | ij_scss_hex_color_lower_case = false 132 | ij_scss_hex_color_short_format = false 133 | ij_scss_hex_color_upper_case = false 134 | ij_scss_keep_blank_lines_in_code = 2 135 | ij_scss_keep_indents_on_empty_lines = false 136 | ij_scss_keep_single_line_blocks = false 137 | ij_scss_line_comment_add_space = false 138 | ij_scss_line_comment_at_first_column = false 139 | ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow 140 | ij_scss_space_after_colon = true 141 | ij_scss_space_before_opening_brace = true 142 | ij_scss_use_double_quotes = true 143 | ij_scss_value_alignment = 0 144 | 145 | [*.vue] 146 | indent_size = 2 147 | tab_width = 2 148 | ij_continuation_indent_size = 4 149 | ij_vue_indent_children_of_top_level = template 150 | ij_vue_interpolation_new_line_after_start_delimiter = true 151 | ij_vue_interpolation_new_line_before_end_delimiter = true 152 | ij_vue_interpolation_wrap = off 153 | ij_vue_keep_indents_on_empty_lines = false 154 | ij_vue_spaces_within_interpolation_expressions = true 155 | 156 | [.editorconfig] 157 | ij_editorconfig_align_group_field_declarations = false 158 | ij_editorconfig_space_after_colon = false 159 | ij_editorconfig_space_after_comma = true 160 | ij_editorconfig_space_before_colon = false 161 | ij_editorconfig_space_before_comma = false 162 | ij_editorconfig_spaces_around_assignment_operators = true 163 | 164 | [{*.ad,*.adoc,*.asciidoc,.asciidoctorconfig}] 165 | ij_asciidoc_blank_lines_after_header = 1 166 | ij_asciidoc_blank_lines_keep_after_header = 1 167 | ij_asciidoc_formatting_enabled = true 168 | ij_asciidoc_one_sentence_per_line = true 169 | 170 | [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] 171 | ij_xml_align_attributes = true 172 | ij_xml_align_text = false 173 | ij_xml_attribute_wrap = normal 174 | ij_xml_block_comment_add_space = false 175 | ij_xml_block_comment_at_first_column = false 176 | ij_xml_keep_blank_lines = 2 177 | ij_xml_keep_indents_on_empty_lines = false 178 | ij_xml_keep_line_breaks = true 179 | ij_xml_keep_line_breaks_in_text = true 180 | ij_xml_keep_whitespaces = false 181 | ij_xml_keep_whitespaces_around_cdata = preserve 182 | ij_xml_keep_whitespaces_inside_cdata = false 183 | ij_xml_line_comment_at_first_column = false 184 | ij_xml_space_after_tag_name = false 185 | ij_xml_space_around_equals_in_attribute = false 186 | ij_xml_space_inside_empty_tag = false 187 | ij_xml_text_wrap = normal 188 | 189 | [{*.ats,*.cts,*.mts,*.ts}] 190 | ij_continuation_indent_size = 4 191 | ij_typescript_align_imports = false 192 | ij_typescript_align_multiline_array_initializer_expression = false 193 | ij_typescript_align_multiline_binary_operation = false 194 | ij_typescript_align_multiline_chained_methods = false 195 | ij_typescript_align_multiline_extends_list = false 196 | ij_typescript_align_multiline_for = true 197 | ij_typescript_align_multiline_parameters = true 198 | ij_typescript_align_multiline_parameters_in_calls = false 199 | ij_typescript_align_multiline_ternary_operation = false 200 | ij_typescript_align_object_properties = 0 201 | ij_typescript_align_union_types = false 202 | ij_typescript_align_var_statements = 0 203 | ij_typescript_array_initializer_new_line_after_left_brace = false 204 | ij_typescript_array_initializer_right_brace_on_new_line = false 205 | ij_typescript_array_initializer_wrap = off 206 | ij_typescript_assignment_wrap = off 207 | ij_typescript_binary_operation_sign_on_next_line = false 208 | ij_typescript_binary_operation_wrap = off 209 | ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** 210 | ij_typescript_blank_lines_after_imports = 1 211 | ij_typescript_blank_lines_around_class = 1 212 | ij_typescript_blank_lines_around_field = 0 213 | ij_typescript_blank_lines_around_field_in_interface = 0 214 | ij_typescript_blank_lines_around_function = 1 215 | ij_typescript_blank_lines_around_method = 1 216 | ij_typescript_blank_lines_around_method_in_interface = 1 217 | ij_typescript_block_brace_style = end_of_line 218 | ij_typescript_block_comment_add_space = false 219 | ij_typescript_block_comment_at_first_column = true 220 | ij_typescript_call_parameters_new_line_after_left_paren = false 221 | ij_typescript_call_parameters_right_paren_on_new_line = false 222 | ij_typescript_call_parameters_wrap = off 223 | ij_typescript_catch_on_new_line = false 224 | ij_typescript_chained_call_dot_on_new_line = true 225 | ij_typescript_class_brace_style = end_of_line 226 | ij_typescript_comma_on_new_line = false 227 | ij_typescript_do_while_brace_force = never 228 | ij_typescript_else_on_new_line = false 229 | ij_typescript_enforce_trailing_comma = keep 230 | ij_typescript_enum_constants_wrap = on_every_item 231 | ij_typescript_extends_keyword_wrap = off 232 | ij_typescript_extends_list_wrap = off 233 | ij_typescript_field_prefix = _ 234 | ij_typescript_file_name_style = relaxed 235 | ij_typescript_finally_on_new_line = false 236 | ij_typescript_for_brace_force = never 237 | ij_typescript_for_statement_new_line_after_left_paren = false 238 | ij_typescript_for_statement_right_paren_on_new_line = false 239 | ij_typescript_for_statement_wrap = off 240 | ij_typescript_force_quote_style = false 241 | ij_typescript_force_semicolon_style = false 242 | ij_typescript_function_expression_brace_style = end_of_line 243 | ij_typescript_if_brace_force = never 244 | ij_typescript_import_merge_members = global 245 | ij_typescript_import_prefer_absolute_path = global 246 | ij_typescript_import_sort_members = true 247 | ij_typescript_import_sort_module_name = false 248 | ij_typescript_import_use_node_resolution = true 249 | ij_typescript_imports_wrap = on_every_item 250 | ij_typescript_indent_case_from_switch = true 251 | ij_typescript_indent_chained_calls = true 252 | ij_typescript_indent_package_children = 0 253 | ij_typescript_jsdoc_include_types = false 254 | ij_typescript_jsx_attribute_value = braces 255 | ij_typescript_keep_blank_lines_in_code = 2 256 | ij_typescript_keep_first_column_comment = true 257 | ij_typescript_keep_indents_on_empty_lines = false 258 | ij_typescript_keep_line_breaks = true 259 | ij_typescript_keep_simple_blocks_in_one_line = false 260 | ij_typescript_keep_simple_methods_in_one_line = false 261 | ij_typescript_line_comment_add_space = true 262 | ij_typescript_line_comment_at_first_column = false 263 | ij_typescript_method_brace_style = end_of_line 264 | ij_typescript_method_call_chain_wrap = off 265 | ij_typescript_method_parameters_new_line_after_left_paren = false 266 | ij_typescript_method_parameters_right_paren_on_new_line = false 267 | ij_typescript_method_parameters_wrap = off 268 | ij_typescript_object_literal_wrap = on_every_item 269 | ij_typescript_parentheses_expression_new_line_after_left_paren = false 270 | ij_typescript_parentheses_expression_right_paren_on_new_line = false 271 | ij_typescript_place_assignment_sign_on_next_line = false 272 | ij_typescript_prefer_as_type_cast = false 273 | ij_typescript_prefer_explicit_types_function_expression_returns = false 274 | ij_typescript_prefer_explicit_types_function_returns = false 275 | ij_typescript_prefer_explicit_types_vars_fields = false 276 | ij_typescript_prefer_parameters_wrap = false 277 | ij_typescript_reformat_c_style_comments = false 278 | ij_typescript_space_after_colon = true 279 | ij_typescript_space_after_comma = true 280 | ij_typescript_space_after_dots_in_rest_parameter = false 281 | ij_typescript_space_after_generator_mult = true 282 | ij_typescript_space_after_property_colon = true 283 | ij_typescript_space_after_quest = true 284 | ij_typescript_space_after_type_colon = true 285 | ij_typescript_space_after_unary_not = false 286 | ij_typescript_space_before_async_arrow_lparen = true 287 | ij_typescript_space_before_catch_keyword = true 288 | ij_typescript_space_before_catch_left_brace = true 289 | ij_typescript_space_before_catch_parentheses = true 290 | ij_typescript_space_before_class_lbrace = true 291 | ij_typescript_space_before_class_left_brace = true 292 | ij_typescript_space_before_colon = true 293 | ij_typescript_space_before_comma = false 294 | ij_typescript_space_before_do_left_brace = true 295 | ij_typescript_space_before_else_keyword = true 296 | ij_typescript_space_before_else_left_brace = true 297 | ij_typescript_space_before_finally_keyword = true 298 | ij_typescript_space_before_finally_left_brace = true 299 | ij_typescript_space_before_for_left_brace = true 300 | ij_typescript_space_before_for_parentheses = true 301 | ij_typescript_space_before_for_semicolon = false 302 | ij_typescript_space_before_function_left_parenth = true 303 | ij_typescript_space_before_generator_mult = false 304 | ij_typescript_space_before_if_left_brace = true 305 | ij_typescript_space_before_if_parentheses = true 306 | ij_typescript_space_before_method_call_parentheses = false 307 | ij_typescript_space_before_method_left_brace = true 308 | ij_typescript_space_before_method_parentheses = false 309 | ij_typescript_space_before_property_colon = false 310 | ij_typescript_space_before_quest = true 311 | ij_typescript_space_before_switch_left_brace = true 312 | ij_typescript_space_before_switch_parentheses = true 313 | ij_typescript_space_before_try_left_brace = true 314 | ij_typescript_space_before_type_colon = false 315 | ij_typescript_space_before_unary_not = false 316 | ij_typescript_space_before_while_keyword = true 317 | ij_typescript_space_before_while_left_brace = true 318 | ij_typescript_space_before_while_parentheses = true 319 | ij_typescript_spaces_around_additive_operators = true 320 | ij_typescript_spaces_around_arrow_function_operator = true 321 | ij_typescript_spaces_around_assignment_operators = true 322 | ij_typescript_spaces_around_bitwise_operators = true 323 | ij_typescript_spaces_around_equality_operators = true 324 | ij_typescript_spaces_around_logical_operators = true 325 | ij_typescript_spaces_around_multiplicative_operators = true 326 | ij_typescript_spaces_around_relational_operators = true 327 | ij_typescript_spaces_around_shift_operators = true 328 | ij_typescript_spaces_around_unary_operator = false 329 | ij_typescript_spaces_within_array_initializer_brackets = false 330 | ij_typescript_spaces_within_brackets = false 331 | ij_typescript_spaces_within_catch_parentheses = false 332 | ij_typescript_spaces_within_for_parentheses = false 333 | ij_typescript_spaces_within_if_parentheses = false 334 | ij_typescript_spaces_within_imports = false 335 | ij_typescript_spaces_within_interpolation_expressions = false 336 | ij_typescript_spaces_within_method_call_parentheses = false 337 | ij_typescript_spaces_within_method_parentheses = false 338 | ij_typescript_spaces_within_object_literal_braces = false 339 | ij_typescript_spaces_within_object_type_braces = true 340 | ij_typescript_spaces_within_parentheses = false 341 | ij_typescript_spaces_within_switch_parentheses = false 342 | ij_typescript_spaces_within_type_assertion = false 343 | ij_typescript_spaces_within_union_types = true 344 | ij_typescript_spaces_within_while_parentheses = false 345 | ij_typescript_special_else_if_treatment = true 346 | ij_typescript_ternary_operation_signs_on_next_line = false 347 | ij_typescript_ternary_operation_wrap = off 348 | ij_typescript_union_types_wrap = on_every_item 349 | ij_typescript_use_chained_calls_group_indents = false 350 | ij_typescript_use_double_quotes = true 351 | ij_typescript_use_explicit_js_extension = auto 352 | ij_typescript_use_path_mapping = always 353 | ij_typescript_use_public_modifier = false 354 | ij_typescript_use_semicolon_after_statement = true 355 | ij_typescript_var_declaration_wrap = normal 356 | ij_typescript_while_brace_force = never 357 | ij_typescript_while_on_new_line = false 358 | ij_typescript_wrap_comments = false 359 | 360 | [{*.bash,*.sh,*.zsh}] 361 | indent_size = 2 362 | tab_width = 2 363 | ij_shell_binary_ops_start_line = false 364 | ij_shell_keep_column_alignment_padding = false 365 | ij_shell_minify_program = false 366 | ij_shell_redirect_followed_by_space = false 367 | ij_shell_switch_cases_indented = false 368 | ij_shell_use_unix_line_separator = true 369 | 370 | [{*.cjs,*.js}] 371 | ij_continuation_indent_size = 4 372 | ij_javascript_align_imports = false 373 | ij_javascript_align_multiline_array_initializer_expression = false 374 | ij_javascript_align_multiline_binary_operation = false 375 | ij_javascript_align_multiline_chained_methods = false 376 | ij_javascript_align_multiline_extends_list = false 377 | ij_javascript_align_multiline_for = true 378 | ij_javascript_align_multiline_parameters = true 379 | ij_javascript_align_multiline_parameters_in_calls = false 380 | ij_javascript_align_multiline_ternary_operation = false 381 | ij_javascript_align_object_properties = 0 382 | ij_javascript_align_union_types = false 383 | ij_javascript_align_var_statements = 0 384 | ij_javascript_array_initializer_new_line_after_left_brace = false 385 | ij_javascript_array_initializer_right_brace_on_new_line = false 386 | ij_javascript_array_initializer_wrap = off 387 | ij_javascript_assignment_wrap = off 388 | ij_javascript_binary_operation_sign_on_next_line = false 389 | ij_javascript_binary_operation_wrap = off 390 | ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** 391 | ij_javascript_blank_lines_after_imports = 1 392 | ij_javascript_blank_lines_around_class = 1 393 | ij_javascript_blank_lines_around_field = 0 394 | ij_javascript_blank_lines_around_function = 1 395 | ij_javascript_blank_lines_around_method = 1 396 | ij_javascript_block_brace_style = end_of_line 397 | ij_javascript_block_comment_add_space = false 398 | ij_javascript_block_comment_at_first_column = true 399 | ij_javascript_call_parameters_new_line_after_left_paren = false 400 | ij_javascript_call_parameters_right_paren_on_new_line = false 401 | ij_javascript_call_parameters_wrap = off 402 | ij_javascript_catch_on_new_line = false 403 | ij_javascript_chained_call_dot_on_new_line = true 404 | ij_javascript_class_brace_style = end_of_line 405 | ij_javascript_comma_on_new_line = false 406 | ij_javascript_do_while_brace_force = never 407 | ij_javascript_else_on_new_line = false 408 | ij_javascript_enforce_trailing_comma = keep 409 | ij_javascript_extends_keyword_wrap = off 410 | ij_javascript_extends_list_wrap = off 411 | ij_javascript_field_prefix = _ 412 | ij_javascript_file_name_style = relaxed 413 | ij_javascript_finally_on_new_line = false 414 | ij_javascript_for_brace_force = never 415 | ij_javascript_for_statement_new_line_after_left_paren = false 416 | ij_javascript_for_statement_right_paren_on_new_line = false 417 | ij_javascript_for_statement_wrap = off 418 | ij_javascript_force_quote_style = false 419 | ij_javascript_force_semicolon_style = false 420 | ij_javascript_function_expression_brace_style = end_of_line 421 | ij_javascript_if_brace_force = never 422 | ij_javascript_import_merge_members = global 423 | ij_javascript_import_prefer_absolute_path = global 424 | ij_javascript_import_sort_members = true 425 | ij_javascript_import_sort_module_name = false 426 | ij_javascript_import_use_node_resolution = true 427 | ij_javascript_imports_wrap = on_every_item 428 | ij_javascript_indent_case_from_switch = true 429 | ij_javascript_indent_chained_calls = true 430 | ij_javascript_indent_package_children = 0 431 | ij_javascript_jsx_attribute_value = braces 432 | ij_javascript_keep_blank_lines_in_code = 2 433 | ij_javascript_keep_first_column_comment = false 434 | ij_javascript_keep_indents_on_empty_lines = false 435 | ij_javascript_keep_line_breaks = true 436 | ij_javascript_keep_simple_blocks_in_one_line = false 437 | ij_javascript_keep_simple_methods_in_one_line = false 438 | ij_javascript_line_comment_add_space = true 439 | ij_javascript_line_comment_at_first_column = false 440 | ij_javascript_method_brace_style = end_of_line 441 | ij_javascript_method_call_chain_wrap = off 442 | ij_javascript_method_parameters_new_line_after_left_paren = false 443 | ij_javascript_method_parameters_right_paren_on_new_line = false 444 | ij_javascript_method_parameters_wrap = off 445 | ij_javascript_object_literal_wrap = on_every_item 446 | ij_javascript_parentheses_expression_new_line_after_left_paren = false 447 | ij_javascript_parentheses_expression_right_paren_on_new_line = false 448 | ij_javascript_place_assignment_sign_on_next_line = false 449 | ij_javascript_prefer_as_type_cast = false 450 | ij_javascript_prefer_explicit_types_function_expression_returns = false 451 | ij_javascript_prefer_explicit_types_function_returns = false 452 | ij_javascript_prefer_explicit_types_vars_fields = false 453 | ij_javascript_prefer_parameters_wrap = false 454 | ij_javascript_reformat_c_style_comments = false 455 | ij_javascript_space_after_colon = true 456 | ij_javascript_space_after_comma = true 457 | ij_javascript_space_after_dots_in_rest_parameter = false 458 | ij_javascript_space_after_generator_mult = true 459 | ij_javascript_space_after_property_colon = true 460 | ij_javascript_space_after_quest = true 461 | ij_javascript_space_after_type_colon = true 462 | ij_javascript_space_after_unary_not = false 463 | ij_javascript_space_before_async_arrow_lparen = true 464 | ij_javascript_space_before_catch_keyword = true 465 | ij_javascript_space_before_catch_left_brace = true 466 | ij_javascript_space_before_catch_parentheses = true 467 | ij_javascript_space_before_class_lbrace = true 468 | ij_javascript_space_before_class_left_brace = true 469 | ij_javascript_space_before_colon = true 470 | ij_javascript_space_before_comma = false 471 | ij_javascript_space_before_do_left_brace = true 472 | ij_javascript_space_before_else_keyword = true 473 | ij_javascript_space_before_else_left_brace = true 474 | ij_javascript_space_before_finally_keyword = true 475 | ij_javascript_space_before_finally_left_brace = true 476 | ij_javascript_space_before_for_left_brace = true 477 | ij_javascript_space_before_for_parentheses = true 478 | ij_javascript_space_before_for_semicolon = false 479 | ij_javascript_space_before_function_left_parenth = true 480 | ij_javascript_space_before_generator_mult = false 481 | ij_javascript_space_before_if_left_brace = true 482 | ij_javascript_space_before_if_parentheses = true 483 | ij_javascript_space_before_method_call_parentheses = false 484 | ij_javascript_space_before_method_left_brace = true 485 | ij_javascript_space_before_method_parentheses = false 486 | ij_javascript_space_before_property_colon = false 487 | ij_javascript_space_before_quest = true 488 | ij_javascript_space_before_switch_left_brace = true 489 | ij_javascript_space_before_switch_parentheses = true 490 | ij_javascript_space_before_try_left_brace = true 491 | ij_javascript_space_before_type_colon = false 492 | ij_javascript_space_before_unary_not = false 493 | ij_javascript_space_before_while_keyword = true 494 | ij_javascript_space_before_while_left_brace = true 495 | ij_javascript_space_before_while_parentheses = true 496 | ij_javascript_spaces_around_additive_operators = true 497 | ij_javascript_spaces_around_arrow_function_operator = true 498 | ij_javascript_spaces_around_assignment_operators = true 499 | ij_javascript_spaces_around_bitwise_operators = true 500 | ij_javascript_spaces_around_equality_operators = true 501 | ij_javascript_spaces_around_logical_operators = true 502 | ij_javascript_spaces_around_multiplicative_operators = true 503 | ij_javascript_spaces_around_relational_operators = true 504 | ij_javascript_spaces_around_shift_operators = true 505 | ij_javascript_spaces_around_unary_operator = false 506 | ij_javascript_spaces_within_array_initializer_brackets = false 507 | ij_javascript_spaces_within_brackets = false 508 | ij_javascript_spaces_within_catch_parentheses = false 509 | ij_javascript_spaces_within_for_parentheses = false 510 | ij_javascript_spaces_within_if_parentheses = false 511 | ij_javascript_spaces_within_imports = false 512 | ij_javascript_spaces_within_interpolation_expressions = false 513 | ij_javascript_spaces_within_method_call_parentheses = false 514 | ij_javascript_spaces_within_method_parentheses = false 515 | ij_javascript_spaces_within_object_literal_braces = false 516 | ij_javascript_spaces_within_object_type_braces = true 517 | ij_javascript_spaces_within_parentheses = false 518 | ij_javascript_spaces_within_switch_parentheses = false 519 | ij_javascript_spaces_within_type_assertion = false 520 | ij_javascript_spaces_within_union_types = true 521 | ij_javascript_spaces_within_while_parentheses = false 522 | ij_javascript_special_else_if_treatment = true 523 | ij_javascript_ternary_operation_signs_on_next_line = false 524 | ij_javascript_ternary_operation_wrap = off 525 | ij_javascript_union_types_wrap = on_every_item 526 | ij_javascript_use_chained_calls_group_indents = false 527 | ij_javascript_use_double_quotes = true 528 | ij_javascript_use_explicit_js_extension = auto 529 | ij_javascript_use_path_mapping = always 530 | ij_javascript_use_public_modifier = false 531 | ij_javascript_use_semicolon_after_statement = true 532 | ij_javascript_var_declaration_wrap = normal 533 | ij_javascript_while_brace_force = never 534 | ij_javascript_while_on_new_line = false 535 | ij_javascript_wrap_comments = false 536 | 537 | [{*.erb,*.rhtml}] 538 | indent_size = 2 539 | tab_width = 2 540 | ij_continuation_indent_size = 2 541 | ij_rhtml_keep_indents_on_empty_lines = false 542 | 543 | [{*.ft,*.vm,*.vsl}] 544 | ij_vtl_keep_indents_on_empty_lines = false 545 | 546 | [{*.gant,*.groovy,*.gy}] 547 | ij_groovy_align_group_field_declarations = false 548 | ij_groovy_align_multiline_array_initializer_expression = false 549 | ij_groovy_align_multiline_assignment = false 550 | ij_groovy_align_multiline_binary_operation = false 551 | ij_groovy_align_multiline_chained_methods = false 552 | ij_groovy_align_multiline_extends_list = false 553 | ij_groovy_align_multiline_for = true 554 | ij_groovy_align_multiline_list_or_map = true 555 | ij_groovy_align_multiline_method_parentheses = false 556 | ij_groovy_align_multiline_parameters = true 557 | ij_groovy_align_multiline_parameters_in_calls = false 558 | ij_groovy_align_multiline_resources = true 559 | ij_groovy_align_multiline_ternary_operation = false 560 | ij_groovy_align_multiline_throws_list = false 561 | ij_groovy_align_named_args_in_map = true 562 | ij_groovy_align_throws_keyword = false 563 | ij_groovy_array_initializer_new_line_after_left_brace = false 564 | ij_groovy_array_initializer_right_brace_on_new_line = false 565 | ij_groovy_array_initializer_wrap = off 566 | ij_groovy_assert_statement_wrap = off 567 | ij_groovy_assignment_wrap = off 568 | ij_groovy_binary_operation_wrap = off 569 | ij_groovy_blank_lines_after_class_header = 0 570 | ij_groovy_blank_lines_after_imports = 1 571 | ij_groovy_blank_lines_after_package = 1 572 | ij_groovy_blank_lines_around_class = 1 573 | ij_groovy_blank_lines_around_field = 0 574 | ij_groovy_blank_lines_around_field_in_interface = 0 575 | ij_groovy_blank_lines_around_method = 1 576 | ij_groovy_blank_lines_around_method_in_interface = 1 577 | ij_groovy_blank_lines_before_imports = 1 578 | ij_groovy_blank_lines_before_method_body = 0 579 | ij_groovy_blank_lines_before_package = 0 580 | ij_groovy_block_brace_style = end_of_line 581 | ij_groovy_block_comment_add_space = false 582 | ij_groovy_block_comment_at_first_column = true 583 | ij_groovy_call_parameters_new_line_after_left_paren = false 584 | ij_groovy_call_parameters_right_paren_on_new_line = false 585 | ij_groovy_call_parameters_wrap = off 586 | ij_groovy_catch_on_new_line = false 587 | ij_groovy_class_annotation_wrap = split_into_lines 588 | ij_groovy_class_brace_style = end_of_line 589 | ij_groovy_class_count_to_use_import_on_demand = 5 590 | ij_groovy_do_while_brace_force = never 591 | ij_groovy_else_on_new_line = false 592 | ij_groovy_enable_groovydoc_formatting = true 593 | ij_groovy_enum_constants_wrap = off 594 | ij_groovy_extends_keyword_wrap = off 595 | ij_groovy_extends_list_wrap = off 596 | ij_groovy_field_annotation_wrap = split_into_lines 597 | ij_groovy_finally_on_new_line = false 598 | ij_groovy_for_brace_force = never 599 | ij_groovy_for_statement_new_line_after_left_paren = false 600 | ij_groovy_for_statement_right_paren_on_new_line = false 601 | ij_groovy_for_statement_wrap = off 602 | ij_groovy_ginq_general_clause_wrap_policy = 2 603 | ij_groovy_ginq_having_wrap_policy = 1 604 | ij_groovy_ginq_indent_having_clause = true 605 | ij_groovy_ginq_indent_on_clause = true 606 | ij_groovy_ginq_on_wrap_policy = 1 607 | ij_groovy_ginq_space_after_keyword = true 608 | ij_groovy_if_brace_force = never 609 | ij_groovy_import_annotation_wrap = 2 610 | ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* 611 | ij_groovy_indent_case_from_switch = true 612 | ij_groovy_indent_label_blocks = true 613 | ij_groovy_insert_inner_class_imports = false 614 | ij_groovy_keep_blank_lines_before_right_brace = 2 615 | ij_groovy_keep_blank_lines_in_code = 2 616 | ij_groovy_keep_blank_lines_in_declarations = 2 617 | ij_groovy_keep_control_statement_in_one_line = true 618 | ij_groovy_keep_first_column_comment = false 619 | ij_groovy_keep_indents_on_empty_lines = false 620 | ij_groovy_keep_line_breaks = true 621 | ij_groovy_keep_multiple_expressions_in_one_line = false 622 | ij_groovy_keep_simple_blocks_in_one_line = false 623 | ij_groovy_keep_simple_classes_in_one_line = true 624 | ij_groovy_keep_simple_lambdas_in_one_line = true 625 | ij_groovy_keep_simple_methods_in_one_line = true 626 | ij_groovy_label_indent_absolute = false 627 | ij_groovy_label_indent_size = 0 628 | ij_groovy_lambda_brace_style = end_of_line 629 | ij_groovy_layout_static_imports_separately = true 630 | ij_groovy_line_comment_add_space = false 631 | ij_groovy_line_comment_add_space_on_reformat = false 632 | ij_groovy_line_comment_at_first_column = true 633 | ij_groovy_method_annotation_wrap = split_into_lines 634 | ij_groovy_method_brace_style = end_of_line 635 | ij_groovy_method_call_chain_wrap = off 636 | ij_groovy_method_parameters_new_line_after_left_paren = false 637 | ij_groovy_method_parameters_right_paren_on_new_line = false 638 | ij_groovy_method_parameters_wrap = off 639 | ij_groovy_modifier_list_wrap = false 640 | ij_groovy_names_count_to_use_import_on_demand = 3 641 | ij_groovy_packages_to_use_import_on_demand = java.awt.*,javax.swing.* 642 | ij_groovy_parameter_annotation_wrap = off 643 | ij_groovy_parentheses_expression_new_line_after_left_paren = false 644 | ij_groovy_parentheses_expression_right_paren_on_new_line = false 645 | ij_groovy_prefer_parameters_wrap = false 646 | ij_groovy_resource_list_new_line_after_left_paren = false 647 | ij_groovy_resource_list_right_paren_on_new_line = false 648 | ij_groovy_resource_list_wrap = off 649 | ij_groovy_space_after_assert_separator = true 650 | ij_groovy_space_after_colon = true 651 | ij_groovy_space_after_comma = true 652 | ij_groovy_space_after_comma_in_type_arguments = true 653 | ij_groovy_space_after_for_semicolon = true 654 | ij_groovy_space_after_quest = true 655 | ij_groovy_space_after_type_cast = true 656 | ij_groovy_space_before_annotation_parameter_list = false 657 | ij_groovy_space_before_array_initializer_left_brace = false 658 | ij_groovy_space_before_assert_separator = false 659 | ij_groovy_space_before_catch_keyword = true 660 | ij_groovy_space_before_catch_left_brace = true 661 | ij_groovy_space_before_catch_parentheses = true 662 | ij_groovy_space_before_class_left_brace = true 663 | ij_groovy_space_before_closure_left_brace = true 664 | ij_groovy_space_before_colon = true 665 | ij_groovy_space_before_comma = false 666 | ij_groovy_space_before_do_left_brace = true 667 | ij_groovy_space_before_else_keyword = true 668 | ij_groovy_space_before_else_left_brace = true 669 | ij_groovy_space_before_finally_keyword = true 670 | ij_groovy_space_before_finally_left_brace = true 671 | ij_groovy_space_before_for_left_brace = true 672 | ij_groovy_space_before_for_parentheses = true 673 | ij_groovy_space_before_for_semicolon = false 674 | ij_groovy_space_before_if_left_brace = true 675 | ij_groovy_space_before_if_parentheses = true 676 | ij_groovy_space_before_method_call_parentheses = false 677 | ij_groovy_space_before_method_left_brace = true 678 | ij_groovy_space_before_method_parentheses = false 679 | ij_groovy_space_before_quest = true 680 | ij_groovy_space_before_record_parentheses = false 681 | ij_groovy_space_before_switch_left_brace = true 682 | ij_groovy_space_before_switch_parentheses = true 683 | ij_groovy_space_before_synchronized_left_brace = true 684 | ij_groovy_space_before_synchronized_parentheses = true 685 | ij_groovy_space_before_try_left_brace = true 686 | ij_groovy_space_before_try_parentheses = true 687 | ij_groovy_space_before_while_keyword = true 688 | ij_groovy_space_before_while_left_brace = true 689 | ij_groovy_space_before_while_parentheses = true 690 | ij_groovy_space_in_named_argument = true 691 | ij_groovy_space_in_named_argument_before_colon = false 692 | ij_groovy_space_within_empty_array_initializer_braces = false 693 | ij_groovy_space_within_empty_method_call_parentheses = false 694 | ij_groovy_spaces_around_additive_operators = true 695 | ij_groovy_spaces_around_assignment_operators = true 696 | ij_groovy_spaces_around_bitwise_operators = true 697 | ij_groovy_spaces_around_equality_operators = true 698 | ij_groovy_spaces_around_lambda_arrow = true 699 | ij_groovy_spaces_around_logical_operators = true 700 | ij_groovy_spaces_around_multiplicative_operators = true 701 | ij_groovy_spaces_around_regex_operators = true 702 | ij_groovy_spaces_around_relational_operators = true 703 | ij_groovy_spaces_around_shift_operators = true 704 | ij_groovy_spaces_within_annotation_parentheses = false 705 | ij_groovy_spaces_within_array_initializer_braces = false 706 | ij_groovy_spaces_within_braces = true 707 | ij_groovy_spaces_within_brackets = false 708 | ij_groovy_spaces_within_cast_parentheses = false 709 | ij_groovy_spaces_within_catch_parentheses = false 710 | ij_groovy_spaces_within_for_parentheses = false 711 | ij_groovy_spaces_within_gstring_injection_braces = false 712 | ij_groovy_spaces_within_if_parentheses = false 713 | ij_groovy_spaces_within_list_or_map = false 714 | ij_groovy_spaces_within_method_call_parentheses = false 715 | ij_groovy_spaces_within_method_parentheses = false 716 | ij_groovy_spaces_within_parentheses = false 717 | ij_groovy_spaces_within_switch_parentheses = false 718 | ij_groovy_spaces_within_synchronized_parentheses = false 719 | ij_groovy_spaces_within_try_parentheses = false 720 | ij_groovy_spaces_within_tuple_expression = false 721 | ij_groovy_spaces_within_while_parentheses = false 722 | ij_groovy_special_else_if_treatment = true 723 | ij_groovy_ternary_operation_wrap = off 724 | ij_groovy_throws_keyword_wrap = off 725 | ij_groovy_throws_list_wrap = off 726 | ij_groovy_use_flying_geese_braces = false 727 | ij_groovy_use_fq_class_names = false 728 | ij_groovy_use_fq_class_names_in_javadoc = true 729 | ij_groovy_use_relative_indents = false 730 | ij_groovy_use_single_class_imports = true 731 | ij_groovy_variable_annotation_wrap = off 732 | ij_groovy_while_brace_force = never 733 | ij_groovy_while_on_new_line = false 734 | ij_groovy_wrap_chain_calls_after_dot = false 735 | ij_groovy_wrap_long_lines = false 736 | 737 | [{*.gemspec,*.jbuilder,*.rake,*.rb,*.rbi,*.rbw,*.ru,*.thor,.simplecov,capfile,gemfile,guardfile,isolate,rakefile,steepfile,vagrantfile}] 738 | ij_ruby_align_group_field_declarations = false 739 | ij_ruby_align_multiline_parameters = true 740 | ij_ruby_blank_lines_around_class = 1 741 | ij_ruby_blank_lines_around_method = 1 742 | ij_ruby_chain_calls_alignment = 2 743 | ij_ruby_convert_brace_block_by_enter = true 744 | ij_ruby_empty_declarations_style = 1 745 | ij_ruby_force_newlines_around_visibility_mods = true 746 | ij_ruby_indent_private_methods = false 747 | ij_ruby_indent_protected_methods = false 748 | ij_ruby_indent_public_methods = false 749 | ij_ruby_indent_visibility_modifiers = true 750 | ij_ruby_indent_when_cases = false 751 | ij_ruby_keep_blank_lines_in_code = 1 752 | ij_ruby_keep_blank_lines_in_declarations = 1 753 | ij_ruby_keep_line_breaks = true 754 | ij_ruby_parentheses_around_method_arguments = true 755 | ij_ruby_spaces_around_assignment_operators = true 756 | ij_ruby_spaces_around_hashrocket = true 757 | ij_ruby_spaces_around_other_operators = true 758 | ij_ruby_spaces_around_range_operators = false 759 | ij_ruby_spaces_around_relational_operators = true 760 | ij_ruby_spaces_within_array_initializer_braces = true 761 | ij_ruby_spaces_within_braces = true 762 | ij_ruby_spaces_within_pipes = false 763 | ij_ruby_use_external_formatter = false 764 | 765 | [{*.gradle.kts,*.kt,*.kts,*.main.kts,*.space.kts}] 766 | ij_kotlin_align_in_columns_case_branch = false 767 | ij_kotlin_align_multiline_binary_operation = false 768 | ij_kotlin_align_multiline_extends_list = false 769 | ij_kotlin_align_multiline_method_parentheses = false 770 | ij_kotlin_align_multiline_parameters = true 771 | ij_kotlin_align_multiline_parameters_in_calls = false 772 | ij_kotlin_allow_trailing_comma = false 773 | ij_kotlin_allow_trailing_comma_on_call_site = false 774 | ij_kotlin_assignment_wrap = normal 775 | ij_kotlin_blank_lines_after_class_header = 0 776 | ij_kotlin_blank_lines_around_block_when_branches = 0 777 | ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 778 | ij_kotlin_block_comment_add_space = false 779 | ij_kotlin_block_comment_at_first_column = false 780 | ij_kotlin_call_parameters_new_line_after_left_paren = true 781 | ij_kotlin_call_parameters_right_paren_on_new_line = true 782 | ij_kotlin_call_parameters_wrap = on_every_item 783 | ij_kotlin_catch_on_new_line = false 784 | ij_kotlin_class_annotation_wrap = split_into_lines 785 | ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL 786 | ij_kotlin_continuation_indent_for_chained_calls = false 787 | ij_kotlin_continuation_indent_for_expression_bodies = false 788 | ij_kotlin_continuation_indent_in_argument_lists = false 789 | ij_kotlin_continuation_indent_in_elvis = false 790 | ij_kotlin_continuation_indent_in_if_conditions = false 791 | ij_kotlin_continuation_indent_in_parameter_lists = false 792 | ij_kotlin_continuation_indent_in_supertype_lists = false 793 | ij_kotlin_else_on_new_line = false 794 | ij_kotlin_enum_constants_wrap = off 795 | ij_kotlin_extends_list_wrap = normal 796 | ij_kotlin_field_annotation_wrap = split_into_lines 797 | ij_kotlin_finally_on_new_line = false 798 | ij_kotlin_if_rparen_on_new_line = true 799 | ij_kotlin_import_nested_classes = false 800 | ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ 801 | ij_kotlin_insert_whitespaces_in_simple_one_line_method = true 802 | ij_kotlin_keep_blank_lines_before_right_brace = 2 803 | ij_kotlin_keep_blank_lines_in_code = 2 804 | ij_kotlin_keep_blank_lines_in_declarations = 2 805 | ij_kotlin_keep_first_column_comment = false 806 | ij_kotlin_keep_indents_on_empty_lines = false 807 | ij_kotlin_keep_line_breaks = true 808 | ij_kotlin_lbrace_on_next_line = false 809 | ij_kotlin_line_break_after_multiline_when_entry = true 810 | ij_kotlin_line_comment_add_space = true 811 | ij_kotlin_line_comment_add_space_on_reformat = true 812 | ij_kotlin_line_comment_at_first_column = false 813 | ij_kotlin_method_annotation_wrap = split_into_lines 814 | ij_kotlin_method_call_chain_wrap = normal 815 | ij_kotlin_method_parameters_new_line_after_left_paren = true 816 | ij_kotlin_method_parameters_right_paren_on_new_line = true 817 | ij_kotlin_method_parameters_wrap = on_every_item 818 | ij_kotlin_name_count_to_use_star_import = 5 819 | ij_kotlin_name_count_to_use_star_import_for_members = 3 820 | ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** 821 | ij_kotlin_parameter_annotation_wrap = off 822 | ij_kotlin_space_after_comma = true 823 | ij_kotlin_space_after_extend_colon = true 824 | ij_kotlin_space_after_type_colon = true 825 | ij_kotlin_space_before_catch_parentheses = true 826 | ij_kotlin_space_before_comma = false 827 | ij_kotlin_space_before_extend_colon = true 828 | ij_kotlin_space_before_for_parentheses = true 829 | ij_kotlin_space_before_if_parentheses = true 830 | ij_kotlin_space_before_lambda_arrow = true 831 | ij_kotlin_space_before_type_colon = false 832 | ij_kotlin_space_before_when_parentheses = true 833 | ij_kotlin_space_before_while_parentheses = true 834 | ij_kotlin_spaces_around_additive_operators = true 835 | ij_kotlin_spaces_around_assignment_operators = true 836 | ij_kotlin_spaces_around_equality_operators = true 837 | ij_kotlin_spaces_around_function_type_arrow = true 838 | ij_kotlin_spaces_around_logical_operators = true 839 | ij_kotlin_spaces_around_multiplicative_operators = true 840 | ij_kotlin_spaces_around_range = false 841 | ij_kotlin_spaces_around_relational_operators = true 842 | ij_kotlin_spaces_around_unary_operator = false 843 | ij_kotlin_spaces_around_when_arrow = true 844 | ij_kotlin_variable_annotation_wrap = off 845 | ij_kotlin_while_on_new_line = false 846 | ij_kotlin_wrap_elvis_expressions = 1 847 | ij_kotlin_wrap_expression_body_functions = 1 848 | ij_kotlin_wrap_first_method_in_call_chain = false 849 | 850 | [{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}] 851 | indent_size = 2 852 | ij_json_array_wrapping = split_into_lines 853 | ij_json_keep_blank_lines_in_code = 0 854 | ij_json_keep_indents_on_empty_lines = false 855 | ij_json_keep_line_breaks = true 856 | ij_json_keep_trailing_comma = false 857 | ij_json_object_wrapping = split_into_lines 858 | ij_json_property_alignment = do_not_align 859 | ij_json_space_after_colon = true 860 | ij_json_space_after_comma = true 861 | ij_json_space_before_colon = false 862 | ij_json_space_before_comma = false 863 | ij_json_spaces_within_braces = false 864 | ij_json_spaces_within_brackets = false 865 | ij_json_wrap_long_lines = false 866 | 867 | [{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] 868 | ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 869 | ij_html_align_attributes = true 870 | ij_html_align_text = false 871 | ij_html_attribute_wrap = normal 872 | ij_html_block_comment_add_space = false 873 | ij_html_block_comment_at_first_column = false 874 | ij_html_do_not_align_children_of_min_lines = 0 875 | ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p 876 | ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot 877 | ij_html_enforce_quotes = false 878 | ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var 879 | ij_html_keep_blank_lines = 2 880 | ij_html_keep_indents_on_empty_lines = false 881 | ij_html_keep_line_breaks = true 882 | ij_html_keep_line_breaks_in_text = true 883 | ij_html_keep_whitespaces = false 884 | ij_html_keep_whitespaces_inside = span,pre,textarea 885 | ij_html_line_comment_at_first_column = false 886 | ij_html_new_line_after_last_attribute = never 887 | ij_html_new_line_before_first_attribute = never 888 | ij_html_quote_style = double 889 | ij_html_remove_new_line_before_tags = br 890 | ij_html_space_after_tag_name = false 891 | ij_html_space_around_equality_in_attribute = false 892 | ij_html_space_inside_empty_tag = false 893 | ij_html_text_wrap = normal 894 | 895 | [{*.http,*.rest}] 896 | indent_size = 0 897 | ij_continuation_indent_size = 4 898 | ij_http-request_call_parameters_wrap = normal 899 | 900 | [{*.java,*.jbang}] 901 | ij_java_align_consecutive_assignments = false 902 | ij_java_align_consecutive_variable_declarations = false 903 | ij_java_align_group_field_declarations = false 904 | ij_java_align_multiline_annotation_parameters = false 905 | ij_java_align_multiline_array_initializer_expression = false 906 | ij_java_align_multiline_assignment = false 907 | ij_java_align_multiline_binary_operation = false 908 | ij_java_align_multiline_chained_methods = false 909 | ij_java_align_multiline_deconstruction_list_components = true 910 | ij_java_align_multiline_extends_list = false 911 | ij_java_align_multiline_for = true 912 | ij_java_align_multiline_method_parentheses = false 913 | ij_java_align_multiline_parameters = true 914 | ij_java_align_multiline_parameters_in_calls = false 915 | ij_java_align_multiline_parenthesized_expression = false 916 | ij_java_align_multiline_records = true 917 | ij_java_align_multiline_resources = true 918 | ij_java_align_multiline_ternary_operation = false 919 | ij_java_align_multiline_text_blocks = false 920 | ij_java_align_multiline_throws_list = false 921 | ij_java_align_subsequent_simple_methods = false 922 | ij_java_align_throws_keyword = false 923 | ij_java_align_types_in_multi_catch = true 924 | ij_java_annotation_parameter_wrap = off 925 | ij_java_array_initializer_new_line_after_left_brace = false 926 | ij_java_array_initializer_right_brace_on_new_line = false 927 | ij_java_array_initializer_wrap = off 928 | ij_java_assert_statement_colon_on_next_line = false 929 | ij_java_assert_statement_wrap = off 930 | ij_java_assignment_wrap = off 931 | ij_java_binary_operation_sign_on_next_line = false 932 | ij_java_binary_operation_wrap = off 933 | ij_java_blank_lines_after_anonymous_class_header = 0 934 | ij_java_blank_lines_after_class_header = 0 935 | ij_java_blank_lines_after_imports = 1 936 | ij_java_blank_lines_after_package = 1 937 | ij_java_blank_lines_around_class = 1 938 | ij_java_blank_lines_around_field = 0 939 | ij_java_blank_lines_around_field_in_interface = 0 940 | ij_java_blank_lines_around_initializer = 1 941 | ij_java_blank_lines_around_method = 1 942 | ij_java_blank_lines_around_method_in_interface = 1 943 | ij_java_blank_lines_before_class_end = 0 944 | ij_java_blank_lines_before_imports = 1 945 | ij_java_blank_lines_before_method_body = 0 946 | ij_java_blank_lines_before_package = 0 947 | ij_java_block_brace_style = end_of_line 948 | ij_java_block_comment_add_space = false 949 | ij_java_block_comment_at_first_column = false 950 | ij_java_builder_methods = none 951 | ij_java_call_parameters_new_line_after_left_paren = false 952 | ij_java_call_parameters_right_paren_on_new_line = false 953 | ij_java_call_parameters_wrap = off 954 | ij_java_case_statement_on_separate_line = true 955 | ij_java_catch_on_new_line = false 956 | ij_java_class_annotation_wrap = split_into_lines 957 | ij_java_class_brace_style = end_of_line 958 | ij_java_class_count_to_use_import_on_demand = 5 959 | ij_java_class_names_in_javadoc = 1 960 | ij_java_deconstruction_list_wrap = normal 961 | ij_java_do_not_indent_top_level_class_members = false 962 | ij_java_do_not_wrap_after_single_annotation = false 963 | ij_java_do_not_wrap_after_single_annotation_in_parameter = false 964 | ij_java_do_while_brace_force = never 965 | ij_java_doc_add_blank_line_after_description = true 966 | ij_java_doc_add_blank_line_after_param_comments = false 967 | ij_java_doc_add_blank_line_after_return = false 968 | ij_java_doc_add_p_tag_on_empty_lines = true 969 | ij_java_doc_align_exception_comments = true 970 | ij_java_doc_align_param_comments = true 971 | ij_java_doc_do_not_wrap_if_one_line = false 972 | ij_java_doc_enable_formatting = true 973 | ij_java_doc_enable_leading_asterisks = true 974 | ij_java_doc_indent_on_continuation = false 975 | ij_java_doc_keep_empty_lines = true 976 | ij_java_doc_keep_empty_parameter_tag = true 977 | ij_java_doc_keep_empty_return_tag = true 978 | ij_java_doc_keep_empty_throws_tag = true 979 | ij_java_doc_keep_invalid_tags = true 980 | ij_java_doc_param_description_on_new_line = false 981 | ij_java_doc_preserve_line_breaks = false 982 | ij_java_doc_use_throws_not_exception_tag = true 983 | ij_java_else_on_new_line = false 984 | ij_java_entity_dd_suffix = EJB 985 | ij_java_entity_eb_suffix = Bean 986 | ij_java_entity_hi_suffix = Home 987 | ij_java_entity_lhi_prefix = Local 988 | ij_java_entity_lhi_suffix = Home 989 | ij_java_entity_li_prefix = Local 990 | ij_java_entity_pk_class = java.lang.String 991 | ij_java_entity_vo_suffix = VO 992 | ij_java_enum_constants_wrap = off 993 | ij_java_extends_keyword_wrap = off 994 | ij_java_extends_list_wrap = off 995 | ij_java_field_annotation_wrap = split_into_lines 996 | ij_java_finally_on_new_line = false 997 | ij_java_for_brace_force = never 998 | ij_java_for_statement_new_line_after_left_paren = false 999 | ij_java_for_statement_right_paren_on_new_line = false 1000 | ij_java_for_statement_wrap = off 1001 | ij_java_generate_final_locals = false 1002 | ij_java_generate_final_parameters = false 1003 | ij_java_if_brace_force = never 1004 | ij_java_imports_layout = *,|,javax.**,java.**,|,$* 1005 | ij_java_indent_case_from_switch = true 1006 | ij_java_insert_inner_class_imports = false 1007 | ij_java_insert_override_annotation = true 1008 | ij_java_keep_blank_lines_before_right_brace = 2 1009 | ij_java_keep_blank_lines_between_package_declaration_and_header = 2 1010 | ij_java_keep_blank_lines_in_code = 2 1011 | ij_java_keep_blank_lines_in_declarations = 2 1012 | ij_java_keep_builder_methods_indents = false 1013 | ij_java_keep_control_statement_in_one_line = true 1014 | ij_java_keep_first_column_comment = false 1015 | ij_java_keep_indents_on_empty_lines = false 1016 | ij_java_keep_line_breaks = true 1017 | ij_java_keep_multiple_expressions_in_one_line = false 1018 | ij_java_keep_simple_blocks_in_one_line = false 1019 | ij_java_keep_simple_classes_in_one_line = false 1020 | ij_java_keep_simple_lambdas_in_one_line = false 1021 | ij_java_keep_simple_methods_in_one_line = false 1022 | ij_java_label_indent_absolute = false 1023 | ij_java_label_indent_size = 0 1024 | ij_java_lambda_brace_style = end_of_line 1025 | ij_java_layout_static_imports_separately = true 1026 | ij_java_line_comment_add_space = true 1027 | ij_java_line_comment_add_space_on_reformat = true 1028 | ij_java_line_comment_at_first_column = false 1029 | ij_java_message_dd_suffix = EJB 1030 | ij_java_message_eb_suffix = Bean 1031 | ij_java_method_annotation_wrap = split_into_lines 1032 | ij_java_method_brace_style = end_of_line 1033 | ij_java_method_call_chain_wrap = off 1034 | ij_java_method_parameters_new_line_after_left_paren = false 1035 | ij_java_method_parameters_right_paren_on_new_line = false 1036 | ij_java_method_parameters_wrap = off 1037 | ij_java_modifier_list_wrap = false 1038 | ij_java_multi_catch_types_wrap = normal 1039 | ij_java_names_count_to_use_import_on_demand = 3 1040 | ij_java_new_line_after_lparen_in_annotation = false 1041 | ij_java_new_line_after_lparen_in_deconstruction_pattern = true 1042 | ij_java_new_line_after_lparen_in_record_header = false 1043 | ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* 1044 | ij_java_parameter_annotation_wrap = off 1045 | ij_java_parentheses_expression_new_line_after_left_paren = false 1046 | ij_java_parentheses_expression_right_paren_on_new_line = false 1047 | ij_java_place_assignment_sign_on_next_line = false 1048 | ij_java_prefer_longer_names = true 1049 | ij_java_prefer_parameters_wrap = false 1050 | ij_java_record_components_wrap = normal 1051 | ij_java_repeat_synchronized = true 1052 | ij_java_replace_instanceof_and_cast = false 1053 | ij_java_replace_null_check = true 1054 | ij_java_replace_sum_lambda_with_method_ref = true 1055 | ij_java_resource_list_new_line_after_left_paren = false 1056 | ij_java_resource_list_right_paren_on_new_line = false 1057 | ij_java_resource_list_wrap = off 1058 | ij_java_rparen_on_new_line_in_annotation = false 1059 | ij_java_rparen_on_new_line_in_deconstruction_pattern = true 1060 | ij_java_rparen_on_new_line_in_record_header = false 1061 | ij_java_session_dd_suffix = EJB 1062 | ij_java_session_eb_suffix = Bean 1063 | ij_java_session_hi_suffix = Home 1064 | ij_java_session_lhi_prefix = Local 1065 | ij_java_session_lhi_suffix = Home 1066 | ij_java_session_li_prefix = Local 1067 | ij_java_session_si_suffix = Service 1068 | ij_java_space_after_closing_angle_bracket_in_type_argument = false 1069 | ij_java_space_after_colon = true 1070 | ij_java_space_after_comma = true 1071 | ij_java_space_after_comma_in_type_arguments = true 1072 | ij_java_space_after_for_semicolon = true 1073 | ij_java_space_after_quest = true 1074 | ij_java_space_after_type_cast = true 1075 | ij_java_space_before_annotation_array_initializer_left_brace = false 1076 | ij_java_space_before_annotation_parameter_list = false 1077 | ij_java_space_before_array_initializer_left_brace = false 1078 | ij_java_space_before_catch_keyword = true 1079 | ij_java_space_before_catch_left_brace = true 1080 | ij_java_space_before_catch_parentheses = true 1081 | ij_java_space_before_class_left_brace = true 1082 | ij_java_space_before_colon = true 1083 | ij_java_space_before_colon_in_foreach = true 1084 | ij_java_space_before_comma = false 1085 | ij_java_space_before_deconstruction_list = false 1086 | ij_java_space_before_do_left_brace = true 1087 | ij_java_space_before_else_keyword = true 1088 | ij_java_space_before_else_left_brace = true 1089 | ij_java_space_before_finally_keyword = true 1090 | ij_java_space_before_finally_left_brace = true 1091 | ij_java_space_before_for_left_brace = true 1092 | ij_java_space_before_for_parentheses = true 1093 | ij_java_space_before_for_semicolon = false 1094 | ij_java_space_before_if_left_brace = true 1095 | ij_java_space_before_if_parentheses = true 1096 | ij_java_space_before_method_call_parentheses = false 1097 | ij_java_space_before_method_left_brace = true 1098 | ij_java_space_before_method_parentheses = false 1099 | ij_java_space_before_opening_angle_bracket_in_type_parameter = false 1100 | ij_java_space_before_quest = true 1101 | ij_java_space_before_switch_left_brace = true 1102 | ij_java_space_before_switch_parentheses = true 1103 | ij_java_space_before_synchronized_left_brace = true 1104 | ij_java_space_before_synchronized_parentheses = true 1105 | ij_java_space_before_try_left_brace = true 1106 | ij_java_space_before_try_parentheses = true 1107 | ij_java_space_before_type_parameter_list = false 1108 | ij_java_space_before_while_keyword = true 1109 | ij_java_space_before_while_left_brace = true 1110 | ij_java_space_before_while_parentheses = true 1111 | ij_java_space_inside_one_line_enum_braces = false 1112 | ij_java_space_within_empty_array_initializer_braces = false 1113 | ij_java_space_within_empty_method_call_parentheses = false 1114 | ij_java_space_within_empty_method_parentheses = false 1115 | ij_java_spaces_around_additive_operators = true 1116 | ij_java_spaces_around_annotation_eq = true 1117 | ij_java_spaces_around_assignment_operators = true 1118 | ij_java_spaces_around_bitwise_operators = true 1119 | ij_java_spaces_around_equality_operators = true 1120 | ij_java_spaces_around_lambda_arrow = true 1121 | ij_java_spaces_around_logical_operators = true 1122 | ij_java_spaces_around_method_ref_dbl_colon = false 1123 | ij_java_spaces_around_multiplicative_operators = true 1124 | ij_java_spaces_around_relational_operators = true 1125 | ij_java_spaces_around_shift_operators = true 1126 | ij_java_spaces_around_type_bounds_in_type_parameters = true 1127 | ij_java_spaces_around_unary_operator = false 1128 | ij_java_spaces_within_angle_brackets = false 1129 | ij_java_spaces_within_annotation_parentheses = false 1130 | ij_java_spaces_within_array_initializer_braces = false 1131 | ij_java_spaces_within_braces = false 1132 | ij_java_spaces_within_brackets = false 1133 | ij_java_spaces_within_cast_parentheses = false 1134 | ij_java_spaces_within_catch_parentheses = false 1135 | ij_java_spaces_within_deconstruction_list = false 1136 | ij_java_spaces_within_for_parentheses = false 1137 | ij_java_spaces_within_if_parentheses = false 1138 | ij_java_spaces_within_method_call_parentheses = false 1139 | ij_java_spaces_within_method_parentheses = false 1140 | ij_java_spaces_within_parentheses = false 1141 | ij_java_spaces_within_record_header = false 1142 | ij_java_spaces_within_switch_parentheses = false 1143 | ij_java_spaces_within_synchronized_parentheses = false 1144 | ij_java_spaces_within_try_parentheses = false 1145 | ij_java_spaces_within_while_parentheses = false 1146 | ij_java_special_else_if_treatment = true 1147 | ij_java_subclass_name_suffix = Impl 1148 | ij_java_ternary_operation_signs_on_next_line = false 1149 | ij_java_ternary_operation_wrap = off 1150 | ij_java_test_name_suffix = Test 1151 | ij_java_throws_keyword_wrap = off 1152 | ij_java_throws_list_wrap = off 1153 | ij_java_use_external_annotations = false 1154 | ij_java_use_fq_class_names = false 1155 | ij_java_use_relative_indents = false 1156 | ij_java_use_single_class_imports = true 1157 | ij_java_variable_annotation_wrap = off 1158 | ij_java_visibility = public 1159 | ij_java_while_brace_force = never 1160 | ij_java_while_on_new_line = false 1161 | ij_java_wrap_comments = false 1162 | ij_java_wrap_first_method_in_call_chain = false 1163 | ij_java_wrap_long_lines = false 1164 | 1165 | [{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] 1166 | ij_jsp_jsp_prefer_comma_separated_import_list = false 1167 | ij_jsp_keep_indents_on_empty_lines = false 1168 | 1169 | [{*.jspx,*.tagx}] 1170 | ij_jspx_keep_indents_on_empty_lines = false 1171 | 1172 | [{*.markdown,*.md}] 1173 | ij_markdown_force_one_space_after_blockquote_symbol = true 1174 | ij_markdown_force_one_space_after_header_symbol = true 1175 | ij_markdown_force_one_space_after_list_bullet = true 1176 | ij_markdown_force_one_space_between_words = true 1177 | ij_markdown_format_tables = true 1178 | ij_markdown_insert_quote_arrows_on_wrap = true 1179 | ij_markdown_keep_indents_on_empty_lines = false 1180 | ij_markdown_keep_line_breaks_inside_text_blocks = true 1181 | ij_markdown_max_lines_around_block_elements = 1 1182 | ij_markdown_max_lines_around_header = 1 1183 | ij_markdown_max_lines_between_paragraphs = 1 1184 | ij_markdown_min_lines_around_block_elements = 1 1185 | ij_markdown_min_lines_around_header = 1 1186 | ij_markdown_min_lines_between_paragraphs = 1 1187 | ij_markdown_wrap_text_if_long = true 1188 | ij_markdown_wrap_text_inside_blockquotes = true 1189 | 1190 | [{*.pb,*.textproto}] 1191 | indent_size = 2 1192 | tab_width = 2 1193 | ij_continuation_indent_size = 4 1194 | ij_prototext_keep_blank_lines_in_code = 2 1195 | ij_prototext_keep_indents_on_empty_lines = false 1196 | ij_prototext_keep_line_breaks = true 1197 | ij_prototext_space_after_colon = true 1198 | ij_prototext_space_after_comma = true 1199 | ij_prototext_space_before_colon = false 1200 | ij_prototext_space_before_comma = false 1201 | ij_prototext_spaces_within_braces = true 1202 | ij_prototext_spaces_within_brackets = false 1203 | 1204 | [{*.properties,spring.handlers,spring.schemas}] 1205 | ij_properties_align_group_field_declarations = false 1206 | ij_properties_keep_blank_lines = false 1207 | ij_properties_key_value_delimiter = equals 1208 | ij_properties_spaces_around_key_value_delimiter = false 1209 | 1210 | [{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] 1211 | ij_qute_keep_indents_on_empty_lines = false 1212 | 1213 | [{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] 1214 | ij_toml_keep_indents_on_empty_lines = false 1215 | 1216 | [{*.yaml,*.yml}] 1217 | indent_size = 2 1218 | ij_yaml_align_values_properties = do_not_align 1219 | ij_yaml_autoinsert_sequence_marker = true 1220 | ij_yaml_block_mapping_on_new_line = false 1221 | ij_yaml_indent_sequence_value = true 1222 | ij_yaml_keep_indents_on_empty_lines = false 1223 | ij_yaml_keep_line_breaks = true 1224 | ij_yaml_sequence_on_new_line = false 1225 | ij_yaml_space_before_colon = false 1226 | ij_yaml_spaces_within_braces = true 1227 | ij_yaml_spaces_within_brackets = true 1228 | --------------------------------------------------------------------------------