的
This commit is contained in:
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020 Frederic Thevenet
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.binjr.sources.logs.adapters;
|
||||
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import eu.binjr.common.preferences.ObservablePreference;
|
||||
import eu.binjr.core.data.adapters.DataAdapter;
|
||||
import eu.binjr.core.data.adapters.DataAdapterPreferences;
|
||||
import eu.binjr.core.data.indexes.parser.profile.BuiltInParsingProfile;
|
||||
import eu.binjr.core.data.indexes.parser.profile.ParsingProfile;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Defines the preferences associated with the Log files adapter.
|
||||
*
|
||||
* @author Frederic Thevenet
|
||||
*/
|
||||
public class LogsAdapterPreferences extends DataAdapterPreferences {
|
||||
private static final Gson gson = new Gson();
|
||||
/**
|
||||
* The default text panel font size preference.
|
||||
*/
|
||||
public ObservablePreference<Number> defaultTextViewFontSize = integerPreference("defaultTextViewFontSize", 10);
|
||||
|
||||
/**
|
||||
* The filters used when scanning folders in the source filesystem.
|
||||
*/
|
||||
public ObservablePreference<String[]> folderFilters = objectPreference(String[].class,
|
||||
"folderFilters",
|
||||
new String[]{"*"},
|
||||
gson::toJson,
|
||||
s -> gson.fromJson(s, String[].class));
|
||||
|
||||
/**
|
||||
* The filters used to prune file extensions to scan in the source filesystem.
|
||||
*/
|
||||
public ObservablePreference<String[]> fileExtensionFilters = objectPreference(String[].class,
|
||||
"extensionFilters",
|
||||
new String[]{"*.log", "*.txt"},
|
||||
gson::toJson,
|
||||
s -> gson.fromJson(s, String[].class));
|
||||
|
||||
/**
|
||||
* The most recently used {@link ParsingProfile}
|
||||
*/
|
||||
public ObservablePreference<String> mostRecentlyUsedParsingProfile =
|
||||
stringPreference("mostRecentlyUsedParsingProfile", BuiltInParsingProfile.ISO.getProfileId());
|
||||
|
||||
/**
|
||||
* The most recently used encoding
|
||||
*/
|
||||
public ObservablePreference<String> mruEncoding =
|
||||
stringPreference("mruEncoding", StandardCharsets.UTF_8.name());
|
||||
|
||||
/**
|
||||
* Initialize a new instance of the {@link LogsAdapterPreferences} class associated to
|
||||
* a {@link DataAdapter} instance.
|
||||
*
|
||||
* @param dataAdapterClass the associated {@link DataAdapter}
|
||||
*/
|
||||
public LogsAdapterPreferences(Class<? extends DataAdapter<?>> dataAdapterClass) {
|
||||
super(dataAdapterClass);
|
||||
}
|
||||
}
|
||||
+473
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* Copyright 2020-2023 Frederic Thevenet
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.binjr.sources.logs.adapters;
|
||||
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import eu.binjr.common.function.CheckedFunction;
|
||||
import eu.binjr.common.function.CheckedLambdas;
|
||||
import eu.binjr.common.io.FileSystemBrowser;
|
||||
import eu.binjr.common.io.IOUtils;
|
||||
import eu.binjr.common.javafx.controls.TimeRange;
|
||||
import eu.binjr.common.javafx.controls.TreeViewUtils;
|
||||
import eu.binjr.common.logging.Logger;
|
||||
import eu.binjr.common.logging.Profiler;
|
||||
import eu.binjr.common.preferences.MostRecentlyUsedList;
|
||||
import eu.binjr.common.text.BinaryPrefixFormatter;
|
||||
import eu.binjr.core.data.adapters.*;
|
||||
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
|
||||
import eu.binjr.core.data.exceptions.DataAdapterException;
|
||||
import eu.binjr.core.data.indexes.*;
|
||||
import eu.binjr.core.data.indexes.parser.EventFormat;
|
||||
import eu.binjr.core.data.indexes.parser.LogEventFormat;
|
||||
import eu.binjr.core.data.indexes.parser.profile.CustomParsingProfile;
|
||||
import eu.binjr.core.data.indexes.parser.profile.ParsingProfile;
|
||||
import eu.binjr.core.data.timeseries.TimeSeriesProcessor;
|
||||
import eu.binjr.core.data.workspace.LogFileSeriesInfo;
|
||||
import eu.binjr.core.data.workspace.TimeSeriesInfo;
|
||||
import eu.binjr.core.dialogs.Dialogs;
|
||||
import eu.binjr.core.preferences.UserHistory;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StoredField;
|
||||
import org.apache.lucene.document.TextField;
|
||||
import org.apache.lucene.facet.FacetField;
|
||||
import org.eclipse.fx.ui.controls.tree.FilterableTreeItem;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static eu.binjr.core.data.indexes.parser.capture.CaptureGroup.SEVERITY;
|
||||
|
||||
/**
|
||||
* A {@link DataAdapter} implementation to retrieve data from a text file.
|
||||
*
|
||||
* @author Frederic Thevenet
|
||||
*/
|
||||
public class LogsDataAdapter extends BaseDataAdapter<SearchHit> implements ProgressAdapter<SearchHit> {
|
||||
private static final Logger logger = Logger.create(LogsDataAdapter.class);
|
||||
private static final Gson gson = new Gson();
|
||||
private static final String DEFAULT_PREFIX = "[Logs]";
|
||||
private static final String ZONE_ID_PARAM_NAME = "zoneId";
|
||||
private static final String ROOT_PATH_PARAM_NAME = "rootPath";
|
||||
private static final String FOLDER_FILTERS_PARAM_NAME = "folderFilters";
|
||||
private static final String EXTENSIONS_FILTERS_PARAM_NAME = "fileExtensionsFilters";
|
||||
private static final String PARSING_PROFILE_PARAM_NAME = "parsingProfile";
|
||||
private static final String LOG_FILE_ENCODING = "LOG_FILE_ENCODING";
|
||||
private static final Property<IndexingStatus> INDEXING_OK = new ReadOnlyObjectWrapper(new SimpleObjectProperty<>(IndexingStatus.OK));
|
||||
|
||||
private final String sourceNamePrefix;
|
||||
private final Map<String, IndexingStatus> indexedFiles = new HashMap<>();
|
||||
private final BinaryPrefixFormatter binaryPrefixFormatter = new BinaryPrefixFormatter("###,###.## ");
|
||||
private final MostRecentlyUsedList<String> defaultParsingProfiles =
|
||||
UserHistory.getInstance().stringMostRecentlyUsedList("defaultParsingProfiles", 100);
|
||||
private final MostRecentlyUsedList<String> userParsingProfiles =
|
||||
UserHistory.getInstance().stringMostRecentlyUsedList("userParsingProfiles", 100);
|
||||
private final Charset encoding;
|
||||
private Path rootPath;
|
||||
private Indexable index;
|
||||
private FileSystemBrowser fileBrowser;
|
||||
private String[] folderFilters;
|
||||
private String[] fileExtensionsFilters;
|
||||
private ParsingProfile parsingProfile;
|
||||
private EventFormat<InputStream> defaultEventFormat;
|
||||
private ZoneId zoneId;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@link LogsDataAdapter} class.
|
||||
*
|
||||
* @throws DataAdapterException if an error occurs while initializing the adapter.
|
||||
*/
|
||||
public LogsDataAdapter() throws DataAdapterException {
|
||||
super();
|
||||
zoneId = ZoneId.systemDefault();
|
||||
encoding = StandardCharsets.UTF_8;
|
||||
sourceNamePrefix = DEFAULT_PREFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@link LogsDataAdapter} class from the provided {@link Path}
|
||||
*
|
||||
* @param rootPath the {@link Path} from which to load content.
|
||||
* @param folderFilters a list of names of folders to inspect for content.
|
||||
* @param fileExtensionsFilters a list of file extensions to inspect for content.
|
||||
* @param profile the parsing profile to use.
|
||||
* @throws DataAdapterException if an error occurs initializing the adapter.
|
||||
*/
|
||||
public LogsDataAdapter(Path rootPath,
|
||||
ZoneId zoneId,
|
||||
String[] folderFilters,
|
||||
String[] fileExtensionsFilters,
|
||||
ParsingProfile profile) throws DataAdapterException {
|
||||
this(DEFAULT_PREFIX, rootPath, zoneId, StandardCharsets.UTF_8, folderFilters, fileExtensionsFilters, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@link LogsDataAdapter} class from the provided {@link Path}
|
||||
*
|
||||
* @param sourcePrefix the name to prepend the source with.
|
||||
* @param rootPath the {@link Path} from which to load content.
|
||||
* @param folderFilters a list of names of folders to inspect for content.
|
||||
* @param fileExtensionsFilters a list of file extensions to inspect for content.
|
||||
* @param profile the parsing profile to use.
|
||||
* @throws DataAdapterException if an error occurs initializing the adapter.
|
||||
*/
|
||||
public LogsDataAdapter(String sourcePrefix,
|
||||
Path rootPath,
|
||||
ZoneId zoneId,
|
||||
String[] folderFilters,
|
||||
String[] fileExtensionsFilters,
|
||||
ParsingProfile profile) throws DataAdapterException {
|
||||
this(sourcePrefix, rootPath, zoneId, StandardCharsets.UTF_8, folderFilters, fileExtensionsFilters, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@link LogsDataAdapter} class from the provided {@link Path}
|
||||
*
|
||||
* @param rootPath the {@link Path} from which to load content.
|
||||
* @param folderFilters a list of names of folders to inspect for content.
|
||||
* @param fileExtensionsFilters a list of file extensions to inspect for content.
|
||||
* @param profile the parsing profile to use.
|
||||
* @throws DataAdapterException if an error occurs initializing the adapter.
|
||||
*/
|
||||
public LogsDataAdapter(Path rootPath,
|
||||
ZoneId zoneId,
|
||||
Charset encoding,
|
||||
String[] folderFilters,
|
||||
String[] fileExtensionsFilters,
|
||||
ParsingProfile profile) throws DataAdapterException {
|
||||
this(DEFAULT_PREFIX, rootPath, zoneId, encoding, folderFilters, fileExtensionsFilters, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@link LogsDataAdapter} class from the provided {@link Path}
|
||||
*
|
||||
* @param sourcePrefix the name to prepend the source with.
|
||||
* @param rootPath the {@link Path} from which to load content.
|
||||
* @param folderFilters a list of names of folders to inspect for content.
|
||||
* @param fileExtensionsFilters a list of file extensions to inspect for content.
|
||||
* @param profile the parsing profile to use.
|
||||
* @throws DataAdapterException if an error occurs initializing the adapter.
|
||||
*/
|
||||
public LogsDataAdapter(String sourcePrefix,
|
||||
Path rootPath,
|
||||
ZoneId zoneId,
|
||||
Charset encoding,
|
||||
String[] folderFilters,
|
||||
String[] fileExtensionsFilters,
|
||||
ParsingProfile profile) throws DataAdapterException {
|
||||
super();
|
||||
this.sourceNamePrefix = sourcePrefix;
|
||||
this.rootPath = rootPath;
|
||||
this.encoding = encoding;
|
||||
Map<String, String> params = new HashMap<>();
|
||||
initParams(rootPath, zoneId, folderFilters, fileExtensionsFilters, profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getParams() {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put(LOG_FILE_ENCODING, getEncoding());
|
||||
params.put(ROOT_PATH_PARAM_NAME, rootPath.toString());
|
||||
params.put(ZONE_ID_PARAM_NAME, zoneId.toString());
|
||||
params.put(FOLDER_FILTERS_PARAM_NAME, gson.toJson(folderFilters));
|
||||
params.put(EXTENSIONS_FILTERS_PARAM_NAME, gson.toJson(fileExtensionsFilters));
|
||||
params.put(PARSING_PROFILE_PARAM_NAME, gson.toJson(CustomParsingProfile.of(parsingProfile)));
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadParams(Map<String, String> params) throws DataAdapterException {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(() -> "LogsDataAdapter params:");
|
||||
params.forEach((s, s2) -> logger.debug(() -> "key=" + s + ", value=" + s2));
|
||||
}
|
||||
initParams(Paths.get(validateParameterNullity(params, ROOT_PATH_PARAM_NAME)),
|
||||
validateParameter(params, ZONE_ID_PARAM_NAME,
|
||||
s -> {
|
||||
if (s == null) {
|
||||
logger.warn("Parameter " + ZONE_ID_PARAM_NAME + " is missing in adapter " + getSourceName());
|
||||
return ZoneId.systemDefault();
|
||||
}
|
||||
return ZoneId.of(s);
|
||||
}),
|
||||
gson.fromJson(validateParameterNullity(params, FOLDER_FILTERS_PARAM_NAME), String[].class),
|
||||
gson.fromJson(validateParameterNullity(params, EXTENSIONS_FILTERS_PARAM_NAME), String[].class),
|
||||
gson.fromJson(validateParameterNullity(params, PARSING_PROFILE_PARAM_NAME), CustomParsingProfile.class));
|
||||
}
|
||||
|
||||
private void initParams(Path rootPath,
|
||||
ZoneId zoneId,
|
||||
String[] folderFilters,
|
||||
String[] fileExtensionsFilters,
|
||||
ParsingProfile parsingProfile) throws DataAdapterException {
|
||||
this.rootPath = rootPath;
|
||||
this.zoneId = zoneId;
|
||||
this.folderFilters = folderFilters;
|
||||
this.fileExtensionsFilters = fileExtensionsFilters;
|
||||
this.parsingProfile = parsingProfile;
|
||||
this.defaultEventFormat = new LogEventFormat(parsingProfile, getTimeZoneId(), encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() throws DataAdapterException {
|
||||
super.onStart();
|
||||
try {
|
||||
this.fileBrowser = FileSystemBrowser.of(rootPath);
|
||||
this.index = Indexes.LOG_FILES.acquire();
|
||||
} catch (IOException e) {
|
||||
throw new CannotInitializeDataAdapterException("An error occurred during the data adapter initialization", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterableTreeItem<SourceBinding> getBindingTree() throws DataAdapterException {
|
||||
FilterableTreeItem<SourceBinding> configNode = new FilterableTreeItem<>(
|
||||
new LogFilesBinding.Builder()
|
||||
.withLabel(getSourceName())
|
||||
.withAdapter(this)
|
||||
.build());
|
||||
attachNodes(configNode);
|
||||
return configNode;
|
||||
}
|
||||
|
||||
private void attachNodes(FilterableTreeItem<SourceBinding> root) throws DataAdapterException {
|
||||
try (var p = Profiler.start("Building log files binding tree", logger::perf)) {
|
||||
Map<Path, FilterableTreeItem<SourceBinding>> nodeDict = new HashMap<>();
|
||||
nodeDict.put(fileBrowser.toInternalPath("/"), root);
|
||||
for (var fsEntry : fileBrowser.listEntries(folderFilters, fileExtensionsFilters)) {
|
||||
String fileName = fsEntry.getPath().getFileName().toString();
|
||||
var attachTo = root;
|
||||
if (fsEntry.getPath().getParent() != null) {
|
||||
attachTo = nodeDict.get(fsEntry.getPath().getParent());
|
||||
if (attachTo == null) {
|
||||
attachTo = makeBranchNode(nodeDict, fsEntry.getPath().getParent(), root);
|
||||
}
|
||||
}
|
||||
FilterableTreeItem<SourceBinding> filenode = new FilterableTreeItem<>(
|
||||
new LogFilesBinding.Builder()
|
||||
.withLabel(fileName + " (" + binaryPrefixFormatter.format(fsEntry.getSize()) + "B)")
|
||||
.withPath(getId() + "/" + fsEntry.getPath().toString())
|
||||
.withParent(attachTo.getValue())
|
||||
.withParsingProfile(parsingProfile)
|
||||
.withAdapter(this)
|
||||
.build());
|
||||
attachTo.getInternalChildren().add(filenode);
|
||||
}
|
||||
TreeViewUtils.sortFromBranch(root);
|
||||
} catch (Exception e) {
|
||||
Dialogs.notifyException("Error while enumerating files: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private FilterableTreeItem<SourceBinding> makeBranchNode(Map<Path, FilterableTreeItem<SourceBinding>> nodeDict,
|
||||
Path path,
|
||||
FilterableTreeItem<SourceBinding> root) {
|
||||
var parent = root;
|
||||
var rootPath = path.isAbsolute() ? path.getRoot() : path.getName(0);
|
||||
for (int i = 0; i < path.getNameCount(); i++) {
|
||||
Path current = rootPath.resolve(path.getName(i));
|
||||
FilterableTreeItem<SourceBinding> filenode = nodeDict.get(current);
|
||||
if (filenode == null) {
|
||||
filenode = new FilterableTreeItem<>(
|
||||
new LogFilesBinding.Builder()
|
||||
.withLabel(current.getFileName().toString())
|
||||
.withPath(getId() + "/" + path.toString())
|
||||
.withParent(parent.getValue())
|
||||
.withParsingProfile(parsingProfile)
|
||||
.withAdapter(this)
|
||||
.build());
|
||||
nodeDict.put(current, filenode);
|
||||
parent.getInternalChildren().add(filenode);
|
||||
}
|
||||
parent = filenode;
|
||||
rootPath = current;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeRange getInitialTimeRange(String path, List<TimeSeriesInfo<SearchHit>> seriesInfo) throws DataAdapterException {
|
||||
try {
|
||||
return index.getTimeRangeBoundaries(seriesInfo.stream().map(this::getPathFacetValue).toList(), getTimeZoneId());
|
||||
} catch (IOException e) {
|
||||
throw new DataAdapterException("Error retrieving initial time range", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public Map<TimeSeriesInfo<SearchHit>, TimeSeriesProcessor<SearchHit>> fetchData(String path,
|
||||
Instant begin,
|
||||
Instant end,
|
||||
List<TimeSeriesInfo<SearchHit>> seriesInfo,
|
||||
boolean bypassCache) throws DataAdapterException {
|
||||
return loadSeries(path, seriesInfo, bypassCache ? ReloadPolicy.ALL : ReloadPolicy.UNLOADED, null, INDEXING_OK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TimeSeriesInfo<SearchHit>, TimeSeriesProcessor<SearchHit>> loadSeries(String path,
|
||||
List<TimeSeriesInfo<SearchHit>> seriesInfo,
|
||||
ReloadPolicy reloadPolicy,
|
||||
DoubleProperty progress,
|
||||
Property<IndexingStatus> indexingStatus) throws DataAdapterException {
|
||||
Map<TimeSeriesInfo<SearchHit>, TimeSeriesProcessor<SearchHit>> data = new HashMap<>();
|
||||
try {
|
||||
ensureIndexed(seriesInfo.stream()
|
||||
.filter(s -> s instanceof LogFileSeriesInfo)
|
||||
.map(s -> (LogFileSeriesInfo) s)
|
||||
.toList(),
|
||||
progress,
|
||||
reloadPolicy,
|
||||
indexingStatus);
|
||||
} catch (Exception e) {
|
||||
throw new DataAdapterException("Error fetching logs from " + path, e);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private synchronized void ensureIndexed(List<LogFileSeriesInfo> seriesInfo,
|
||||
DoubleProperty progress,
|
||||
ReloadPolicy reloadPolicy,
|
||||
Property<IndexingStatus> indexingStatus) throws IOException {
|
||||
final var toDo = seriesInfo.stream()
|
||||
.filter(p -> switch (reloadPolicy) {
|
||||
case ALL -> true;
|
||||
case UNLOADED -> !indexedFiles.containsKey(getPathFacetValue(p));
|
||||
case INCOMPLETE ->
|
||||
indexedFiles.getOrDefault(getPathFacetValue(p), IndexingStatus.CANCELED) == IndexingStatus.CANCELED;
|
||||
})
|
||||
.toList();
|
||||
if (toDo.size() > 0) {
|
||||
final long totalSizeInBytes = toDo.stream()
|
||||
.map(CheckedLambdas.wrap((CheckedFunction<LogFileSeriesInfo, Long, IOException>)
|
||||
e -> fileBrowser.getEntry(e.getBinding().getPath().replace(getId() + "/", "")).getSize()))
|
||||
.reduce(Long::sum).orElse(0L);
|
||||
|
||||
final ChangeListener<Number> progressListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue != null && totalSizeInBytes > 0) {
|
||||
var oldProgress = (oldValue.longValue() * 100 / totalSizeInBytes) / 100.0;
|
||||
var newProgress = (newValue.longValue() * 100 / totalSizeInBytes) / 100.0;
|
||||
if (progress != null && oldProgress != newProgress) {
|
||||
Dialogs.runOnFXThread(() -> progress.setValue(newProgress));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final LongProperty charRead = new SimpleLongProperty(0);
|
||||
charRead.addListener(progressListener);
|
||||
try {
|
||||
for (int i = 0; i < toDo.size(); i++) {
|
||||
var tsInfo = toDo.get(i);
|
||||
String path = tsInfo.getBinding().getPath();
|
||||
var key = getPathFacetValue(tsInfo);
|
||||
index.add(key,
|
||||
fileBrowser.getData(path.replace(getId() + "/", "")),
|
||||
(i == toDo.size() - 1), // commit if last file
|
||||
getEventFormat(tsInfo),
|
||||
(doc, event) -> {
|
||||
// add all other sections as prefixed search fields
|
||||
event.getTextFields().entrySet().stream()
|
||||
.filter(e -> !e.getKey().equals(SEVERITY))
|
||||
.forEach(e -> doc.add(new TextField(e.getKey(), e.getValue(), Field.Store.NO)));
|
||||
// Add severity
|
||||
String severity = event.getTextField(SEVERITY) == null ? "unknown" : event.getTextField(SEVERITY).toLowerCase();
|
||||
doc.add(new FacetField(SEVERITY, severity));
|
||||
doc.add(new StoredField(SEVERITY, severity));
|
||||
return doc;
|
||||
},
|
||||
charRead,
|
||||
indexingStatus);
|
||||
indexedFiles.put(key, indexingStatus.getValue());
|
||||
}
|
||||
} finally {
|
||||
// remove listener
|
||||
charRead.removeListener(progressListener);
|
||||
if (progress != null) {
|
||||
Dialogs.runOnFXThread(() -> progress.setValue(-1));
|
||||
}
|
||||
// reset cancellation request
|
||||
indexingStatus.setValue(IndexingStatus.OK);
|
||||
}
|
||||
}
|
||||
// Update loading status for series
|
||||
for (var series : seriesInfo) {
|
||||
series.setIndexingStatus(indexedFiles.get(getPathFacetValue(series)));
|
||||
}
|
||||
}
|
||||
|
||||
private String readTextFile(String path) throws IOException {
|
||||
try (Profiler ignored = Profiler.start("Extracting text from file " + path, logger::perf)) {
|
||||
try (var reader = new BufferedReader(new InputStreamReader(fileBrowser.getData(path), StandardCharsets.UTF_8))) {
|
||||
return reader.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getPathFacetValue(TimeSeriesInfo<?> p) {
|
||||
if (p instanceof LogFileSeriesInfo lfsi && lfsi.getParsingProfile() != null) {
|
||||
return lfsi.getPathFacetValue();
|
||||
}
|
||||
return LogFileSeriesInfo.makePathFacetValue(parsingProfile, p);
|
||||
}
|
||||
|
||||
private EventFormat<InputStream> getEventFormat(TimeSeriesInfo<?> p) {
|
||||
if (p instanceof LogFileSeriesInfo lfsi && lfsi.getParsingProfile() != null) {
|
||||
return new LogEventFormat(lfsi.getParsingProfile(), getTimeZoneId(), encoding);
|
||||
}
|
||||
return defaultEventFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEncoding() {
|
||||
return encoding.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZoneId getTimeZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceName() {
|
||||
return String.format("%s %s", sourceNamePrefix, rootPath != null ? rootPath.getFileName() : "???");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
Indexes.LOG_FILES.release();
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while releasing index " + Indexes.LOG_FILES.name() + ": " + e.getMessage());
|
||||
logger.debug("Stack Trace:", e);
|
||||
}
|
||||
IOUtils.close(fileBrowser);
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2020-2022 Frederic Thevenet
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.binjr.sources.logs.adapters;
|
||||
|
||||
import eu.binjr.common.javafx.controls.LabelWithInlineHelp;
|
||||
import eu.binjr.common.javafx.controls.NodeUtils;
|
||||
import eu.binjr.common.logging.Logger;
|
||||
import eu.binjr.core.data.adapters.DataAdapter;
|
||||
import eu.binjr.core.data.adapters.DataAdapterFactory;
|
||||
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
|
||||
import eu.binjr.core.data.exceptions.DataAdapterException;
|
||||
import eu.binjr.core.data.exceptions.NoAdapterFoundException;
|
||||
import eu.binjr.core.data.indexes.parser.profile.BuiltInParsingProfile;
|
||||
import eu.binjr.core.data.indexes.parser.profile.ParsingProfile;
|
||||
import eu.binjr.core.dialogs.DataAdapterDialog;
|
||||
import eu.binjr.core.dialogs.Dialogs;
|
||||
import eu.binjr.core.dialogs.LogParsingProfileDialog;
|
||||
import eu.binjr.core.preferences.UserPreferences;
|
||||
import javafx.geometry.*;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An implementation of the {@link DataAdapterDialog} class that presents a dialog box to retrieve the parameters specific {@link LogsDataAdapterDialog}
|
||||
*
|
||||
* @author Frederic Thevenet
|
||||
*/
|
||||
public class LogsDataAdapterDialog extends DataAdapterDialog<Path> {
|
||||
private static final Logger logger = Logger.create(LogsDataAdapterDialog.class);
|
||||
private final TextField extensionFiltersTextField;
|
||||
private final TextField encodingField;
|
||||
private final LogsAdapterPreferences prefs;
|
||||
private final ChoiceBox<ParsingProfile> parsingChoiceBox = new ChoiceBox<>();
|
||||
|
||||
|
||||
private void updateProfileList(ParsingProfile[] newValue) {
|
||||
parsingChoiceBox.getItems().clear();
|
||||
parsingChoiceBox.getItems().setAll(BuiltInParsingProfile.editableProfiles());
|
||||
parsingChoiceBox.getItems().addAll(newValue);
|
||||
parsingChoiceBox.getSelectionModel().select(parsingChoiceBox.getItems().stream()
|
||||
.filter(p -> Objects.equals(p.getProfileId(), prefs.mostRecentlyUsedParsingProfile.get()))
|
||||
.findAny().orElse(BuiltInParsingProfile.ALL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@link LogsDataAdapterDialog} class.
|
||||
*
|
||||
* @param owner the owner window for the dialog
|
||||
* @throws NoAdapterFoundException if an error occurs initializing the dialog.
|
||||
*/
|
||||
public LogsDataAdapterDialog(Node owner) throws NoAdapterFoundException {
|
||||
super(owner, Mode.PATH, "mostRecentLogsArchives", true);
|
||||
this.prefs = (LogsAdapterPreferences) DataAdapterFactory.getInstance().getAdapterPreferences(LogsDataAdapter.class.getName());
|
||||
setDialogHeaderText("Add a Log File, Zip Archive or Folder");
|
||||
this.setUriLabelInlineHelp("""
|
||||
A file system path to get log files from.
|
||||
It can either be:
|
||||
- The path to a single text-based log file, which will then be the sole item available in the source tree.
|
||||
- The path to a folder, in which case all files in the folder and sub-folders that match the extension filter below will be available the the source tree.
|
||||
- The path to a Zip archive, in which case all files in the archive that match the extension filter below will be available the the source tree.
|
||||
""");
|
||||
this.setTimezoneLabelInlineHelp("""
|
||||
The default timezone for timestamps in the source files.
|
||||
""");
|
||||
extensionFiltersTextField = new TextField(String.join(", ", prefs.fileExtensionFilters.get()));
|
||||
var extensionlabel = new LabelWithInlineHelp();
|
||||
extensionlabel.setText("Extensions");
|
||||
extensionlabel.setInlineHelp("""
|
||||
The set of extensions used to filter which files should be available in the source tree when pointing to a folder or a zip archive.
|
||||
You can specify any number of glob patterns separated by spaces, commas or semi-colons.
|
||||
""");
|
||||
extensionlabel.setAlignment(Pos.CENTER_RIGHT);
|
||||
GridPane.setConstraints(extensionlabel, 0, 2, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
|
||||
GridPane.setConstraints(extensionFiltersTextField, 1, 2, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
|
||||
|
||||
this.encodingField = new TextField(prefs.mruEncoding.get());
|
||||
TextFields.bindAutoCompletion(encodingField, Charset.availableCharsets().keySet());
|
||||
GridPane.setConstraints(encodingField, 1, 3, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
|
||||
var encodingLabel = new LabelWithInlineHelp();
|
||||
encodingLabel.setText("Encoding");
|
||||
encodingLabel.setInlineHelp("""
|
||||
The text encoding of files to extracts log events from.
|
||||
""");
|
||||
encodingLabel.setAlignment(Pos.CENTER_RIGHT);
|
||||
GridPane.setConstraints(encodingLabel, 0, 3, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
|
||||
|
||||
var parsingLabel = new LabelWithInlineHelp();
|
||||
parsingLabel.setText("Parsing profile");
|
||||
parsingLabel.setInlineHelp("""
|
||||
The default parsing profile used to process log files loaded from this source.
|
||||
It is still possible to select a different profile on of file-by-file basis once the source is loaded.
|
||||
""");
|
||||
parsingLabel.setAlignment(Pos.CENTER_RIGHT);
|
||||
var parsingHBox = new HBox();
|
||||
parsingHBox.setSpacing(5);
|
||||
updateProfileList(UserPreferences.getInstance().userLogEventsParsingProfiles.get());
|
||||
parsingChoiceBox.setMaxWidth(Double.MAX_VALUE);
|
||||
var editParsingButton = new Button("Edit");
|
||||
editParsingButton.setOnAction(event -> {
|
||||
try {
|
||||
new LogParsingProfileDialog(this.getOwner(), parsingChoiceBox.getValue()).showAndWait().ifPresent(selection -> {
|
||||
prefs.mostRecentlyUsedParsingProfile.set(selection.getProfileId());
|
||||
updateProfileList( UserPreferences.getInstance().userLogEventsParsingProfiles.get());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Dialogs.notifyException("Failed to show parsing profile windows", e, owner);
|
||||
}
|
||||
});
|
||||
parsingHBox.getChildren().addAll(parsingChoiceBox, editParsingButton);
|
||||
HBox.setHgrow(parsingChoiceBox, Priority.ALWAYS);
|
||||
GridPane.setConstraints(parsingLabel, 0, 4, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
|
||||
GridPane.setConstraints(parsingHBox, 1, 4, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
|
||||
|
||||
getParamsGridPane().getChildren().addAll(extensionlabel, extensionFiltersTextField, parsingLabel, parsingHBox, encodingLabel, encodingField);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File displayFileChooser(Node owner) {
|
||||
try {
|
||||
ContextMenu sourceMenu = new ContextMenu();
|
||||
MenuItem fileMenuItem = new MenuItem("Log file");
|
||||
fileMenuItem.setOnAction(eventHandler -> {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Open Log File");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Log file", "*.log", "*.txt", "*.trace", "*.out", "*.err"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*", "*"));
|
||||
Dialogs.getInitialDir(getMostRecentList()).ifPresent(fileChooser::setInitialDirectory);
|
||||
File selectedFile = fileChooser.showOpenDialog(NodeUtils.getStage(owner));
|
||||
if (selectedFile != null) {
|
||||
setSourceUri(selectedFile.getPath());
|
||||
}
|
||||
});
|
||||
sourceMenu.getItems().add(fileMenuItem);
|
||||
MenuItem zipMenuItem = new MenuItem("Zip file");
|
||||
zipMenuItem.setOnAction(eventHandler -> {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Open Zip Archive");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Zip archive", "*.zip"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*", "*"));
|
||||
Dialogs.getInitialDir(getMostRecentList()).ifPresent(fileChooser::setInitialDirectory);
|
||||
File selectedFile = fileChooser.showOpenDialog(NodeUtils.getStage(owner));
|
||||
if (selectedFile != null) {
|
||||
setSourceUri(selectedFile.getPath());
|
||||
}
|
||||
});
|
||||
sourceMenu.getItems().add(zipMenuItem);
|
||||
MenuItem folderMenuItem = new MenuItem("Folder");
|
||||
folderMenuItem.setOnAction(eventHandler -> {
|
||||
DirectoryChooser dirChooser = new DirectoryChooser();
|
||||
dirChooser.setTitle("Open Folder");
|
||||
Dialogs.getInitialDir(getMostRecentList()).ifPresent(dirChooser::setInitialDirectory);
|
||||
File selectedFile = dirChooser.showDialog(NodeUtils.getStage(owner));
|
||||
if (selectedFile != null) {
|
||||
setSourceUri(selectedFile.getPath());
|
||||
}
|
||||
});
|
||||
sourceMenu.getItems().add(folderMenuItem);
|
||||
sourceMenu.show(owner, Side.RIGHT, 0, 0);
|
||||
} catch (Exception e) {
|
||||
Dialogs.notifyException("Error while displaying file chooser: " + e.getMessage(), e, owner);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<DataAdapter> getDataAdapters() throws DataAdapterException {
|
||||
Path path = Paths.get(getSourceUri());
|
||||
if (!Files.exists(path)) {
|
||||
throw new CannotInitializeDataAdapterException("Cannot find " + getSourceUri());
|
||||
}
|
||||
if (!path.isAbsolute()) {
|
||||
throw new CannotInitializeDataAdapterException("The provided path is not valid.");
|
||||
}
|
||||
getMostRecentList().push(path);
|
||||
prefs.fileExtensionFilters.set(Arrays.stream(extensionFiltersTextField.getText().split("[,;\" ]+")).filter(e -> !e.isBlank()).toArray(String[]::new));
|
||||
prefs.mostRecentlyUsedParsingProfile.set(parsingChoiceBox.getValue().getProfileId());
|
||||
|
||||
String charsetName = encodingField.getText();
|
||||
if (!Charset.isSupported(charsetName)) {
|
||||
throw new CannotInitializeDataAdapterException("Invalid or unsupported encoding: " + charsetName);
|
||||
}
|
||||
|
||||
return List.of(new LogsDataAdapter(path,
|
||||
ZoneId.of(getSourceTimezone()),
|
||||
Charset.forName(charsetName),
|
||||
prefs.folderFilters.get(),
|
||||
prefs.fileExtensionFilters.get(),
|
||||
parsingChoiceBox.getValue()));
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2020-2023 Frederic Thevenet
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.binjr.sources.logs.adapters;
|
||||
|
||||
|
||||
import eu.binjr.common.javafx.controls.ToolButtonBuilder;
|
||||
import eu.binjr.core.data.adapters.AdapterMetadata;
|
||||
import eu.binjr.core.data.adapters.BaseDataAdapterInfo;
|
||||
import eu.binjr.core.data.adapters.SourceLocality;
|
||||
import eu.binjr.core.data.adapters.VisualizationType;
|
||||
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
|
||||
import eu.binjr.core.preferences.AppEnvironment;
|
||||
|
||||
|
||||
/**
|
||||
* Defines the metadata associated with LogsDataAdapterInfo.
|
||||
*
|
||||
* @author Frederic Thevenet
|
||||
*/
|
||||
@AdapterMetadata(
|
||||
name = "Logs",
|
||||
description = "Log Files Data Adapter",
|
||||
copyright = AppEnvironment.COPYRIGHT_NOTICE,
|
||||
license = AppEnvironment.LICENSE,
|
||||
siteUrl = AppEnvironment.HTTP_WWW_BINJR_EU,
|
||||
adapterClass = LogsDataAdapter.class,
|
||||
dialogClass = LogsDataAdapterDialog.class,
|
||||
preferencesClass = LogsAdapterPreferences.class,
|
||||
sourceLocality = SourceLocality.LOCAL,
|
||||
apiLevel = AppEnvironment.PLUGIN_API_LEVEL,
|
||||
visualizationType = VisualizationType.EVENTS
|
||||
)
|
||||
public class LogsDataAdapterInfo extends BaseDataAdapterInfo {
|
||||
|
||||
/**
|
||||
* Initialises a new instance of the {@link LogsDataAdapterInfo} class.
|
||||
*
|
||||
* @throws CannotInitializeDataAdapterException if the adapter's initialization failed
|
||||
*/
|
||||
public LogsDataAdapterInfo() throws CannotInitializeDataAdapterException {
|
||||
super(LogsDataAdapterInfo.class);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Copyright 2020 Frederic Thevenet
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# Text Data adapter service implementation
|
||||
eu.binjr.sources.logs.adapters.LogsDataAdapterInfo
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2022 Frederic Thevenet
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package eu.binjr.sources.logs.data.parsers;
|
||||
|
||||
|
||||
import eu.binjr.core.data.indexes.parser.LogEventFormat;
|
||||
import eu.binjr.core.data.indexes.parser.profile.BuiltInParsingProfile;
|
||||
import eu.binjr.core.data.indexes.parser.profile.ParsingProfile;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneId;
|
||||
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ParserTest {
|
||||
|
||||
@Test
|
||||
public void parseBinjrLogsFull() {
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.ISO, "[2020/07/21-02:56:42.460] [info] Hello World!"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.ISO, "[2020-11-13 19:59:22.627] [INFO ] Hello world!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBinjrLogsNoFraction() {
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.ISO, "[2020/07/21-02:56:42] [info] Hello World!"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.ISO, "[2020-11-13 19:59:22] [INFO ] Hello world!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBinjrLogsUnknownSeverity() {
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.ISO, "[2020-11-13 19:59:22.627] [FOO ] Hello world!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBinjrLogsNoSeverity() {
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.ISO, "[2020-11-13 19:59:22.627] Hello world!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBinjrStrictFull() {
|
||||
assertFalse(parsingTest(BuiltInParsingProfile.BINJR_STRICT, "[2020/07/21-02:56:42.460] [info] Hello World!"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.BINJR_STRICT, "[2020-11-13 19:59:22.627] [INFO ] Hello world!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBinjrStrictNoFraction() {
|
||||
assertFalse(parsingTest(BuiltInParsingProfile.BINJR_STRICT, "[2020/07/21-02:56:42] [info] Hello World!"));
|
||||
assertFalse(parsingTest(BuiltInParsingProfile.BINJR_STRICT, "[2020-11-13 19:59:22] [INFO ] Hello world!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBinjrStrictUnknownSeverity() {
|
||||
assertFalse(parsingTest(BuiltInParsingProfile.BINJR_STRICT, "[2020-11-13 19:59:22.627] [FOO ] Hello world!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBinjrStrictNoSeverity() {
|
||||
assertFalse(parsingTest(BuiltInParsingProfile.BINJR_STRICT, "[2020-11-13 19:59:22.627] Hello world!"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void parseGc() {
|
||||
// Logs with uptime
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.144s][info][gc,phases ] GC(59) Phase 4: Compact heap 11.810ms"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.151s][info][gc,heap ] GC(59) Eden regions: 9->0(130)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.151s][info][gc,heap ] GC(59) Survivor regions: 0->0(12)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.151s][info][gc,heap ] GC(59) Old regions: 139->137"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.151s][info][gc,heap ] GC(59) Archive regions: 0->0"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.151s][info][gc,heap ] GC(59) Humongous regions: 10->10"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.151s][info][gc,metaspace ] GC(59) Metaspace: 48468K(49200K)->48468K(49200K) NonClass: 41947K(42416K)->41947K(42416K) Class: 6521K(6784K)->6521K(6784K)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.151s][info][gc ] GC(59) Pause Full (System.gc()) 153M->143M(477M) 94.613ms"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[54.152s][info][gc,cpu ] GC(59) User=0.66s Sys=0.00s Real=0.10s"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[65.250s][info][gc,heap,exit ] Heap"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[65.250s][info][gc,heap,exit ] garbage-first heap total 488448K, used 163531K [0x0000000080000000, 0x0000000100000000)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[65.250s][info][gc,heap,exit ] region size 1024K, 17 young (17408K), 0 survivors (0K)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[65.250s][info][gc,heap,exit ] Metaspace used 48659K, capacity 49344K, committed 49456K, reserved 1091584K"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[65.250s][info][gc,heap,exit ] class space used 6554K, capacity 6778K, committed 6784K, reserved 1048576K"));
|
||||
|
||||
//Logs with time (ISO)
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.093+0200][info ][safepoint ] Application time: 0.4142997 seconds"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.094+0200][info ][safepoint ] Entering safepoint region: G1CollectForAllocation"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.097+0200][info ][gc,start ] GC(846) Pause Young (Normal) (GCLocker Initiated GC)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.097+0200][info ][gc,task ] GC(846) Using 143 workers of 143 for evacuation"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,phases ] GC(846) Pre Evacuate Collection Set: 32.2ms"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,phases ] GC(846) Evacuate Collection Set: 43.3ms"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,phases ] GC(846) Post Evacuate Collection Set: 9.2ms"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,phases ] GC(846) Other: 8.9ms"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,heap ] GC(846) Eden regions: 14->0(949)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,heap ] GC(846) Survivor regions: 10->11(960)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,heap ] GC(846) Old regions: 7420->7420"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,heap ] GC(846) Humongous regions: 11634->11632"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc,metaspace ] GC(846) Metaspace: 231059K(255744K)->231059K(255744K)"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.190+0200][info ][gc ] GC(846) Pause Young (Normal) (GCLocker Initiated GC) 610464M->609992M(614400M) 92.765ms"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.191+0200][info ][gc,cpu ] GC(846) User=4.18s Sys=0.03s Real=0.09s"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.192+0200][info ][safepoint ] Leaving safepoint region"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.192+0200][info ][safepoint ] Total time for which application threads were stopped: 0.0987139 seconds, Stopping threads took: 0.0008103 seconds"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.224+0200][info ][safepoint ] Application time: 0.0321887 seconds"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.286+0200][info ][safepoint ] Entering safepoint region: G1CollectForAllocation"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.292+0200][debug][gc,jni ] Setting _needs_gc. Thread `\"VM Thread\" 62 locked."));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.292+0200][info ][safepoint ] Leaving safepoint region"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.292+0200][info ][safepoint ] Total time for which application threads were stopped: 0.0685306 seconds, Stopping threads took: 0.0622806 seconds"));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.299+0200][debug][gc,jni ] Allocation failed. Thread stalled by JNI critical section. Thread \"ForkJoinPool-280-worker-45_CommitThrottle#commit: top level tasks for transactions\" 48 locked."));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.299+0200][debug][gc,jni ] Allocation failed. Thread stalled by JNI critical section. Thread \"ForkJoinPool-280-worker-111_CommitThrottle#commit: top level tasks for transactions\" 46 locked."));
|
||||
assertTrue(parsingTest(BuiltInParsingProfile.JVM, "[2023-02-22T06:46:55.305+0200][debug][gc,jni ] Allocation failed. Thread stalled by JNI critical section. Thread \"ForkJoinPool-280-worker-25_CommitThrottle#commit: top level tasks for transactions\" 46 locked."));
|
||||
}
|
||||
|
||||
private boolean parsingTest(ParsingProfile profile, String text) {
|
||||
var p = new LogEventFormat(profile, ZoneId.systemDefault(), StandardCharsets.UTF_8);
|
||||
var res = p.parse(new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
|
||||
System.out.println(res);
|
||||
return res.iterator().next() != null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user