added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View 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>

View 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>

View 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

View 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.

View 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>

View 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;
}
}
}

View File

@@ -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);
}

View File

@@ -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() {
}
}

View File

@@ -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>

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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 + "]";
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>