Dear community,
I wanted to share an easy option to view the log files in a Browser.
Usually I am not sitting on my computer and when an event occurs, I am to lazy to start my computer. And checking logs by smartphone and command line is quite frustrating.
I have installed PHP and Frontail on my server. But as I always fight with installing packages due the different configurations everybody has, I was searching for a way to read the log files without installing additional packages to help beginners to read the log-files easily.
That’s why I tried to write this tutorial for beginners and even the JavaScript and HTML code is as simple as possible, so everybody can (hopefully) understand it.
I am running Debian on a Raspberry Pi3. If the paths are not equal to your system, please be so kind and let me know which system you use and where your paths are. I will add it to this tutorial.
In this tutorial I will assume an IP address 192.168.0.100 of my Raspberry. Please replace it with your address.
In this solution, no PHP or similar serverside scripting is necessary. The reading of the log files and adjusting of the webview will be done by JavaScript in the browser directly.
The only change that needs to be done, is a “Symbolic link” https://en.wikipedia.org/wiki/Symbolic_link from the webserver directory to the log files.
After that, the log files can be viewed through the webserver, by a webbrowser or as in this solution by Javascript running on a website.
To create a symbolic link, you need to connect to the command line view of your server running openhab.
You can do this by attaching keyboard and screen to you OpenHab Server or by SSH protocol using for example Putty or a similar terminal program.
If you are using Windows, I would recommend to get the freeware putty https://www.putty.org/
Enter the IP address of you OpenHab Server (In my example 192.168.0.100) and connect to it. If you are using Debian, standard username is “pi” and standard passwort is “raspberry”. Of course, use yours, if you changed it.
Please check if the folders and files are available:
$ ls /etc/openhab2/html/
You should be now see files in the webserver folder of Openhab. Usually there is an “index.html” and “readme.txt”
$ cat /var/log/openhab2/events.log
You should now see all your openhab events.
If both are correct, continue. If not, try to find the correct paths and replace them in this tutorial with yours.
Step 1
run following commands in terminal to create links from your webserver folder to the log-folder:
$ sudo ln --symbolic /var/log/openhab2/events.log /etc/openhab2/html/ln_log_events.html
$ sudo ln --symbolic /var/log/openhab2/openhab.log /etc/openhab2/html/ln_log_openhab.html
You can try it with this command:
$ cat /etc/openhab2/html/ln_log_events.html
If the symbolic link is created successfully, create a HTML file inside the webserver folder.
You can use a samba share, if you know how, or nano editor in your terminal. https://www.howtogeek.com/howto/42980/the-beginners-guide-to-nano-the-linux-command-line-text-editor/
Step 2
Create the file /etc/openhab2/html/logs.html and place this whole content inside:
<html>
<head>
<title>Openhab Log-File Viewer v1.43</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
<!-- Thanks to https://saeedalipoor.github.io/icono/ -->
<link rel="stylesheet" href="http://icono-49d6.kxcdn.com/icono.min.css">
<style id="sitestyles">
html, body {
margin:0px;
padding:0px;
}
#top {
height:35px;
}
#logcontent {
height:calc(100% - 35px);
overflow-y:auto;
width:100%;
}
[class*=icono-]{
float:right;
color:unset;
margin-right: 20px!important;
cursor:pointer;
color:#000;
}
#filter {
line-height:25px;
}
#settings {
display:none;
width:100%;
height:100%;
overflow:auto;
position:absolute;
top:0px;
left:0px;
background:#ddd;
color:#000;
}
.entryrow {
border-bottom:1px solid grey;
width:100%;
}
#loadmore {
width:90%;
margin:20px;
height:30px;
line-height:30px;
text-align:center;
border:1px solid black;
cursor:pointer;
}
.markicon {
color:#C8E6C9;
}
</style>
<script type="text/javascript" src="http://code.jquery.com/jquery-2.2.2.min.js"></script>
<script type="text/javascript">
var readEventsDone = false;
var readOpenhabDone = false;
var rowEventsLog;
var rowOpenhabLog
var doubleRows = true;
var regex = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}) (\[\w+ \] )?(\[.*?\]) - (.*?)$/;
var maxentries = 200;
var entiresstep = 100;
var filter = false;
var filterRegExp = false;
var excludeAlways = false;
var jumpToId = 0;
var markcounter = 0;
var markColors = [
'#C8E6C9',
'#FFCC80',
'#B2DFDB',
'#FFF59D',
'#C5E1A5',
'#CFD8DC',
'#90CAF9',
'#E1BEE7'
];
var debug=false;
$(document).ready(function(){
loadData();
});
function loadData() {
readEventsDone = false;
readOpenhabDone = false;
jumpToId=0;
$('#logcontent').html('Please wait...<br>');
$.ajax({
beforeSend: function() {
$('#logcontent').append('Reading events.log...<br>');
},
url:'ln_log_events.html',
success: function(data) {
rowEventsLog = data.split("\n");
rowEventsLog = $.map( rowEventsLog, function(v){ return v === "" ? null : v; });
readEventsDone=true;
$('#logcontent').append('Completed events.log<br>');
check();
}
});
$.ajax({
beforeSend: function() {
$('#logcontent').append('Reading openhab.log...<br>');
},
url:'ln_log_openhab.html',
success: function(data) {
rowOpenhabLog = data.split("\n");
rowOpenhabLog = $.map( rowOpenhabLog, function(v){ return v === "" ? null : v; });
readOpenhabDone=true;
$('#logcontent').append('Completed openhab.log<br>');
check();
}
});
}
function check() {
if (readEventsDone && readOpenhabDone) show(maxentries);
}
function show(maxentriesInternal) {
$('#logcontent').html('');
filter = $('#filter').val();
if (debug) printdebug('Filter=##'+filter+'##','debug');
if (filter) {
$('#logcontent').append('Searching for:' + $('#filter').val() + '<br>');
filterRegExp = new RegExp('('+filter+')','ig');
}
if (excludeAlways) {
$('#logcontent').append('Excluding:' + excludeAlways + '<br>');
}
var pointerEvents = rowEventsLog.length - 1;
var pointerOpenhab = rowOpenhabLog.length - 1;
if (debug) printdebug('pointerEvents='+pointerEvents);
if (debug) printdebug('pointerOpenhab='+pointerOpenhab);
var nextTimestampEvents = 0;
var nextTimestampOpenhab = 0;
if (pointerEvents > 0) {
doneWithEventsLog = false;
nextTimestampEvents = Date.parse(rowEventsLog[(pointerEvents)].substr(0,23));
if (debug) printdebug('nextTimestampEvents="'+nextTimestampEvents);
} else doneWithEventsLog = true;
if (pointerOpenhab > 0) {
doneWithOpenhabLog = false;
nextTimestampOpenhab = Date.parse(rowOpenhabLog[(pointerOpenhab)].substr(0,23));
if (debug) printdebug('nextTimestampOpenhab="'+nextTimestampOpenhab);
} else doneWithOpenhabLog = true;
var counter=0
var jumpToCounter=0
while (!doneWithEventsLog || !doneWithOpenhabLog) {
counter++;
jumpToCounter++;
if (counter > maxentriesInternal) {
if (debug) printdebug('counter > maxentriesInternal: BREAK');
break;
}
if (nextTimestampEvents > nextTimestampOpenhab) {
// ######################################## Event Log ########################################
if (debug) printdebug('nextTimestampEvents > nextTimestampOpenhab: EventLog is next');
if ( excludeAlways && rowEventsLog[pointerEvents].match(excludeAlways) !== null) {
if (debug) printdebug('Skip due exclude:'+rowEventsLog[(pointerEvents)] );
counter--;
jumpToCounter--;
} else if (filter && rowEventsLog[pointerEvents].match(filterRegExp) === null) {
if (debug) printdebug('Skip due Filter:'+rowEventsLog[(pointerEvents)] );
counter--;
}
else printrow(rowEventsLog[pointerEvents],'event', jumpToCounter );
nextTimestampEvents = 0
pointerEvents--;
while (pointerEvents >= 0 && !(nextTimestampEvents > 0)) {
nextTimestampEvents = Date.parse(rowEventsLog[(pointerEvents)].substr(0,23));
if (debug) printdebug('EL: nextTimestampOpenhab:'+nextTimestampOpenhab+' # DateTime should be standing here:('+rowEventsLog[(pointerEvents)].substr(0,23)+')' );
if (nextTimestampEvents < 1451606400000|| nextTimestampEvents > 4670438400000) nextTimestampEvents = 0; //wrong Date parse
if (!(nextTimestampEvents > 0)) {
if (debug) printdebug('EL: Skip due no datetime found:'+rowEventsLog[(pointerEvents)] );
pointerEvents--;
} else if (debug) printdebug('EL: No Skip due datetime found:'+rowEventsLog[(pointerEvents)] );
}
if (pointerEvents < 0) doneWithEventsLog = true;
// ######################################## Event Log ########################################
} else {
// ######################################## Openhab Log ########################################
if (debug) printdebug('nextTimestampEvents < nextTimestampOpenhab: OpenhabLog is next');
if ( excludeAlways && rowOpenhabLog[pointerOpenhab].match(excludeAlways) !== null) {
if (debug) printdebug('Skip due exclude:'+rowOpenhabLog[(pointerOpenhab)] );
counter--;
jumpToCounter--;
} else if (filter && rowOpenhabLog[pointerOpenhab].match(filterRegExp) === null) {
if (debug) printdebug('Skip due Filter:'+rowOpenhabLog[(pointerOpenhab)] );
counter--;
}
else printrow(rowOpenhabLog[pointerOpenhab],'openhab', jumpToCounter );
nextTimestampOpenhab = 0
pointerOpenhab--;
while (pointerOpenhab >= 0 && !(nextTimestampOpenhab > 0)) {
nextTimestampOpenhab = Date.parse(rowOpenhabLog[(pointerOpenhab)].substr(0,23));
if (debug) printdebug('OL: nextTimestampOpenhab:'+nextTimestampOpenhab+' # DateTime should be standing here:('+rowOpenhabLog[(pointerOpenhab)].substr(0,23)+')' );
if (nextTimestampOpenhab < 1451606400000|| nextTimestampOpenhab > 4670438400000) nextTimestampOpenhab = 0; //wrong Date parse
if (!(nextTimestampOpenhab > 0)) {
if (debug) printdebug('OL: Skip due no datetime found:'+rowOpenhabLog[(pointerOpenhab)] );
pointerOpenhab--;
} else if (debug) printdebug('OL: No Skip due datetime found:'+rowOpenhabLog[(pointerOpenhab)] );
}
if (pointerOpenhab < 0) doneWithOpenhabLog = true;
// ######################################## Openhab Log ########################################
}
}
if (counter > maxentriesInternal) {
$('#logcontent').append( "<div id='loadmore' onclick='show(" + ( parseInt(maxentriesInternal) + parseInt(entiresstep) ) + ")'>Load " + entiresstep + " more entries</div>" );
}
}
function jumpTo(id) {
jumpToId = id;
$('#filter').val('');
filter = false;
show(jumpToId+8);
$('#logcontent').scrollTop(parseInt($('#logcontent')[0].scrollHeight), 2000 );
}
function printrow(value, type, id) {
var row = value.split(new RegExp(regex));
if (row[1] == undefined) return;
var output = '';
if (filterRegExp) row[1] = row[1].replace(filterRegExp, '<u>$1</u>');
if (id == jumpToId ) output += '<div class="entryrow entry' + type + '" style="background-color:#FF8A80;">';
else output += '<div class="entryrow entry' + type + '">';
if (filter) output += '<div class="thisId icono-pin" onclick="jumpTo('+id+')"></div>';
else output += '<div class="thisId icono-bookmark markicon" onclick="mark(this)"></div>';
if (debug) output += '<b>(Id:' + id + ')</b>';
output += '<i>' + row[1] + ' '; //date and time
if (typeof row[2] != "undefined") {
if (filterRegExp) row[2] = row[2].replace(filterRegExp, '<u>$1</u>');
output += row[2] + ' ';
}
if (filterRegExp) row[2] = row[3].replace(filterRegExp, '<u>$1</u>');
output += row[3] + ' </i>';
if (doubleRows ) output += '<br>';
if (filterRegExp) row[4] = row[4].replace(filterRegExp, '<u>$1</u>');
output += '<b>' + row[4] + '</b></div>';
$('#logcontent').append( output );
}
function printdebug(value) {
var output = '<div class="entryrow entryDebug">' + value + '</div>';
$('#logcontent').append( output );
}
function mark(that){
$(that).parent().css("background-color",$(that).css("color"));
$(that).remove();
markcounter++;
if ( markcounter >= markColors.length ) markcounter = 0;
$(".markicon").css("color",markColors[markcounter]);
};
</script>
</head>
<body>
<div id="top">
<input type="text" id="filter" placeholder="Filter">
<span class="icono-sliders" id="iconsettings"></span>
<span class="icono-reset" onclick="loadData()"></span>
</div>
<div id="logcontent"></div>
<div id="settings">
<span class="icono-crossCircle" id="iconclose"></span>
Settings
<p>
Font-family: <select class="settings" id="fontfamily">
<option value="0">default</option>
<option value="verdana">verdana</option>
<option value="times">times</option>
<option value="courier">courier</option>
<option value="arial">arial</option>
</select>
<p>
Font-size: <select class="settings" id="fontsize">
<option value="0">default</option>
<option>8px</option>
<option>10px</option>
<option>12px</option>
<option>14px</option>
<option>16px</option>
<option>18px</option>
<option>20px</option>
</select>
<p>
Lines: <select class="settings" id="lines">
<option>2</option>
<option>1</option>
</select>
<p>
Color default text and icons: <input type="color" class="settings" id="colordefault">
<p>
Color openhab.log entries: <input type="color" class="settings" id="coloropenhab" value="#EF6C00">
<p>
Color events.log entries: <input type="color" class="settings" id="colorevents" value="#5C6BC0">
<p>
Always exclude entries with content: <input type="text" class="settings" id="exclude"> (e.g. "temperature" or "temperature;brightness")
<p>
Maximum entries shown: <input type="text" class="settings" id="maxentries" value="200"> (Reduce if browser is lagging)
<p>
</div>
<script>
$('#filter').on('input', function() {
show(maxentries);
$('#logcontent').scrollTop(0);
});
$('#iconsettings').click(function(e) {
$("#settings").css("display","block");
});
$('#iconclose').click(function(e) {
$("#settings").css("display","none");
});
$('.settings').change(function() {
var queryParameters = {}, queryString = location.search.substring(1),
re = /([^&=]+)=([^&]*)/g, m;
while (m = re.exec(queryString)) {
queryParameters[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
queryParameters[$(this).attr('id')] = this.value.replace('#','');
location.search = $.param(queryParameters);
});
let searchParams = new URLSearchParams(window.location.search)
if (searchParams.has('fontfamily')) {
$('html, body').css("font-family", searchParams.get("fontfamily"));
$("#fontfamily").val(searchParams.get("fontfamily"));
}
if (searchParams.has('fontsize')) {
$('html, body').css("font-size", searchParams.get("fontsize"));
$("#fontsize").val(searchParams.get("fontsize"));
}
if (searchParams.has('lines')) {
if (searchParams.get("lines") == 1) doubleRows = false;
$("#lines").val(searchParams.get("lines"));
}
if (searchParams.has('colordefault')) {
$('#sitestyles').append('body {color:#' + searchParams.get("colordefault") + '}');
$("#colordefault").val('#'+searchParams.get("colordefault"));
}
if (searchParams.has('coloropenhab')) {
$('#sitestyles').append('.entryopenhab {color:#' + searchParams.get("coloropenhab") + '}');
$("#coloropenhab").val('#'+searchParams.get("coloropenhab"));
}
else $('#sitestyles').append('.entryopenhab {color:#EF6C00}');
if (searchParams.has('colorevents')) {
$('#sitestyles').append('.entryevent {color:#' + searchParams.get("colorevents") + '}');
$("#colorevents").val('#'+searchParams.get("colorevents"));
}
else $('#sitestyles').append('.entryevent {color:#5C6BC0}');
if (searchParams.has('exclude') && searchParams.get("exclude") != '') {
var excludeParam = searchParams.get("exclude");
$("#exclude").val(excludeParam);
excludeAlways = new RegExp(excludeParam.replace(/;/g,'|'),'i');
}
if (searchParams.has('maxentries')) {
maxentries = searchParams.get("maxentries");
if (maxentries < 100) entiresstep = maxentries;
else entiresstep = 100;
$("#maxentries").val(searchParams.get("maxentries"));
}
</script>
</body>
</html>
Step 3
Call http://[ip or name]:8080/static/ln_log_events.html in your Browser. You should see the pure log file. If not, check file permissions.
If it works, call http://[ip or name]:8080/static/logs.html in your browser.
Enjoy
Note:
You can change the view as you want by the setting button on top right. These changes will NOT be saved i a cookie or on the server. Changes will generate a URL which contains the settings.
If you want to use it as a frame, for example in habpanel, adjust the settings in a separate browser tab as needed and copy the final URL with all settings included to the frame.
If your log files are really big, I wrote also a bash script to split my log files, backup every entry more than two days ago and keep the newest entry. Check Google for bash scripts and how to run them daily using crontabs. Test the bash first by running it and change the line to
debug=false;
to activate it.
#!/bin/bash
daysToKeep=2
debug=true;
if $debug; then
echo Debug mode on
else
echo Debug mode off
fi
dateToKeep=$(date --date="$daysToKeep days ago" +"%Y-%m-%d")
echo "Date to keep $daysToKeep days:$(tput setaf 3) $dateToKeep $(tput sgr 0)"
dateToArchive=$(date --date="$((daysToKeep+1)) days ago" +"%Y-%m-%d")
echo Newest date to archive: $dateToArchive
# Log openhab
if $debug; then
echo "\n$(tput setaf 5)######### openhab.log #########$(tput sgr 0)"
zeileo=$(cat /var/log/openhab2/openhab.log | tr -d "\000" | grep -n -m 1 "$dateToKeep " | cut -f1 -d:)
echo "\n"First entry line with actual date: $zeileo
echo $((zeileo-1)): $(sed -n "$((zeileo-1))"p /var/log/openhab2/openhab.log)
echo $((zeileo+0)): $(sed -n "$((zeileo+0))"p /var/log/openhab2/openhab.log)
echo $((zeileo+1)): $(sed -n "$((zeileo+1))"p /var/log/openhab2/openhab.log)
fi
if [ ! -f /var/log/openhab2/openhab_$dateToArchive.log.old ]; then
head -n$zeileo /var/log/openhab2/openhab.log > /var/log/openhab2/openhab_$dateToArchive.log.old
tail --lines=+$zeileo /var/log/openhab2/openhab.log > /var/log/openhab2/openhab.log.new
if $debug; then
echo "\n"Last lines in Archive /var/log/openhab2/openhab_$dateToArchive.log.old:
lastLineArchive=$(wc -l < /var/log/openhab2/openhab_$dateToArchive.log.old)
echo $((lastLineArchive - 1)): $(sed -n "$((lastLineArchive-1))"p /var/log/openhab2/openhab_$dateToArchive.log.old)
echo $((lastLineArchive - 0)): $(sed -n "$((lastLineArchive-0))"p /var/log/openhab2/openhab_$dateToArchive.log.old)
echo "\nFirst line in shrinked Log file:"
echo 1: $(head -n 1 /var/log/openhab2/openhab.log.new)
fi
if $debug; then
echo "\n$(tput setaf 1)Debug mode active. Log has not changed.$(tput sgr 0)"
rm /var/log/openhab2/openhab_$dateToArchive.log.old
else
#cat /var/log/openhab2/openhab.log.new > /var/log/openhab2/openhab.log
echo "\n$(tput setaf 2)Backup is created. Log is splitted.$(tput sgr 0)"
fi
rm /var/log/openhab2/openhab.log.new
else
echo "$(tput setaf 1)Backup already exists!$(tput sgr 0)"
fi
# Log events
if $debug; then
echo "\n$(tput setaf 5)######### events.log #########$(tput sgr 0)"
zeilee=$(cat /var/log/openhab2/events.log | tr -d "\000" | grep -n -m 1 "$dateToKeep " | cut -f1 -d:)
echo "\n"First entry line with actual date: $zeilee
echo $((zeilee-1)): $(sed -n "$((zeilee-1))"p /var/log/openhab2/events.log)
echo $((zeilee+0)): $(sed -n "$((zeilee+0))"p /var/log/openhab2/events.log)
echo $((zeilee+1)): $(sed -n "$((zeilee+1))"p /var/log/openhab2/events.log)
fi
if [ ! -f /var/log/openhab2/events_$dateToArchive.log.old ]; then
head -n$zeilee /var/log/openhab2/events.log > /var/log/openhab2/events_$dateToArchive.log.old
tail --lines=+$zeilee /var/log/openhab2/events.log > /var/log/openhab2/events.log.new
if $debug; then
echo "\n"Last lines in Archive /var/log/openhab2/events_$dateToArchive.log.old:
lastLineArchive=$(wc -l < /var/log/openhab2/events_$dateToArchive.log.old)
echo $((lastLineArchive - 1)): $(sed -n "$((lastLineArchive-1))"p /var/log/openhab2/events_$dateToArchive.log.old)
echo $((lastLineArchive - 0)): $(sed -n "$((lastLineArchive-0))"p /var/log/openhab2/events_$dateToArchive.log.old)
echo "\nFirst line in actual Log file:"
echo 1: $(head -n 1 /var/log/openhab2/events.log.new)
fi
if $debug; then
echo "\n$(tput setaf 1)Debug mode active. Log has not changed.$(tput sgr 0)"
rm /var/log/openhab2/events_$dateToArchive.log.old
else
#cat /var/log/openhab2/events.log.new > /var/log/openhab2/events.log
echo "\n$(tput setaf 2)Backup wurde erzeugt. Log wurde getrennt.$(tput sgr 0)"
fi
rm /var/log/openhab2/events.log.new
else
echo "$(tput setaf 1)Backup already exists!$(tput sgr 0)"
fi
If you have any questions, suggestions or changes you want be included, feel free to post them here.
If you have different paths, please let me know, I will extend the this tutorial.
I hope you like it.
Screenshots: