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