added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
34
bundles/org.openhab.binding.logreader/.classpath
Normal file
34
bundles/org.openhab.binding.logreader/.classpath
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="src/3rdparty/java"/>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.logreader/.project
Normal file
23
bundles/org.openhab.binding.logreader/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.logreader</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
20
bundles/org.openhab.binding.logreader/NOTICE
Normal file
20
bundles/org.openhab.binding.logreader/NOTICE
Normal file
@@ -0,0 +1,20 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
commons-io (files under src/3rdparty)
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://commons.apache.org/proper/commons-io/
|
||||
* Source: https://github.com/paulianttila/commons-io/tree/IO-279-fix
|
||||
127
bundles/org.openhab.binding.logreader/README.md
Normal file
127
bundles/org.openhab.binding.logreader/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Log Reader Binding
|
||||
|
||||
This binding reads and analyzes log files. Search patterns are fully configurable, therefore different kind of log files should be possible to monitor by this binding.
|
||||
When certain log events are recognized, openHAB rules can be used to send notification about the event e.g by email for further analysis.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding supports one ThingType: `reader`.
|
||||
A reader supports 3 separate channels
|
||||
|
||||
* One for errors
|
||||
* one for warnings
|
||||
* one custom channel for other purposes.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The `reader` Thing has the following configuration parameters:
|
||||
|
||||
| Parameter | Type | Required | Default if omitted | Description |
|
||||
| ------------------------------| ------- | -------- | -------------------------------- |-----------------------------------------------------------------------------------------|
|
||||
| `filePath` | String | yes | `${OPENHAB_LOGDIR}/openhab.log` | Path to log file. ${OPENHAB_LOGDIR} is automatically replaced by the correct directory. |
|
||||
| `refreshRate` | integer | no | `1000` | Time in milliseconds between individual log reads. |
|
||||
| `errorPatterns` | String | no | `ERROR+` | Search patterns separated by \| character for error events. |
|
||||
| `errorBlacklistingPatterns` | String | no | | Search patterns for blacklisting unwanted error events separated by \| character. |
|
||||
| `warningPatterns` | String | no | `WARN+` | Search patterns separated by \| character for warning events. |
|
||||
| `warningBlacklistingPatterns` | String | no | | Search patterns for blacklisting unwanted warning events separated by \| character. |
|
||||
| `customPatterns` | String | no | | Search patterns separated by \| character for custom events. |
|
||||
| `customBlacklistingPatterns` | String | no | | Search patterns for blacklisting unwanted custom events separated by \| character. |
|
||||
|
||||
Search patterns follows Java regular expression syntax. See https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html.
|
||||
|
||||
## Channels
|
||||
|
||||
List of channels
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
| ------------------ | ------------ | ----------------------------------------------------------- |
|
||||
| `lastErrorEvent` | `String` | Displays content of last [ERROR] event |
|
||||
| `lastWarningEvent` | `String` | Displays content of last [WARN] event |
|
||||
| `lastCustomEvent` | `String` | Displays content of last [CUSTOM] event |
|
||||
| `errorEvents` | `Number` | Displays number of [ERROR] lines matched to search pattern |
|
||||
| `warningEvents` | `Number` | Displays number of [WARN] lines matched to search pattern |
|
||||
| `customEvents` | `Number` | Displays number of [CUSTOM] lines matched to search pattern |
|
||||
| `logRotated` | `DateTime` | Last time when log rotated recognized |
|
||||
| `newErrorEvent` | - | Trigger channel for last [ERROR] line |
|
||||
| `newWarningEvent` | - | Trigger channel for last [WARN] line |
|
||||
| `newCustomEvent` | - | Trigger channel for last [CUSTOM] line |
|
||||
|
||||
## Examples
|
||||
|
||||
### example.things
|
||||
|
||||
```xtend
|
||||
|
||||
logreader:reader:openhablog[ refreshRate=1000, errorPatterns="ERROR+", errorBlacklistingPatterns="annoying error which should be ignored|Another annoying error which should be ignored" ]
|
||||
|
||||
```
|
||||
|
||||
### example.items
|
||||
|
||||
```xtend
|
||||
|
||||
DateTime logreaderLogRotated "Last Log Rotation [%1$tY.%1$tm.%1$te %1$tR]" <time> { channel="logreader:reader:openhablog:logRotated" }
|
||||
Number logreaderErrors "Error events matched [%d]" <alarm> { channel="logreader:reader:openhablog:errorEvents" }
|
||||
String logreaderLastError "Last error [%s]" { channel="logreader:reader:openhablog:lastErrorEvent" }
|
||||
Number logreaderWarnings "Warning events matched [%d]" <alarm> { channel="logreader:reader:openhablog:warningEvents" }
|
||||
String logreaderLastWarning "Last warning [%s]" { channel="logreader:reader:openhablog:lastWarningEvent" }
|
||||
Number logreaderCustoms "Custom events matched [%d]" <alarm> { channel="logreader:reader:openhablog:customEvents" }
|
||||
String logreaderLastCustom "Last Custom [%s]" { channel="logreader:reader:openhablog:lastCustomEvent" }
|
||||
|
||||
```
|
||||
|
||||
### example.sitemap
|
||||
|
||||
```xtend
|
||||
|
||||
sitemap logreader_example label="Example" {
|
||||
Frame label="openHAB Log Reader" {
|
||||
Text item=logreaderErrors
|
||||
Text item=logreaderLastError
|
||||
Text item=logreaderWarnings
|
||||
Text item=logreaderLastWarning
|
||||
Text item=logreaderCustoms
|
||||
Text item=logreaderLastCustom
|
||||
Text item=logreaderLogRotated
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### example.rules
|
||||
|
||||
```xtend
|
||||
rule "LogReader"
|
||||
when
|
||||
Channel "logreader:reader:openhablog:newErrorEvent" triggered
|
||||
then
|
||||
// do something
|
||||
end
|
||||
```
|
||||
|
||||
Use the rules with your Telegram Bot (need openHAB Telegram Action installed and configured)
|
||||
|
||||
```xtend
|
||||
rule "LogReader"
|
||||
when
|
||||
Channel 'logreader:reader:openhablog:newErrorEvent' triggered
|
||||
then
|
||||
// do something
|
||||
sendTelegram("bot3", "*ERROR* LogReader Event!\n%s Errors are in the log! Here is the last row of it:\n`%s`", logreaderErrors.state.toString, logreaderLastError.state.toString)
|
||||
end
|
||||
```
|
||||
|
||||
Be careful when sending e.g. email notifications.
|
||||
You could easily send thousand of *spam* emails in short period if e.g. one binding is in error loop.
|
||||
|
||||
### Thing status
|
||||
|
||||
Check thing status for errors.
|
||||
|
||||
### Verbose logging
|
||||
|
||||
Enable DEBUG logging in karaf console to see more precise error messages:
|
||||
|
||||
`log:set DEBUG org.openhab.binding.logreader`
|
||||
|
||||
See [openHAB2 logging docs](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) for more help.
|
||||
39
bundles/org.openhab.binding.logreader/pom.xml
Normal file
39
bundles/org.openhab.binding.logreader/pom.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.logreader</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Log Reader Binding</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/3rdparty</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
653
bundles/org.openhab.binding.logreader/src/3rdparty/java/org/apache/commons/io/input/Tailer.java
vendored
Normal file
653
bundles/org.openhab.binding.logreader/src/3rdparty/java/org/apache/commons/io/input/Tailer.java
vendored
Normal file
@@ -0,0 +1,653 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.commons.io.input;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
/**
|
||||
* Simple implementation of the unix "tail -f" functionality.
|
||||
*
|
||||
* <h2>1. Create a TailerListener implementation</h2>
|
||||
* <p>
|
||||
* First you need to create a {@link TailerListener} implementation
|
||||
* ({@link TailerListenerAdapter} is provided for convenience so that you don't have to
|
||||
* implement every method).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* For example:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public class MyTailerListener extends TailerListenerAdapter {
|
||||
* public void handle(String line) {
|
||||
* System.out.println(line);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>2. Using a Tailer</h2>
|
||||
*
|
||||
* <p>
|
||||
* You can create and use a Tailer in one of three ways:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Using one of the static helper methods:
|
||||
* <ul>
|
||||
* <li>{@link Tailer#create(File, TailerListener)}</li>
|
||||
* <li>{@link Tailer#create(File, TailerListener, long)}</li>
|
||||
* <li>{@link Tailer#create(File, TailerListener, long, boolean)}</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Using an {@link java.util.concurrent.Executor}</li>
|
||||
* <li>Using an {@link Thread}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* An example of each of these is shown below.
|
||||
* </p>
|
||||
*
|
||||
* <h3>2.1 Using the static helper method</h3>
|
||||
*
|
||||
* <pre>
|
||||
* TailerListener listener = new MyTailerListener();
|
||||
* Tailer tailer = Tailer.create(file, listener, delay);
|
||||
* </pre>
|
||||
*
|
||||
* <h3>2.2 Using an Executor</h3>
|
||||
*
|
||||
* <pre>
|
||||
* TailerListener listener = new MyTailerListener();
|
||||
* Tailer tailer = new Tailer(file, listener, delay);
|
||||
*
|
||||
* // stupid executor impl. for demo purposes
|
||||
* Executor executor = new Executor() {
|
||||
* public void execute(Runnable command) {
|
||||
* command.run();
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* executor.execute(tailer);
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <h3>2.3 Using a Thread</h3>
|
||||
*
|
||||
* <pre>
|
||||
* TailerListener listener = new MyTailerListener();
|
||||
* Tailer tailer = new Tailer(file, listener, delay);
|
||||
* Thread thread = new Thread(tailer);
|
||||
* thread.setDaemon(true); // optional
|
||||
* thread.start();
|
||||
* </pre>
|
||||
*
|
||||
* <h2>3. Stopping a Tailer</h2>
|
||||
* <p>
|
||||
* Remember to stop the tailer when you have done with it:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* tailer.stop();
|
||||
* </pre>
|
||||
*
|
||||
* <h2>4. Interrupting a Tailer</h2>
|
||||
* <p>
|
||||
* You can interrupt the thread a tailer is running on by calling {@link Thread#interrupt()}.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* thread.interrupt();
|
||||
* </pre>
|
||||
* <p>
|
||||
* If you interrupt a tailer, the tailer listener is called with the {@link InterruptedException}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The file is read using the default charset; this can be overridden if necessary
|
||||
* </p>
|
||||
*
|
||||
* @see TailerListener
|
||||
* @see TailerListenerAdapter
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
* @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()}
|
||||
*/
|
||||
public class Tailer implements Runnable {
|
||||
|
||||
private static final int EOF = -1;
|
||||
|
||||
private static final int DEFAULT_DELAY_MILLIS = 1000;
|
||||
|
||||
private static final String RAF_MODE = "r";
|
||||
|
||||
private static final int DEFAULT_BUFSIZE = 4096;
|
||||
|
||||
// The default charset used for reading files
|
||||
private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
|
||||
|
||||
/**
|
||||
* Buffer on top of RandomAccessFile.
|
||||
*/
|
||||
private final byte inbuf[];
|
||||
|
||||
/**
|
||||
* The file which will be tailed.
|
||||
*/
|
||||
private final File file;
|
||||
|
||||
/**
|
||||
* The character set that will be used to read the file.
|
||||
*/
|
||||
private final Charset cset;
|
||||
|
||||
/**
|
||||
* The amount of time to wait for the file to be updated.
|
||||
*/
|
||||
private final long delayMillis;
|
||||
|
||||
/**
|
||||
* Whether to tail from the end or start of file
|
||||
*/
|
||||
private final boolean end;
|
||||
|
||||
/**
|
||||
* The listener to notify of events when tailing.
|
||||
*/
|
||||
private final TailerListener listener;
|
||||
|
||||
/**
|
||||
* Whether to close and reopen the file whilst waiting for more input.
|
||||
*/
|
||||
private final boolean reOpen;
|
||||
|
||||
/**
|
||||
* Whether to ignore reading modified file with the same length.
|
||||
*/
|
||||
private final boolean ignoreNew;
|
||||
|
||||
/**
|
||||
* The tailer will run as long as this value is true.
|
||||
*/
|
||||
private volatile boolean run = true;
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s.
|
||||
*
|
||||
* @param file The file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener) {
|
||||
this(file, listener, DEFAULT_DELAY_MILLIS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, starting from the beginning.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener, final long delayMillis) {
|
||||
this(file, listener, delayMillis, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, with a delay other than the default 1.0s.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
|
||||
this(file, listener, delayMillis, end, DEFAULT_BUFSIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, with a delay other than the default 1.0s.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen if true, close and reopen the file between reading chunks
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
|
||||
final boolean reOpen) {
|
||||
this(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, with a delay other than the default 1.0s.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen if true, close and reopen the file between reading chunks
|
||||
* @param ignoreNew if true, will ignore reading modified file with the same length.
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
|
||||
final boolean reOpen, final boolean ignoreNew) {
|
||||
this(file, listener, delayMillis, end, reOpen, ignoreNew, DEFAULT_BUFSIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, with a specified buffer size.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param bufSize Buffer size
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
|
||||
final int bufSize) {
|
||||
this(file, listener, delayMillis, end, false, bufSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, with a specified buffer size.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen if true, close and reopen the file between reading chunks
|
||||
* @param bufSize Buffer size
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
|
||||
final boolean reOpen, final int bufSize) {
|
||||
this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, false, bufSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, with a specified buffer size.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen if true, close and reopen the file between reading chunks
|
||||
* @param ignoreNew if true, will ignore reading modified file with the same length.
|
||||
* @param bufSize Buffer size
|
||||
*/
|
||||
public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
|
||||
final boolean reOpen, final boolean ignoreNew, final int bufSize) {
|
||||
this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, ignoreNew, bufSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tailer for the given file, with a specified buffer size.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param cset the Charset to be used for reading the file
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen if true, close and reopen the file between reading chunks
|
||||
* @param ignoreNew if true, will ignore reading modified file with the same length.
|
||||
* @param bufSize Buffer size
|
||||
*/
|
||||
public Tailer(final File file, final Charset cset, final TailerListener listener, final long delayMillis,
|
||||
final boolean end, final boolean reOpen, final boolean ignoreNew, final int bufSize) {
|
||||
this.file = file;
|
||||
this.delayMillis = delayMillis;
|
||||
this.end = end;
|
||||
|
||||
this.inbuf = new byte[bufSize];
|
||||
|
||||
// Save and prepare the listener
|
||||
this.listener = listener;
|
||||
listener.init(this);
|
||||
this.reOpen = reOpen;
|
||||
this.ignoreNew = ignoreNew;
|
||||
this.cset = cset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param bufSize buffer size.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
|
||||
final boolean end, final int bufSize) {
|
||||
return create(file, listener, delayMillis, end, false, bufSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen whether to close/reopen the file between chunks
|
||||
* @param bufSize buffer size.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
|
||||
final boolean end, final boolean reOpen, final int bufSize) {
|
||||
return create(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, false, bufSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen whether to close/reopen the file between chunks
|
||||
* @param ignoreNew if true, will ignore reading modified file with the same length.
|
||||
* @param bufSize buffer size.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
|
||||
final boolean end, final boolean reOpen, final boolean ignoreNew, final int bufSize) {
|
||||
return create(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, ignoreNew, bufSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param charset the character set to use for reading the file
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen whether to close/reopen the file between chunks
|
||||
* @param ignoreNew if true, will ignore reading modified file with the same length.
|
||||
* @param bufSize buffer size.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final Charset charset, final TailerListener listener,
|
||||
final long delayMillis, final boolean end, final boolean reOpen, final boolean ignoreNew,
|
||||
final int bufSize) {
|
||||
final Tailer tailer = new Tailer(file, charset, listener, delayMillis, end, reOpen, ignoreNew, bufSize);
|
||||
final Thread thread = new Thread(tailer);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
return tailer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file with default buffer size.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
|
||||
final boolean end) {
|
||||
return create(file, listener, delayMillis, end, DEFAULT_BUFSIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file with default buffer size.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen whether to close/reopen the file between chunks
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
|
||||
final boolean end, final boolean reOpen) {
|
||||
return create(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file with default buffer size.
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
|
||||
* @param reOpen whether to close/reopen the file between chunks
|
||||
* @param ignoreNew if true, will ignore reading modified file with the same length.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
|
||||
final boolean end, final boolean reOpen, final boolean ignoreNew) {
|
||||
return create(file, listener, delayMillis, end, reOpen, ignoreNew, DEFAULT_BUFSIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file, starting at the beginning of the file
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @param delayMillis the delay between checks of the file for new content in milliseconds.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener, final long delayMillis) {
|
||||
return create(file, listener, delayMillis, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a Tailer for the given file, starting at the beginning of the file
|
||||
* with the default delay of 1.0s
|
||||
*
|
||||
* @param file the file to follow.
|
||||
* @param listener the TailerListener to use.
|
||||
* @return The new tailer
|
||||
*/
|
||||
public static Tailer create(final File file, final TailerListener listener) {
|
||||
return create(file, listener, DEFAULT_DELAY_MILLIS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file.
|
||||
*
|
||||
* @return the file
|
||||
*/
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to keep on running.
|
||||
*
|
||||
* @return whether to keep on running.
|
||||
* @since 2.5
|
||||
*/
|
||||
protected boolean getRun() {
|
||||
return run;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the delay in milliseconds.
|
||||
*
|
||||
* @return the delay in milliseconds.
|
||||
*/
|
||||
public long getDelay() {
|
||||
return delayMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Follows changes in the file, calling the TailerListener's handle method for each new line.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
RandomAccessFile reader = null;
|
||||
try {
|
||||
long last = 0; // The last time the file was checked for changes
|
||||
long position = 0; // position within the file
|
||||
// Open the file
|
||||
while (getRun() && reader == null) {
|
||||
try {
|
||||
reader = new RandomAccessFile(file, RAF_MODE);
|
||||
} catch (final FileNotFoundException e) {
|
||||
listener.fileNotFound();
|
||||
}
|
||||
if (reader == null) {
|
||||
Thread.sleep(delayMillis);
|
||||
} else {
|
||||
// The current position in the file
|
||||
position = end ? file.length() : 0;
|
||||
last = file.lastModified();
|
||||
reader.seek(position);
|
||||
}
|
||||
}
|
||||
while (getRun()) {
|
||||
final boolean newer = FileUtils.isFileNewer(file, last); // IO-279, must be done first
|
||||
// Check the file length to see if it was rotated
|
||||
final long length = file.length();
|
||||
if (length < position) {
|
||||
// File was rotated
|
||||
listener.fileRotated();
|
||||
// Reopen the reader after rotation ensuring that the old file is closed iff we re-open it
|
||||
// successfully
|
||||
try (RandomAccessFile save = reader) {
|
||||
reader = new RandomAccessFile(file, RAF_MODE);
|
||||
// At this point, we're sure that the old file is rotated
|
||||
// Finish scanning the old file and then we'll start with the new one
|
||||
try {
|
||||
readLines(save);
|
||||
} catch (IOException ioe) {
|
||||
listener.handle(ioe);
|
||||
}
|
||||
position = 0;
|
||||
} catch (final FileNotFoundException e) {
|
||||
// in this case we continue to use the previous reader and position values
|
||||
listener.fileNotFound();
|
||||
Thread.sleep(delayMillis);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
// File was not rotated
|
||||
// See if the file needs to be read again
|
||||
if (length > position) {
|
||||
// The file has more content than it did last time
|
||||
position = readLines(reader);
|
||||
last = file.lastModified();
|
||||
} else if (newer) {
|
||||
if (!ignoreNew) {
|
||||
/*
|
||||
* This can happen if the file is truncated or overwritten with the exact same length of
|
||||
* information. In cases like this, the file position needs to be reset
|
||||
*/
|
||||
position = 0;
|
||||
reader.seek(position); // cannot be null here
|
||||
|
||||
// Now we can read new lines
|
||||
position = readLines(reader);
|
||||
}
|
||||
last = file.lastModified();
|
||||
}
|
||||
}
|
||||
if (reOpen && reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
Thread.sleep(delayMillis);
|
||||
if (getRun() && reOpen) {
|
||||
reader = new RandomAccessFile(file, RAF_MODE);
|
||||
reader.seek(position);
|
||||
}
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
listener.handle(e);
|
||||
} catch (final Exception e) {
|
||||
listener.handle(e);
|
||||
} finally {
|
||||
try {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
listener.handle(e);
|
||||
}
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the tailer to complete its current loop and return.
|
||||
*/
|
||||
public void stop() {
|
||||
this.run = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read new lines.
|
||||
*
|
||||
* @param reader The file to read
|
||||
* @return The new position after the lines have been read
|
||||
* @throws java.io.IOException if an I/O error occurs.
|
||||
*/
|
||||
private long readLines(final RandomAccessFile reader) throws IOException {
|
||||
try (ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64)) {
|
||||
long pos = reader.getFilePointer();
|
||||
long rePos = pos; // position to re-read
|
||||
int num;
|
||||
boolean seenCR = false;
|
||||
while (getRun() && ((num = reader.read(inbuf)) != EOF)) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
final byte ch = inbuf[i];
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
seenCR = false; // swallow CR before LF
|
||||
listener.handle(new String(lineBuf.toByteArray(), cset));
|
||||
lineBuf.reset();
|
||||
rePos = pos + i + 1;
|
||||
break;
|
||||
case '\r':
|
||||
if (seenCR) {
|
||||
lineBuf.write('\r');
|
||||
}
|
||||
seenCR = true;
|
||||
break;
|
||||
default:
|
||||
if (seenCR) {
|
||||
seenCR = false; // swallow final CR
|
||||
listener.handle(new String(lineBuf.toByteArray(), cset));
|
||||
lineBuf.reset();
|
||||
rePos = pos + i + 1;
|
||||
}
|
||||
lineBuf.write(ch);
|
||||
}
|
||||
}
|
||||
pos = reader.getFilePointer();
|
||||
}
|
||||
|
||||
reader.seek(rePos); // Ensure we can re-read if necessary
|
||||
|
||||
if (listener instanceof TailerListenerAdapter) {
|
||||
((TailerListenerAdapter) listener).endOfFileReached();
|
||||
}
|
||||
|
||||
return rePos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.commons.io.input;
|
||||
|
||||
/**
|
||||
* Listener for events from a {@link Tailer}.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface TailerListener {
|
||||
|
||||
/**
|
||||
* The tailer will call this method during construction,
|
||||
* giving the listener a method of stopping the tailer.
|
||||
*
|
||||
* @param tailer the tailer.
|
||||
*/
|
||||
void init(Tailer tailer);
|
||||
|
||||
/**
|
||||
* This method is called if the tailed file is not found.
|
||||
* <p>
|
||||
* <b>Note:</b> this is called from the tailer thread.
|
||||
*/
|
||||
void fileNotFound();
|
||||
|
||||
/**
|
||||
* Called if a file rotation is detected.
|
||||
*
|
||||
* This method is called before the file is reopened, and fileNotFound may
|
||||
* be called if the new file has not yet been created.
|
||||
* <p>
|
||||
* <b>Note:</b> this is called from the tailer thread.
|
||||
*/
|
||||
void fileRotated();
|
||||
|
||||
/**
|
||||
* Handles a line from a Tailer.
|
||||
* <p>
|
||||
* <b>Note:</b> this is called from the tailer thread.
|
||||
*
|
||||
* @param line the line.
|
||||
*/
|
||||
void handle(String line);
|
||||
|
||||
/**
|
||||
* Handles an Exception .
|
||||
* <p>
|
||||
* <b>Note:</b> this is called from the tailer thread.
|
||||
*
|
||||
* @param ex the exception.
|
||||
*/
|
||||
void handle(Exception ex);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.commons.io.input;
|
||||
|
||||
/**
|
||||
* {@link TailerListener} Adapter.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class TailerListenerAdapter implements TailerListener {
|
||||
|
||||
/**
|
||||
* The tailer will call this method during construction,
|
||||
* giving the listener a method of stopping the tailer.
|
||||
*
|
||||
* @param tailer the tailer.
|
||||
*/
|
||||
@Override
|
||||
public void init(final Tailer tailer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called if the tailed file is not found.
|
||||
*/
|
||||
@Override
|
||||
public void fileNotFound() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if a file rotation is detected.
|
||||
*
|
||||
* This method is called before the file is reopened, and fileNotFound may
|
||||
* be called if the new file has not yet been created.
|
||||
*/
|
||||
@Override
|
||||
public void fileRotated() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a line from a Tailer.
|
||||
*
|
||||
* @param line the line.
|
||||
*/
|
||||
@Override
|
||||
public void handle(final String line) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an Exception .
|
||||
*
|
||||
* @param ex the exception.
|
||||
*/
|
||||
@Override
|
||||
public void handle(final Exception ex) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called each time the Tailer reaches the end of the file.
|
||||
*
|
||||
* <b>Note:</b> this is called from the tailer thread.
|
||||
*
|
||||
* Note: a future version of commons-io will pull this method up to the TailerListener interface,
|
||||
* for now clients must subclass this class to use this feature.
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public void endOfFileReached() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.logreader-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-logreader" description="Log Reader Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.logreader/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link LogReaderBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Miika Jukka - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LogReaderBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "logreader";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_READER = new ThingTypeUID(BINDING_ID, "reader");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_LASTWARNING = "lastWarningEvent";
|
||||
public static final String CHANNEL_LASTERROR = "lastErrorEvent";
|
||||
public static final String CHANNEL_LASTCUSTOMEVENT = "lastCustomEvent";
|
||||
public static final String CHANNEL_WARNINGS = "warningEvents";
|
||||
public static final String CHANNEL_ERRORS = "errorEvents";
|
||||
public static final String CHANNEL_CUSTOMEVENTS = "customEvents";
|
||||
public static final String CHANNEL_LOGROTATED = "logRotated";
|
||||
|
||||
public static final String CHANNEL_NEWWARNING = "newWarningEvent";
|
||||
public static final String CHANNEL_NEWERROR = "newErrorEvent";
|
||||
public static final String CHANNEL_NEWCUSTOM = "newCustomEvent";
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal;
|
||||
|
||||
import static org.openhab.binding.logreader.internal.LogReaderBindingConstants.THING_READER;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.logreader.internal.filereader.FileTailer;
|
||||
import org.openhab.binding.logreader.internal.handler.LogHandler;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link LogReaderHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Miika Jukka - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.logreader")
|
||||
@NonNullByDefault
|
||||
public class LogReaderHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_READER).collect(Collectors.toSet()));
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_READER)) {
|
||||
return new LogHandler(thing, new FileTailer());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.config;
|
||||
|
||||
/**
|
||||
* Configuration class for {@link LogReaderBinding} binding.
|
||||
*
|
||||
* @author Pauli Anttila - Initial contribution
|
||||
*/
|
||||
public class LogReaderConfiguration {
|
||||
public String filePath;
|
||||
public int refreshRate;
|
||||
public String warningPatterns;
|
||||
public String warningBlacklistingPatterns;
|
||||
public String errorPatterns;
|
||||
public String errorBlacklistingPatterns;
|
||||
public String customPatterns;
|
||||
public String customBlacklistingPatterns;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + "filePath=" + filePath + ", refreshRate=" + refreshRate + ", warningPatterns=" + warningPatterns
|
||||
+ ", warningBlacklistingPatterns=" + warningBlacklistingPatterns + ", errorPatterns=" + errorPatterns
|
||||
+ ", errorBlacklistingPatterns=" + errorBlacklistingPatterns + ", customPatterns=" + customPatterns
|
||||
+ ", customBlacklistingPatterns=" + customBlacklistingPatterns + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.filereader;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.openhab.binding.logreader.internal.filereader.api.FileReaderListener;
|
||||
import org.openhab.binding.logreader.internal.filereader.api.LogFileReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for LogFileReader implementations. Implements base functions which are same for all LogFileReaders.
|
||||
*
|
||||
* @author Pauli Anttila - Initial contribution
|
||||
*/
|
||||
public abstract class AbstractLogFileReader implements LogFileReader {
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractLogFileReader.class);
|
||||
|
||||
private List<FileReaderListener> fileReaderListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean registerListener(FileReaderListener fileReaderListener) {
|
||||
Objects.requireNonNull(fileReaderListener, "It's not allowed to pass a null FileReaderListener.");
|
||||
return fileReaderListeners.contains(fileReaderListener) ? false : fileReaderListeners.add(fileReaderListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterListener(FileReaderListener fileReaderListener) {
|
||||
Objects.requireNonNull(fileReaderListener, "It's not allowed to pass a null FileReaderListener.");
|
||||
return fileReaderListeners.remove(fileReaderListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send file not found event to all registered listeners.
|
||||
*
|
||||
*/
|
||||
public void sendFileNotFoundToListeners() {
|
||||
for (FileReaderListener fileReaderListener : fileReaderListeners) {
|
||||
try {
|
||||
fileReaderListener.fileNotFound();
|
||||
} catch (Exception e) {
|
||||
// catch all exceptions give all handlers a fair chance of handling the messages
|
||||
logger.debug("An exception occurred while calling the FileReaderListener. ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send read log line to all registered listeners.
|
||||
*
|
||||
*/
|
||||
public void sendLineToListeners(String line) {
|
||||
for (FileReaderListener fileReaderListener : fileReaderListeners) {
|
||||
try {
|
||||
fileReaderListener.handle(line);
|
||||
} catch (Exception e) {
|
||||
// catch all exceptions give all handlers a fair chance of handling the messages
|
||||
logger.debug("An exception occurred while calling the FileReaderListener. ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send file rotation event to all registered listeners.
|
||||
*
|
||||
*/
|
||||
public void sendFileRotationToListeners() {
|
||||
for (FileReaderListener fileReaderListener : fileReaderListeners) {
|
||||
try {
|
||||
fileReaderListener.fileRotated();
|
||||
} catch (Exception e) {
|
||||
// catch all exceptions give all handlers a fair chance of handling the messages
|
||||
logger.debug("An exception occurred while calling the FileReaderListener. ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send exception event to all registered listeners.
|
||||
*
|
||||
*/
|
||||
public void sendExceptionToListeners(Exception e) {
|
||||
for (FileReaderListener fileReaderListener : fileReaderListeners) {
|
||||
try {
|
||||
fileReaderListener.handle(e);
|
||||
} catch (Exception ex) {
|
||||
// catch all exceptions give all handlers a fair chance of handling the messages
|
||||
logger.debug("An exception occurred while calling the FileReaderListener. ", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.filereader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.commons.io.input.Tailer;
|
||||
import org.apache.commons.io.input.TailerListener;
|
||||
import org.apache.commons.io.input.TailerListenerAdapter;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.logreader.internal.filereader.api.FileReaderException;
|
||||
import org.openhab.binding.logreader.internal.filereader.api.LogFileReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Apache Tailer based log file reader implementation.
|
||||
*
|
||||
* @author Pauli Anttila - Initial contribution
|
||||
*/
|
||||
public class FileTailer extends AbstractLogFileReader implements LogFileReader {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FileTailer.class);
|
||||
|
||||
private Tailer tailer;
|
||||
private ExecutorService executor;
|
||||
|
||||
TailerListener logListener = new TailerListenerAdapter() {
|
||||
|
||||
@Override
|
||||
public void handle(@Nullable String line) {
|
||||
sendLineToListeners(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileNotFound() {
|
||||
sendFileNotFoundToListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(@Nullable Exception e) {
|
||||
sendExceptionToListeners(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileRotated() {
|
||||
sendFileRotationToListeners();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void start(String filePath, long refreshRate) throws FileReaderException {
|
||||
tailer = new Tailer(new File(filePath), logListener, refreshRate, true, false, true);
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
logger.debug("Start executor");
|
||||
executor.execute(tailer);
|
||||
logger.debug("Executor started");
|
||||
} catch (Exception e) {
|
||||
throw new FileReaderException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
logger.debug("Shutdown");
|
||||
if (tailer != null) {
|
||||
tailer.stop();
|
||||
}
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
logger.debug("Shutdown complite");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.filereader.api;
|
||||
|
||||
/**
|
||||
* Exception for file reader errors.
|
||||
*
|
||||
* @author Pauli Anttila - Initial contribution
|
||||
*/
|
||||
public class FileReaderException extends Exception {
|
||||
private static final long serialVersionUID = 1272957002073978608L;
|
||||
|
||||
public FileReaderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FileReaderException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.filereader.api;
|
||||
|
||||
/**
|
||||
* Interface for file reader listeners.
|
||||
*
|
||||
* @author Pauli Anttila - Initial contribution
|
||||
*/
|
||||
public interface FileReaderListener {
|
||||
|
||||
/**
|
||||
* This method is called if the file is not found.
|
||||
*/
|
||||
void fileNotFound();
|
||||
|
||||
/**
|
||||
* This method is called if a file rotation is detected.
|
||||
*
|
||||
*/
|
||||
void fileRotated();
|
||||
|
||||
/**
|
||||
* This method is called when new line is detected.
|
||||
*
|
||||
* @param line the line.
|
||||
*/
|
||||
void handle(String line);
|
||||
|
||||
/**
|
||||
* This method is called when exception has occurred.
|
||||
*
|
||||
* @param ex the exception.
|
||||
*/
|
||||
void handle(Exception ex);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.filereader.api;
|
||||
|
||||
/**
|
||||
* Interface for log file readers.
|
||||
*
|
||||
* @author Pauli Anttila - Initial contribution
|
||||
*/
|
||||
public interface LogFileReader {
|
||||
|
||||
/**
|
||||
* Register listener.
|
||||
*
|
||||
* @param fileReaderListener callback implementation to register.
|
||||
* @return true if registering successfully done.
|
||||
*/
|
||||
boolean registerListener(FileReaderListener fileReaderListener);
|
||||
|
||||
/**
|
||||
* Unregister listener.
|
||||
*
|
||||
* @param fileReaderListener callback implementation to unregister.
|
||||
* @return true if unregistering successfully done.
|
||||
*/
|
||||
boolean unregisterListener(FileReaderListener fileReaderListener);
|
||||
|
||||
/**
|
||||
* Start log file reader.
|
||||
*
|
||||
* @param filePath file to read.
|
||||
* @param refreshRate how often file is read.
|
||||
* @throws FileReaderException
|
||||
*/
|
||||
void start(String filePath, long refreshRate) throws FileReaderException;
|
||||
|
||||
/**
|
||||
* Stop log file reader.
|
||||
*/
|
||||
void stop();
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.handler;
|
||||
|
||||
import static org.openhab.binding.logreader.internal.LogReaderBindingConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import org.openhab.binding.logreader.internal.config.LogReaderConfiguration;
|
||||
import org.openhab.binding.logreader.internal.filereader.api.FileReaderListener;
|
||||
import org.openhab.binding.logreader.internal.filereader.api.LogFileReader;
|
||||
import org.openhab.binding.logreader.internal.searchengine.SearchEngine;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LogReaderHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Miika Jukka - Initial contribution
|
||||
* @author Pauli Anttila - Rewrite
|
||||
*/
|
||||
public class LogHandler extends BaseThingHandler implements FileReaderListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(LogHandler.class);
|
||||
|
||||
private LogReaderConfiguration configuration;
|
||||
|
||||
private LogFileReader fileReader;
|
||||
|
||||
private SearchEngine errorEngine;
|
||||
private SearchEngine warningEngine;
|
||||
private SearchEngine customEngine;
|
||||
|
||||
public LogHandler(Thing thing, LogFileReader fileReader) {
|
||||
super(thing);
|
||||
this.fileReader = fileReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_ERRORS:
|
||||
updateChannel(channelUID, command, errorEngine);
|
||||
break;
|
||||
|
||||
case CHANNEL_WARNINGS:
|
||||
updateChannel(channelUID, command, warningEngine);
|
||||
break;
|
||||
|
||||
case CHANNEL_CUSTOMEVENTS:
|
||||
updateChannel(channelUID, command, customEngine);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.debug("Unsupported command '{}' received for channel '{}'", command, channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
configuration = getConfigAs(LogReaderConfiguration.class);
|
||||
|
||||
configuration.filePath = configuration.filePath.replaceFirst("\\$\\{OPENHAB_LOGDIR\\}",
|
||||
System.getProperty("openhab.logdir"));
|
||||
|
||||
logger.debug("Using configuration: {}", configuration);
|
||||
|
||||
clearCounters();
|
||||
|
||||
try {
|
||||
warningEngine = new SearchEngine(configuration.warningPatterns, configuration.warningBlacklistingPatterns);
|
||||
errorEngine = new SearchEngine(configuration.errorPatterns, configuration.errorBlacklistingPatterns);
|
||||
customEngine = new SearchEngine(configuration.customPatterns, configuration.customBlacklistingPatterns);
|
||||
|
||||
} catch (PatternSyntaxException e) {
|
||||
logger.debug("Illegal search pattern syntax '{}'. ", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Start file reader");
|
||||
|
||||
try {
|
||||
fileReader.registerListener(this);
|
||||
fileReader.start(configuration.filePath, configuration.refreshRate);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during initalization: {}. ", e.getMessage(), e);
|
||||
shutdown();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Stopping thing");
|
||||
shutdown();
|
||||
}
|
||||
|
||||
private void updateChannel(ChannelUID channelUID, Command command, SearchEngine matcher) {
|
||||
if (command instanceof DecimalType) {
|
||||
matcher.setMatchCount(((DecimalType) command).longValue());
|
||||
} else if (command instanceof RefreshType) {
|
||||
updateState(channelUID.getId(), new DecimalType(matcher.getMatchCount()));
|
||||
} else {
|
||||
logger.debug("Unsupported command '{}' received for channel '{}'", command, channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCounters() {
|
||||
if (errorEngine != null) {
|
||||
errorEngine.clearMatchCount();
|
||||
}
|
||||
if (warningEngine != null) {
|
||||
warningEngine.clearMatchCount();
|
||||
}
|
||||
if (customEngine != null) {
|
||||
customEngine.clearMatchCount();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChannelIfLinked(String channelID, State state) {
|
||||
if (isLinked(channelID)) {
|
||||
updateState(channelID, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
logger.debug("Stop file reader");
|
||||
fileReader.unregisterListener(this);
|
||||
fileReader.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileNotFound() {
|
||||
final String msg = String.format("Log file '%s' does not exist", configuration.filePath);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileRotated() {
|
||||
logger.debug("Log rotated");
|
||||
updateChannelIfLinked(CHANNEL_LOGROTATED, new DateTimeType(ZonedDateTime.now()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String line) {
|
||||
if (line == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(thing.getStatus() == ThingStatus.ONLINE)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
if (errorEngine.isMatching(line)) {
|
||||
updateChannelIfLinked(CHANNEL_ERRORS, new DecimalType(errorEngine.getMatchCount()));
|
||||
updateChannelIfLinked(CHANNEL_LASTERROR, new StringType(line));
|
||||
triggerChannel(CHANNEL_NEWERROR, line);
|
||||
}
|
||||
if (warningEngine.isMatching(line)) {
|
||||
updateChannelIfLinked(CHANNEL_WARNINGS, new DecimalType(warningEngine.getMatchCount()));
|
||||
updateChannelIfLinked(CHANNEL_LASTWARNING, new StringType(line));
|
||||
triggerChannel(CHANNEL_NEWWARNING, line);
|
||||
}
|
||||
if (customEngine.isMatching(line)) {
|
||||
updateChannelIfLinked(CHANNEL_CUSTOMEVENTS, new DecimalType(customEngine.getMatchCount()));
|
||||
updateChannelIfLinked(CHANNEL_LASTCUSTOMEVENT, new StringType(line));
|
||||
triggerChannel(CHANNEL_NEWCUSTOM, line);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Exception ex) {
|
||||
final String msg = ex != null ? ex.getMessage() : "";
|
||||
logger.debug("Error while trying to read log file: {}. ", msg, ex);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.logreader.internal.searchengine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This class implements logic for regular expression based searching.
|
||||
*
|
||||
* @author Pauli Anttila - Initial contribution
|
||||
*/
|
||||
public class SearchEngine {
|
||||
|
||||
private List<Pattern> matchers;
|
||||
private List<Pattern> blacklistingMatchers;
|
||||
|
||||
private long matchCount;
|
||||
|
||||
/**
|
||||
* Initialize search patterns.
|
||||
*
|
||||
* @param patterns search patterns.
|
||||
* @param blacklistingPatterns search patterns to bypass results which have found by the initial search patterns.
|
||||
*
|
||||
*/
|
||||
public SearchEngine(String patterns, String blacklistingPatterns) throws PatternSyntaxException {
|
||||
matchers = compilePatterns(patterns);
|
||||
blacklistingMatchers = compilePatterns(blacklistingPatterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data is matching to one of the provided search patterns.
|
||||
*
|
||||
* @param data data against search will be done.
|
||||
* @return true if one of the search patterns found.
|
||||
*/
|
||||
public boolean isMatching(String data) {
|
||||
if (isMatching(matchers, data)) {
|
||||
if (notBlacklisted(data)) {
|
||||
matchCount++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getMatchCount() {
|
||||
return matchCount;
|
||||
}
|
||||
|
||||
public void setMatchCount(long matchCount) {
|
||||
this.matchCount = matchCount;
|
||||
}
|
||||
|
||||
public void clearMatchCount() {
|
||||
setMatchCount(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split pattern string and precompile search patterns.
|
||||
*
|
||||
* @param patterns patterns which will handled.
|
||||
* @return list of precompiled patterns. If pattern parameter is null, empty list is returned.
|
||||
*/
|
||||
private List<Pattern> compilePatterns(@Nullable String patterns) throws PatternSyntaxException {
|
||||
List<Pattern> patternsList = new ArrayList<>();
|
||||
|
||||
if (patterns != null && !patterns.isEmpty()) {
|
||||
String list[] = patterns.split("\\|");
|
||||
if (list.length > 0) {
|
||||
for (String patternStr : list) {
|
||||
patternsList.add(Pattern.compile(patternStr));
|
||||
}
|
||||
}
|
||||
}
|
||||
return patternsList;
|
||||
}
|
||||
|
||||
private boolean notBlacklisted(String data) {
|
||||
return !isMatching(blacklistingMatchers, data);
|
||||
}
|
||||
|
||||
private boolean isMatching(@Nullable List<Pattern> patterns, String data) {
|
||||
if (patterns != null) {
|
||||
for (Pattern pattern : patterns) {
|
||||
Matcher matcher = pattern.matcher(data);
|
||||
if (matcher.find()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="logreader" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>LogReader Binding</name>
|
||||
<description>Binding that reads through log files and searches e.g. errors and warnings</description>
|
||||
<author>Pauli Anttila, Miika Jukka</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="logreader"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="lastErrorEvent">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Error Event</label>
|
||||
<description>Displays contents of last [ERROR] event</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="lastWarningEvent">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Warning Event</label>
|
||||
<description>Displays contents of last [WARN] event</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="lastCustomEvent" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Custom Event</label>
|
||||
<description>Displays contents of last custom event</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="warningEvents">
|
||||
<item-type>Number</item-type>
|
||||
<label>Warning Events Matched</label>
|
||||
<description>Displays number of [WARN] lines matched to search pattern</description>
|
||||
</channel-type>
|
||||
<channel-type id="errorEvents">
|
||||
<item-type>Number</item-type>
|
||||
<label>Error Events Matched</label>
|
||||
<description>Displays number of [ERROR] lines matched to search pattern</description>
|
||||
</channel-type>
|
||||
<channel-type id="customEvents" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Custom Events Matched</label>
|
||||
<description>Displays number of custom lines matched to search pattern</description>
|
||||
</channel-type>
|
||||
<channel-type id="logRotated" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Log Rotated</label>
|
||||
<description>Last time when log rotated recognized</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="newErrorEvent">
|
||||
<kind>trigger</kind>
|
||||
<label>New Error Event</label>
|
||||
<description>Fires when a new [ERROR] appears in the log</description>
|
||||
<event>
|
||||
</event>
|
||||
</channel-type>
|
||||
<channel-type id="newWarningEvent">
|
||||
<kind>trigger</kind>
|
||||
<label>New Warning Event</label>
|
||||
<description>Fires when a new [WARN] appears in the log</description>
|
||||
<event>
|
||||
</event>
|
||||
</channel-type>
|
||||
<channel-type id="newCustomEvent" advanced="true">
|
||||
<kind>trigger</kind>
|
||||
<label>New Custom Event</label>
|
||||
<description>Fires when a new [CUSTOM] appears in the log</description>
|
||||
<event>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="logreader"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="reader">
|
||||
|
||||
<label>LogReader</label>
|
||||
<description>Log reader to analyze log events</description>
|
||||
|
||||
<channels>
|
||||
<channel typeId="lastWarningEvent" id="lastWarningEvent"/>
|
||||
<channel typeId="lastErrorEvent" id="lastErrorEvent"/>
|
||||
<channel typeId="lastCustomEvent" id="lastCustomEvent"/>
|
||||
<channel typeId="warningEvents" id="warningEvents"/>
|
||||
<channel typeId="errorEvents" id="errorEvents"/>
|
||||
<channel typeId="customEvents" id="customEvents"/>
|
||||
<channel typeId="logRotated" id="logRotated"/>
|
||||
|
||||
<channel typeId="newWarningEvent" id="newWarningEvent"/>
|
||||
<channel typeId="newErrorEvent" id="newErrorEvent"/>
|
||||
<channel typeId="newCustomEvent" id="newCustomEvent"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="filePath" type="text" required="true">
|
||||
<label>Log File Path</label>
|
||||
<description>Path to log file. Empty will default to ${OPENHAB_LOGDIR}/openhab.log</description>
|
||||
<default>${OPENHAB_LOGDIR}/openhab.log</default>
|
||||
</parameter>
|
||||
<parameter name="refreshRate" type="integer" required="false">
|
||||
<label>Refresh Rate</label>
|
||||
<description>Refresh rate in milliseconds for reading logs</description>
|
||||
<default>1000</default>
|
||||
</parameter>
|
||||
<parameter name="errorPatterns" type="text" required="false">
|
||||
<label>Error Patterns</label>
|
||||
<description>Search patterns separated by | character for error events. Empty will default to ERROR+</description>
|
||||
<default>ERROR+</default>
|
||||
</parameter>
|
||||
<parameter name="errorBlacklistingPatterns" type="text" required="false">
|
||||
<label>Error Blacklisting Patterns</label>
|
||||
<description>Search patterns for blacklisting unwanted error events separated by | character. </description>
|
||||
</parameter>
|
||||
<parameter name="warningPatterns" type="text" required="false">
|
||||
<label>Warning Patterns</label>
|
||||
<description>Search patterns separated by | character for warning events. Empty will default to WARN+</description>
|
||||
<default>WARN+</default>
|
||||
</parameter>
|
||||
<parameter name="warningBlacklistingPatterns" type="text" required="false">
|
||||
<label>Warning Blacklisting Patterns</label>
|
||||
<description>Search patterns for blacklisting unwanted warning events separated by | character.</description>
|
||||
</parameter>
|
||||
<parameter name="customPatterns" type="text" required="false">
|
||||
<label>Custom Patterns</label>
|
||||
<description>Search patterns separated by | character for custom events.</description>
|
||||
</parameter>
|
||||
<parameter name="customBlacklistingPatterns" type="text" required="false">
|
||||
<label>Custom Blacklisting Patterns</label>
|
||||
<description>Search patterns for blacklisting unwanted custom events separated by | character.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user