Abstract in English

This text gives an example of a simple Syslog configuration (SOHO type) and describes a little Konqueror/CGI kioslave/Bash based logfile viewer. The Syslog config files are included in the documentation of the viewers downloadable packages. All examples and source files are commented in english. This software needs no compilation.

Download an Installation Package or a Source Archive:

This document refers to version 0.1 of the centaurilog package - check the internet for newer versions: Click here to see the downlad page.

Zusammenfassung auf Deutsch

Dieser Text stellt eine einfache Syslog Konfiguration (für den SOHO Einsatz) vor und beschreibt eine kleine Konqueror/CGI kioslave/Bash basierte Logfile Anzeige. Die Syslog Konfiguaritionsdateien sind den downloadbaren Packeten der Anzeige Anwendung enthalten. Alle Beispiele und Quellentexte sind auf Englisch kommentiert. Diese Software bedarf keiner Kompilierung.

Laden eines Installationspakets oder der Quellentexte:

Dieses Dokument beschreibt Version 0.1 des centaurilog Pakets - bitte im Internet nach neueren Versionen sehen: Hier der Verweis zur Download Seite.

Eine einfache Syslog Konfiguration

Bei den meisten Linux Distributionen ist ksyslogd zu kompliziert konfiguriert. Im SOHO Betrieb ist die Menge der anfallenden Daten klein genug um dies zu vereinfachen. Wir konfigurieren zunächst einen Server und dann Arbeitsplätze. Die Arbeitsplatzrechner sollen Fehlermeldungen zum Server weiterreichen.

# Server side example for /etc/syslog.conf - see "man syslog.conf".

# print warnings on tty10, use write to announce desasters
kern.warn;*.err;authpriv.none    /dev/tty10
*.crit                           *

# all mail-messages in one file (no sync)
mail.*                          -/var/log/mail

# Warnings in one file (sync for error only)
*.=warn;*.=err                  -/var/log/errors
*.crit                           /var/log/errors

Die folgende Konfiguation für Arbeitsplatzrechner führt einerseits ein lokales Logfile (mail wird hier nicht getrennt behandelt) und sendet Warnungen und Fehler zu Server (heisst hier alpha):

# Workstation example for /etc/syslog.conf - see "man syslog.conf".

# print warnings on tty10, use write to announce desasters
kern.warn;*.err;authpriv.none    /dev/tty10
*.crit                           *

# forward errors to server 'alpha'
*.err                           @alpha

# extra log file for error messages (no snyc here!)
*.err                           -/var/log/errors

# save the rest in one file (avoid sync if non-critical)
*.crit                           /var/log/messages
*.*;*.!=crit                    -/var/log/messages

Achtung: Bitte daran denken den Server aus dem Beispiel (hier alpha) umzubennen! Zudem sollte der syslogd richtig gestartet werden. Bei Debian können die dafür nötigen Optionen in /etc/defaults/ eingetragen werden:

# Debian syslogd configuration: /etc/default/syslogd

# The client might disable --MARK-- messages:
# SYSLOGD="-m0"

# The server also enables remote logging for the domain:
SYSLOGD="-m0 -r -s centauri.home"

# Another way for the server to specify clients is a list of hosts:
# SYSLOGD="-m0 -r -l alpha7:alpha8:alpha9"

Achtung: Hier daran denken den Domänennamen zu editieren. Das Beispiel geht davon aus, dass per DNS Server eine Domäne aufgesetzt ist. Alternativ können auch einzelne Hosts angegeben werden (deren IP-Adressen könnten in /etc/hosts/ stehen).

Diese Beispiele sind auch in der Dokumentation der oben zum Download angebotenen Archive enthalten.

Logfile automatisch rotieren

Ohne weiteres Zutun würden die oben eingeführten Logfiles über alle Grenzen wachsen. Es ist also nötig periodisch neue Dateien zu beginnen und zu alte Dateien zu löschen. Bei Debian könnte diese Aufgabe von logrotate übernommen werden. Leider müsste man dann aber die Konfiguration aus syslog.conf teilweise in die /etc/logrotate.d Konfiguation übertragen. Die wäre unpraktisch - anstelle dessen wird syslogd-listfiles benutzt. Das Tool listet die von syslogd benutzten Dateien auf. Debian hat zwei Cron-Jobs (/etc/cron.daily/ksyslogd und /etc/cron.daily/ksyslogd) dafür vorgesehen. Eventuelle andere Logs werden aber meist über logrotate verwaltet (Mail z.B.).

Im SOHO Betrieb wollen wir vermutlich von täglichem Logfilewechsel auf wöchentlichen Wechsel umstellen. Dazu braucht nur in den Dateien unter /etc/logrotate.d das Schlüsselwort "daily" nach "weekly" umgeändert werden. Leider sind bei Debian die Cron-Jobs nicht so leicht zu ändern. Wir änderen hier zwei Dateien ab:

#!/bin/bash
# Modified /etc/cron.daily/ksyslogd

exit 0   # inserted line to disable daily rotation

....
#!/bin/bash
# Modified /etc/cron.weekly/ksyslogd

...    # skip to line 31 or so and change '--weekly' to '--all'...

for LOG in `syslogd-listfiles --all`

...

Ein einfacher Logfile Viewer

Wie oben zu sehen war landen die Logfile Daten auch bei einer einfachen Konfiguration in mehreren Dateien. Zudem steht in diesen Dateien ziemlich viel uninteressantes. Ein Viewer muss also mehrere Dateien zusammenfassen und nach Inhalt filtern können. Ausser kompliziert zu konfigurirenden Programmen (z.B. logtool) gibt es ausgerechnet bei KDE wenig an Viewer Tools. Nutzen wir also an dieser Stelle KDEs CGI kioslave um ein recht mächtiges aber einfaches Tool selber zu implementieren. Da es sich im Kern um ein Shell-Script handelt ist es leicht eigenen Bedürfnissen anzupassen. Als Oberfläche wird Konqueror benutzt, die Ein-/Ausgabe erfolgt also per HTML.

Die oben zum Download angebotenen Archive enthalten auch eine Manual-Seite zur genaueren Beschreibung des Tools. Nach der Installation könnten wir also in Konqueror die URL man:centaurilog eingeben. Hier steht man: für den Manual kioslave, der aber ein in C++ geschriebenes Programm ist und uns hier nicht weiter interessiert. Unser Logfileviewer (genauer: CGI) muss zunächst für den Benutzer konfiguriert werden. Das geht z.B. mit den Tasten Alt+F2 und der Eingabe centaurilog --setup. Nun können wir Logfiles aus einer laufenden Konqueror Instanz ansehen indem wir als URL cgi:centaurilog eingeben.

Alternativ kann man das Script centaurilog auch direkt aufrufen, eine Konqueror Instanz wird dann automatisch gestartet (siehe auch den Eintrag der im System Menü eingerichtet wird). Wer sich die Mühe macht ein Konqueror-Profil unter dem Namen "centaurilog" abzulegen wird dann auch regelmässig durch ein nach eigenen Vorgaben eingerichtetes Fenster belohnt. Es sei noch angedeutet dass sich Queries "bookmarken" lassen - und dass die Query-Argumente auch beim direkten Aufruf mit der Option --query angegeben werden können.

Der Script Code ist recht einfach und lädt fortgeschrittene Bash Programmierer sicher zu eigenen Kreationen ein:

#!/usr/bin/env bash

declare this="${0##*/}"

function show_help()
{  cat <<EOF
This is a KDE/Konqueror based logfile viewer.

usage:   $this --setup
         $this [--nocheck] [--profile name] [--query urlargs]

options: --setup    do the KDE setup for this user and quit
         --nocheck  do not check if --setup was run before
         --profile  konqueror profile, use '' to override '$this'
         --query    url-encoded query arguments passed to konqueror
         --help     show this text and quit

EOF
   exit 1
}

function run_setup()
{
   local paths="$(kreadconfig --file kcmcgirc --group General --key Paths)"
   local instd="$(readlink -f "$0")" ; instd="${instd%/*}"
   if ! grep -q -E "(^|,)${instd}/?(\$|,)" <<<$paths ; then
      echo "$this: user $USER: CGI config updated to include: $instd"
      [ -n "$paths" ] && instd="$paths,$instd"
      kwriteconfig --file kcmcgirc --group General --key Paths "$instd"
   fi
   [ -n "$1" ] && exit 0
}

function run_normal()
{  local pdir="$HOME/.kde/share/apps/konqueror/profiles"
   [ -r "$pdir/$this" ] && prof="$this"

   while [ $# -gt 0 ] ; do case $1 in
      -pro*|--pro*) prof="$2"; shift ;;
      -que*|--que*) uarg="?$2"; shift ;;
      -noc*|--noc*) fnoc="x" ;;
      -set*|--set*) fset="x" ;;
      -h*|--h*)     show_help ;;
      *)            echo "$this: unknown option - try '--help'"
                    exit 2 ;;
   esac ; shift ; done

   [ -z "$fnoc" -o -n "$fset" ] && run_setup "$fset"
   [ -z "$prof" ] && exec konqueror "cgi:${this}$uarg"
   exec konqueror --profile $prof "cgi:${this}$uarg"
}

function render_form()
{  [ -f /var/log/errors ] || dispError="display: none"
   [ -f /var/log/mail ]   || dispMail="display: none"
cat <<EOF
<script language="javascript">
   function lock(n) {
      var x = document.getElementById("chkSyslog"); if(x == null) return;
      var y = document.getElementById("chkErrors"); if(y == null) return;
      if(x.checked && n==1) y.checked = false;
      if(y.checked && n==2) x.checked = false;
   }
</script>

<h2>Logfile Viewer - Query Form</h2>
<form action="cgi:${this}" method="GET">
   <div class='spacer'></div><table class="query">
   <tr>
      <td valign="top" rowspan="2"><b>Select the Logfile(s):</b></td>
      <td><br></td>
   </tr>
   <tr>
      <td align="right">System Log (all) </td>
      <td><input id="chkSyslog" name="chkSyslog" type="checkbox" $CHK_SYSLOG onclick="lock(1)" value="x"></td>
   </tr>
   <tbody style="$dispError"><tr>
      <td></td>
      <td align="right">... (Errors only) </td>
      <td><input id="chkErrors" name="chkErrors" type="checkbox" $CHK_ERRORS onclick="lock(2)" value="x"></td>
   </tr></tbody>
   <tbody style="$dispMail"><tr>
      <td></td>
      <td align="right">Mail Log</td>
      <td><input type="checkbox" name="chkMail" $CHK_MAIL value="x"></td>
   </tr></tbody>
   <tr>
      <td></td>
      <td align="right">max. Entry Count </td>
      <td><input type=text name=txtMaxlines> (Default: $TXT_MAXLINES)</td>
   </tr>
   <tr>
      <td colspan="3"><b>Content Filters (optional):</b></td>
   </tr>
   <tr>
      <td valign="top" rowspan="3"></td>
      <td align="right">Select a Computer </td>
      <td><input type="text" name="txtComputer" value="$TXT_COMPUTER"></td>
   </tr>
   <tr>
      <td align="right">Filter on Words</td>
      <td><input type="text" name="txtFilter" value="$TXT_FILTER">
      <input type="checkbox" name="chkExclude" $CHK_EXCLUDE value="x"> Exclude</td>
   </tr>
   <tr>
      <td colspan="2">&nbsp;<br><INPUT type="reset">&nbsp;&nbsp;&nbsp;&nbsp;<INPUT type="submit">
      &nbsp;&nbsp;&nbsp;&nbsp;<a href="man:${this}">Display Help</a></td>
   </tr>
</table></form>
EOF
}

function render_data()
{  # take care about temp files ...
   temp="${TMPDIR:-/tmp}/$$_showlog_"
   trap "rm -f ${temp}*" 0

   # convert query string back to varibles
   ltab=$'\a'
   set x ${QUERY_STRING//&/ }
   while [ $# -gt 1 ] ; do
      # UrlDecode and \ handling. It's woodo ... don't panic!
      nam="${2%%=*}" ; val="${2#*=}" ; shift
      val="${val//\%5C/ }" ; val="${val//+/$ltab}"
      read $nam <<<"$(printf "${val//\%/\x}")"
   done

   # which logfiles?
   if [ -n "$chkSyslog" ] ; then
      [ -f /var/log/messages   ] && logf="/var/log/messages $logf"
      [ -f /var/log/messages.0 ] && logf="/var/log/messages.0 $logf"
   fi
   if [ -n "$chkErrors" ] ; then
      [ -f /var/log/errors   ] && logf="/var/log/errors $logf"
      [ -f /var/log/errors.0 ] && logf="/var/log/errors.0 $logf"
   fi
   if [ -n "$chkMail" ] ; then
      [ -f /var/log/mail   ] && logf="/var/log/mail $logf"
      [ -f /var/log/mail.0 ] && logf="/var/log/mail.0 $logf"
   fi

   if [ -z "$logf" ] ; then
      echo "<h2>Logfile Viewer - No Logfiles are Matching the Request</h2>"
      echo "[<A href="cgi:${this}">another query</A>]"
      return
   fi

   for fnam in $logf ; do
       [ -r "$fnam" ] && continue
       sudo=x ; break
   done

   # do we need kdesu to get root rights?
   maxl=$((txtMaxlines + 0)) &>/dev/null
   [ -z "$maxl" -o "$maxl" = 0 ] && maxl="$TXT_MAXLINES"

   msgs="${temp}0"
   if [ -n "$sudo" ] ; then
      kdesu -d -c "sort -m -k1M -k2n $logf | tail -n $maxl >$msgs ; chown $UID $msgs ; chmod 600 $msgs"
   else
      sort -M -n -m $logf | tail -n $maxl >$msgs ; chmod 600 $msgs
   fi

   # filter on computer names ...
   if [ -n "$txtComputer" ] ; then
      sinf="for $txtComputer"
      srex="^.{15} $txtComputer "
      [ -z "${txtComputer//[a-z0-9-_]/}" ] && sopt="x"
   fi

   msgs="${temp}1"
   grep -h -E "$srex" "${temp}0" >$msgs
   lcnt=$(wc -l $msgs)
   cdat=$(date +"%b %e")

   # html header stuff and javascript ...
   cat <<EOF
<h2>Logfile Viewer - Results (processed ${lcnt% *} Entries $sinf)</h2>
[<A href="cgi:${this}">another query</A>]&nbsp;&nbsp;&nbsp;[<A href="#bottom">end of page</A>]
<script language="javascript">
var curshow
function toggle(name,t) {
   var x = document.getElementById(name); if(x == null) return;
   if(curshow != x) {
      if(curshow == null) curshow = document.getElementById("$cdat")
      if(curshow != null && curshow.style.display == "inline")
          curshow.style.display = "none" ; curshow = x
   }
   x.style.display = (x.style.display == "inline") ? "none" : "inline";
}
</script>
EOF
   # filter for text ...
   if [ -n "$txtFilter" ] ; then
      lbrk=$'\n'
      [ -n "$chkExclude" ] && vopt="-v"
      grep $vopt -i -F "${txtFilter//$ltab/$lbrk}" $msgs >"${temp}2"
      rm -f $msgs; msgs="${temp}2"
      lcnt=$(wc -l $msgs)
      echo "<h3>Remaining Entries after Filtering: ${lcnt% *}</h3>"
   fi

   echo "<table class="result">"
   sed -e "s/&/\&amp;/g" -e "s/</\&lt;/g" $msgs |
   while read fmon fdat ftim fnam fmsg ; do
      # output date and time ...
      fdat="$fmon $fdat"
      if [ "$fdat" != "$ldat" ] ; then
         [ -n "$ldat" ] && echo "</tbody>"
         ldat="$fdat" ; ltim=
         [ "$ldat" = "$cdat" ] && vmod="inline" || vmod="none"
         echo "<tr><td colspan=3><em><b>$ldat</b> </em>"
         echo "[<A href=\"javascript:toggle('$ldat')\">Click to show or hide</A>]"
         echo "</td></tr><tbody id='$ldat' style='display: $vmod'>"
      fi
      [ "$ftim" = "$ltim" ] && ftim="&nbsp;" || ltim="$ftim"
      echo "<tr><td class='tim'>$ftim</td>"

      # output computer name and message ...
      [ -z "$sopt" ] && echo "<td class='nam'>${fnam}</td>"
      echo "<td class='txt'>${fmsg}</td></tr>"
   done

   [ -n "$ldat" ] && echo "</tbody>"
   echo "</table>[<A href='cgi:${this}'>another query</A>]&nbsp;&nbsp;&nbsp;[<A href='#top'>top of page</A>]"
}

# launch konqueror if not called as cgi script...
[ -z "${!QUERY_STRING*}" ] && run_normal $*

# set defaults and read configuration
CHK_SYSLOG="checked"
CHK_ERRORS=
CHK_MAIL=
TXT_MAXLINES="10000"
TXT_COMPUTER="$HOSTNAME"
TXT_FILTER=""
CHK_EXCLUDE=

if   [ -r "$HOME/${this}.conf" ] ; then . $HOME/${this}.conf
elif [ -r "/etc/${this}.conf" ]  ; then . /etc/${this}.conf
fi

# cgi call: send html frame and exec worker ...
echo -e "Content-Type: text/html ; charset=utf-8\n\n"
echo "<html><title>CentauriTools - Logfile Viewer</title>"
echo "<body><style>$STYLE</style><a name="top"></a>"

[ -z "$QUERY_STRING" ] && render_form || render_data

echo "<div class='spacer'></div>"
echo "<h4 name="bottom">Version 0.1 &nbsp;&nbsp;&nbsp;&copy; 2005-2006"
echo " <a href="http://www.j-pfennig.de/LinuxImHaus">Dr. J&uuml;rgen Pfennig</a>"
echo " - code published under BSD license.</h4></body></html>"

# end

Verweise

KDE KioSlave:
http://de.wikipedia.org/wiki/KDE_Input/Output
CGI (Common Gateway Interface) in Wikipedia:
http://de.wikipedia.org/wiki/Common_Gateway_Interface
Ein bekanntes HTML/CSS Tutorial:
http://www.selfhtml.org