] "
if(int(@a)-3 < 0);
delete($hash->{HELPER}{EVNTLOG});
delete($hash->{HELPER}{FHEMLOG});
delete($hash->{HELPER}{IDENT});
setidrex($hash,$a[3]) if($a[3]);
setidrex($hash,$a[4]) if($a[4]);
setidrex($hash,$a[5]) if($a[5]);
return "Bad regexp: starting with *"
if((defined($hash->{HELPER}{EVNTLOG}) && $hash->{HELPER}{EVNTLOG} =~ m/^\*/) || (defined($hash->{HELPER}{FHEMLOG}) && $hash->{HELPER}{FHEMLOG} =~ m/^\*/));
eval { "Hallo" =~ m/^$hash->{HELPER}{EVNTLOG}$/ } if($hash->{HELPER}{EVNTLOG});
return "Bad regexp: $@" if($@);
eval { "Hallo" =~ m/^$hash->{HELPER}{FHEMLOG}$/ } if($hash->{HELPER}{FHEMLOG});
return "Bad regexp: $@" if($@);
$hash->{PEERHOST} = $a[2]; # Destination Host (Syslog Server)
$hash->{MYHOST} = hostfqdn (); # FQDN eigener Host
$hash->{HELPER}{PID} = $$; # PROCID in IETF
$hash->{VERSION} = $Log2SyslogVn;
$logInform{$hash->{NAME}} = "fhemlog_Log"; # Funktion die in hash %loginform für $name eingetragen wird
readingsSingleUpdate($hash, "state", "initialized", 1);
return undef;
}
sub Log2Syslog_Undef($$) {
my ($hash, $name) = @_;
return undef;
}
sub Log2Syslog_Delete($$) {
my ($hash, $arg) = @_;
delete $logInform{$hash->{NAME}};
return undef;
}
################################################################
sub Log2Syslog_Attr {
my ($cmd,$name,$aName,$aVal) = @_;
my $hash = $defs{$name};
my $do;
# $cmd can be "del" or "set"
# $name is device name
# aName and aVal are Attribute name and value
if ($aName eq "disable") {
if($cmd eq "set") {
$do = ($aVal) ? 1 : 0;
}
$do = 0 if($cmd eq "del");
my $val = ($do == 1 ? "disabled" : "active");
readingsSingleUpdate($hash, "state", $val, 1);
}
if ($cmd eq "set" && $aName eq "port") {
if($aVal !~ m/^\d+$/) { return " The Value of \"$aName\" is not valid. Use only figures !";}
}
return undef;
}
#################################################################################
# Eventlogging
#################################################################################
sub event_Log($$) {
# $hash is my entry, $dev is the entry of the changed device
my ($hash,$dev) = @_;
my $name = $hash->{NAME};
my $rex = $hash->{HELPER}{EVNTLOG};
my ($prival,$sock);
return if(IsDisabled($name) || !$rex);
my $events = deviceEvents($dev, AttrVal($name, "addStateEvent", 0));
return if(!$events);
my $n = $dev->{NAME};
my $max = int(@{$events});
my $tn = $dev->{NTFY_TRIGGERTIME};
my $ct = $dev->{CHANGETIME};
for (my $i = 0; $i < $max; $i++) {
my $txt = $events->[$i];
$txt = "" if(!defined($txt));
$txt = charfilter($hash,$txt);
my $tim = (($ct && $ct->[$i]) ? $ct->[$i] : $tn);
my ($date,$time) = split(" ",$tim);
if($n =~ m/^$rex$/ || "$n:$txt" =~ m/^$rex$/ || "$tim:$n:$txt" =~ m/^$rex$/) {
my $otp = "$n $txt";
$otp = "$tim $otp" if AttrVal($name,'addTimestamp',0);
$prival = setprival($txt);
my $data = setpayload($hash,$prival,$date,$time,$otp,"event");
$sock = setsock($hash);
if ($sock) {
eval {$sock->send($data);};
$sock->close();
}
}
}
return "";
}
#################################################################################
# FHEM system logging
#################################################################################
sub fhemlog_Log($$) {
my ($name,$raw) = @_;
my $hash = $defs{$name};
my $rex = $hash->{HELPER}{FHEMLOG};
my ($prival,$sock);
return if(IsDisabled($name) || !$rex);
my ($date,$time,$vbose,undef,$txt) = split(" ",$raw,5);
$txt = charfilter($hash,$txt);
$date =~ s/\./-/g;
my $tim = $date." ".$time;
if($txt =~ m/^$rex$/ || "$vbose: $txt" =~ m/^$rex$/) {
my $otp = "$vbose: $txt";
$otp = "$tim $otp" if AttrVal($name,'addTimestamp',0);
$prival = setprival($txt,$vbose);
my $data = setpayload($hash,$prival,$date,$time,$otp,"fhem");
Log3Syslog($name, 4, "$name - Payload created: $data");
$sock = setsock($hash);
if ($sock) {
eval {$sock->send($data);};
$sock->close();
}
}
return;
}
###############################################################################
# Helper für ident & Regex setzen
###############################################################################
sub setidrex ($$) {
my ($hash,$a) = @_;
$hash->{HELPER}{EVNTLOG} = (split("event:",$a))[1] if(lc($a) =~ m/^event:.*/);
$hash->{HELPER}{FHEMLOG} = (split("fhem:",$a))[1] if(lc($a) =~ m/^fhem:.*/);
$hash->{HELPER}{IDENT} = (split("ident:",$a))[1] if(lc($a) =~ m/^ident:.*/);
return;
}
###############################################################################
# Zeichencodierung für Payload filtern
###############################################################################
sub charfilter ($$) {
my ($hash,$txt) = @_;
my $name = $hash->{NAME};
# nur erwünschte Zeichen in payload, ASCII %d32-126
$txt =~ s/ß/ss/g;
$txt =~ s/ä/ae/g;
$txt =~ s/ö/oe/g;
$txt =~ s/ü/ue/g;
$txt =~ s/Ä/AE/g;
$txt =~ s/Ö/OE/g;
$txt =~ s/Ü/UE/g;
$txt =~ tr/ A-Za-z0-9!"#$%&'()*+,-.\/:;<=>?@[\]^_`{|}~//cd;
return($txt);
}
###############################################################################
# erstelle Socket
###############################################################################
sub setsock ($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $host = $hash->{PEERHOST};
my $port = AttrVal($name, "port", 514);
my $type = lc(AttrVal($name, "type", "udp"));
my $st = "active";
# Create Socket and check if successful
my $sock = new IO::Socket::INET (PeerHost => $host, PeerPort => $port, Proto => $type);
$st = "unable for open socket for $host, $type, $port" if (!$sock);
readingsSingleUpdate($hash, "state", $st, 1) if($st ne OldValue($name));
# Logausgabe (nur in das fhem Logfile !)
$st = "Socket opened for Host: $host, Protocol: $type, Port: $port, TLS: ".AttrVal($name, 'TLS', 0) if($sock);
Log3Syslog($name, 5, "$name - $st");
return($sock);
}
###############################################################################
# set PRIVAL (severity & facility)
###############################################################################
sub setprival ($;$$) {
my ($txt,$vbose) = @_;
my $prival;
# Priority = (facility * 8) + severity
# https://tools.ietf.org/pdf/rfc5424.pdf
# determine facility
my $fac = 5; # facility by syslogd
# calculate severity
# mapping verbose level to severity
# 0: Critical -> 2
# 1: Error -> 3
# 2: Warning -> 4
# 3: Notice -> 5
# 4: Informational -> 6
# 5: Debug -> 7
my $sv = 5; # notice (default)
if ($vbose) {
# map verbose to severity
$sv = 2 if ($vbose == 0);
$sv = 3 if ($vbose == 1);
$sv = 4 if ($vbose == 2);
$sv = 5 if ($vbose == 3);
$sv = 6 if ($vbose == 4);
$sv = 7 if ($vbose == 5);
}
$sv = 3 if (lc($txt) =~ m/error/); # error condition
$sv = 4 if (lc($txt) =~ m/warning/); # warning conditions
$prival = ($fac*8)+$sv;
return($prival);
}
###############################################################################
# erstellen Payload für Syslog
###############################################################################
sub setpayload ($$$$$$) {
my ($hash,$prival,$date,$time,$otp,$lt) = @_;
my $name = $hash->{NAME};
my $ident = ($hash->{HELPER}{IDENT}?$hash->{HELPER}{IDENT}:$name)."_".$lt;
my $myhost = $hash->{MYHOST}?$hash->{MYHOST}:"0.0.0.0";
my $lf = AttrVal($name, "logFormat", "IETF");
my $data;
my ($year,$month,$day) = split("-",$date);
if ($lf eq "BSD") {
# BSD Protokollformat https://tools.ietf.org/html/rfc3164
$time = (split(".",$time))[0]; # msec ist nicht erlaubt
$month = $Log2Syslog_BSDMonth{$month}; # Monatsmapping, z.B. 01 -> Jan
$day =~ s/0/ / if($day =~ m/^0.*$/); # in Tagen < 10 muss 0 durch Space ersetzt werden
$ident = substr($ident,0, $RFC3164len{TAG}); # Länge TAG Feld begrenzen
no warnings 'uninitialized';
$data = "<$prival>$month $day $time $myhost TAG$ident: $otp";
use warnings;
$data = substr($data,0, $RFC3164len{DL}); # Länge Total begrenzen
}
if ($lf eq "IETF") {
# IETF Protokollformat https://tools.ietf.org/html/rfc5424
my $pid = $hash->{HELPER}{PID};
my $mid = "FHEM"; # message ID, identify type of message, e.g. for firewall filter
my $tim = $date."T".$time;
no warnings 'uninitialized';
$data = "<$prival>1 $tim $myhost $ident $pid $mid - : $otp";
use warnings;
}
return($data);
}
###############################################################################
# eigene Log3-Ableitung - Schleife vermeiden
###############################################################################
sub Log3Syslog($$$) {
my ($dev, $loglevel, $text) = @_;
our ($logopened,$currlogfile);
$dev = $dev->{NAME} if(defined($dev) && ref($dev) eq "HASH");
if(defined($dev) &&
defined($attr{$dev}) &&
defined (my $devlevel = $attr{$dev}{verbose})) {
return if($loglevel > $devlevel);
} else {
return if($loglevel > $attr{global}{verbose});
}
my ($seconds, $microseconds) = gettimeofday();
my @t = localtime($seconds);
my $nfile = ResolveDateWildcards($attr{global}{logfile}, @t);
OpenLogfile($nfile) if(!$currlogfile || $currlogfile ne $nfile);
my $tim = sprintf("%04d.%02d.%02d %02d:%02d:%02d",
$t[5]+1900,$t[4]+1,$t[3], $t[2],$t[1],$t[0]);
if($attr{global}{mseclog}) {
$tim .= sprintf(".%03d", $microseconds/1000);
}
if($logopened) {
print LOG "$tim $loglevel: $text\n";
} else {
print "$tim $loglevel: $text\n";
}
return undef;
}
1;
=pod
=item helper
=item summary forwards FHEM system logs and/or events to a syslog server
=item summary_DE leitet FHEM Systemlogs und/oder Events an einen Syslog-Server weiter
=begin html
Log2Syslog
Send FHEM system log entries and/or FHEM events to an external syslog server.
Prerequisits
The additional perl module "IO::Socket::INET" must be installed on your system.
Install this package from cpan or by
apt-get install libio-socket-multicast-perl (only on Debian based installations)
Define
define <name> Log2Syslog <destination host> [ident:<ident>] [event:<regexp>] [fhem:<regexp>]
<destination host> = host where the syslog server is running
[ident:<ident>] = optional program identifier. If not set the device name will be used as default
[event:<regexp>] = optional regex to filter events for logging
[fhem:<regexp>] = optional regex to filter fhem system log for logging
After definition the new device sends all new appearing fhem systemlog entries and events to the destination host,
port=514/UDP format:IETF, immediately without further settings if the regex for fhem or event were set.
Without setting regex no fhem system log or event log will be forwarded.
The verbose level of FHEM system logs will convert into equivalent syslog severity level.
Thurthermore the message text will be scanned for signal terms "warning" and "error" (with case insensitivity).
Dependent off the severity will be set equivalent as well. If a severity is already set by verbose level, it wil be overwritten
by the level according to the signal term found in the message text.
Lookup table Verbose-Level to Syslog severity level:
verbose-Level | Schweregrad in Syslog |
0 | Critical |
1 | Error |
2 | Warning |
3 | Notice |
4 | Informational |
5 | Debug |
Example to log anything:
define splunklog Log2Syslog fhemtest 192.168.2.49 ident:Test event:.* fhem:.*
will produce output like this raw example of a splunk syslog server:
Aug 18 21:06:46 fhemtest.myds.me 1 2017-08-18T21:06:46 fhemtest.myds.me Test_event 13339 FHEM - : LogDB sql_processing_time: 0.2306
Aug 18 21:06:46 fhemtest.myds.me 1 2017-08-18T21:06:46 fhemtest.myds.me Test_event 13339 FHEM - : LogDB background_processing_time: 0.2397
Aug 18 21:06:45 fhemtest.myds.me 1 2017-08-18T21:06:45 fhemtest.myds.me Test_event 13339 FHEM - : LogDB CacheUsage: 21
Aug 18 21:08:27 fhemtest.myds.me 1 2017-08-18T21:08:27.760 fhemtest.myds.me Test_fhem 13339 FHEM - : 4: CamTER - Informations of camera Terrasse retrieved
Aug 18 21:08:27 fhemtest.myds.me 1 2017-08-18T21:08:27.095 fhemtest.myds.me Test_fhem 13339 FHEM - : 4: CamTER - CAMID already set - ignore get camid
Attributes
=end html
=begin html_DE
Log2Syslog
Sendet FHEM Systemlog Einträge und/oder Events an einen externen Syslog-Server weiter.
Voraussetzungen
Es wird das Perl Modul "IO::Socket::INET" benötigt und muss installiert sein.
Das Modul kann über CPAN oder mit
apt-get install libio-socket-multicast-perl (auf Debian Linux Systemen)
installiert werden.
Definition
define <name> Log2Syslog <Zielhost> [ident:<ident>] [event:<regexp>] [fhem:<regexp>]
<Zielhost> = Host (Name oder IP-Adresse) auf dem der Syslog-Server läuft
[ident:<ident>] = optinaler Programm Identifier. Wenn nicht gesetzt wird per default der Devicename benutzt.
[event:<regexp>] = optionaler regulärer Ausdruck zur Filterung von Events zur Weiterleitung
[fhem:<regexp>] = optionaler regulärer Ausdruck zur Filterung von FHEM Logs zur Weiterleitung
Direkt nach der Definition sendet das neue Device alle neu auftretenden FHEM Systemlog Einträge und Events ohne weitere
Einstellungen an den Zielhost, Port=514/UDP Format=IETF, wenn reguläre Ausdrücke für Events/FHEM angegeben wurden.
Wurde kein Regex gesetzt, erfolgt keine Weiterleitung von Events oder FHEM Systemlogs.
Die Verbose-Level der FHEM Systemlogs werden in entsprechende Schweregrade der Syslog-Messages umgewandelt.
Weiterhin wird der Meldungstext der FHEM Systemlogs und Events nach den Signalwörtern "warning" und "error" durchsucht
(Groß- /Kleinschreibung wird nicht beachtet). Davon abhängig wird der Schweregrad ebenfalls äquivalent gesetzt und überschreibt
einen eventuell bereits durch Verbose-Level gesetzten Schweregrad.
Umsetzungstabelle Verbose-Level in Syslog-Schweregrad Stufe:
Verbose-Level | Schweregrad in Syslog |
0 | Critical |
1 | Error |
2 | Warning |
3 | Notice |
4 | Informational |
5 | Debug |
Beispiel:
define splunklog Log2Syslog fhemtest 192.168.2.49 ident:Test event:.* fhem:.*
Es werden alle Events weitergeleitet wie deses Beispiel der raw-Ausgabe eines Splunk Syslog Servers zeigt::
Aug 18 21:06:46 fhemtest.myds.me 1 2017-08-18T21:06:46 fhemtest.myds.me Test_event 13339 FHEM - : LogDB sql_processing_time: 0.2306
Aug 18 21:06:46 fhemtest.myds.me 1 2017-08-18T21:06:46 fhemtest.myds.me Test_event 13339 FHEM - : LogDB background_processing_time: 0.2397
Aug 18 21:06:45 fhemtest.myds.me 1 2017-08-18T21:06:45 fhemtest.myds.me Test_event 13339 FHEM - : LogDB CacheUsage: 21
Aug 18 21:08:27 fhemtest.myds.me 1 2017-08-18T21:08:27.760 fhemtest.myds.me Test_fhem 13339 FHEM - : 4: CamTER - Informations of camera Terrasse retrieved
Aug 18 21:08:27 fhemtest.myds.me 1 2017-08-18T21:08:27.095 fhemtest.myds.me Test_fhem 13339 FHEM - : 4: CamTER - CAMID already set - ignore get camid
Attribute
=end html_DE
=cut