###################################################################################################################### # $Id: 93_Log2Syslog.pm 16688 2018-05-04 20:09:12Z DS_Starter $ ###################################################################################################################### # 93_Log2Syslog.pm # # (c) 2017-2018 by Heiko Maaz # e-mail: Heiko dot Maaz at t-online dot de # # This script is part of fhem. # # Fhem is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Fhem is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # # The module is based on idea and input from betateilchen 92_rsyslog.pm # # Implements the Syslog Protocol of RFC 5424 https://tools.ietf.org/html/rfc5424 # and RFC 3164 https://tools.ietf.org/html/rfc3164 and # TLS Transport according to RFC5425 https://tools.ietf.org/pdf/rfc5425.pdf as well # ###################################################################################################################### # Versions History: # # 4.5.0 06.08.2018 Regex capture groups used in parsePayload to set variables, parsing of BSD changed # 4.4.0 04.08.2018 Attribute "outputFields" added # 4.3.0 03.08.2018 Attribute "parseFn" added # 4.2.0 03.08.2018 evaluate sender peer ip-address/hostname, use it as reading in event generation # 4.1.0 02.08.2018 state event generation changed # 4.0.0 30.07.2018 server mode (Collector) # 3.2.1 04.05.2018 fix compatibility with newer IO::Socket::SSL on debian 9, attr ssldebug for # debugging SSL messages # 3.2.0 22.11.2017 add NOTIFYDEV if possible # 3.1.0 28.08.2017 get-function added, commandref revised, $readingFnAttributes deleted # 3.0.0 27.08.2017 change attr type to protocol, ready to check in # 2.6.0 26.08.2017 more than one Log2Syslog device can be created # 2.5.2 26.08.2018 fix in splitting timestamp, change Log2Syslog_trate using internaltimer with attr # rateCalcRerun, function Log2Syslog_closesock # 2.5.1 24.08.2017 some fixes # 2.5.0 23.08.2017 TLS encryption available, new readings, $readingFnAttributes # 2.4.1 21.08.2017 changes in sub Log2Syslog_charfilter, change PROCID to $hash->{SEQNO} # switch to non-blocking in subs event/Log2Syslog_fhemlog # 2.4.0 20.08.2017 new sub Log2Syslog_Log3slog for entries in local fhemlog only -> verbose support # 2.3.1 19.08.2017 commandref revised # 2.3.0 18.08.2017 new parameter "ident" in DEF, sub setidex, Log2Syslog_charfilter # 2.2.0 17.08.2017 set BSD data length, set only acceptable characters (USASCII) in payload # commandref revised # 2.1.0 17.08.2017 sub Log2Syslog_setsock created # 2.0.0 16.08.2017 create syslog without SYS::SYSLOG # 1.1.1 13.08.2017 registrate Log2Syslog_fhemlog to %loginform in case of sending fhem-log # attribute timeout, commandref revised # 1.1.0 26.07.2017 add regex search to sub Log2Syslog_fhemlog # 1.0.0 25.07.2017 initial version package main; use strict; use warnings; use Scalar::Util qw(looks_like_number); use Encode qw(encode_utf8); eval "use IO::Socket::INET;1" or my $MissModulSocket = "IO::Socket::INET"; eval "use Net::Domain qw(hostname hostfqdn hostdomain domainname);1" or my $MissModulNDom = "Net::Domain"; ############################################################################### # Forward declarations # sub Log2Syslog_Log3slog($$$); my $Log2SyslogVn = "4.5.0"; # Mappinghash BSD-Formatierung Monat my %Log2Syslog_BSDMonth = ( "01" => "Jan", "02" => "Feb", "03" => "Mar", "04" => "Apr", "05" => "May", "06" => "Jun", "07" => "Jul", "08" => "Aug", "09" => "Sep", "10" => "Oct", "11" => "Nov", "12" => "Dec", "Jan" => "01", "Feb" => "02", "Mar" => "03", "Apr" => "04", "May" => "05", "Jun" => "06", "Jul" => "07", "Aug" => "08", "Sep" => "09", "Oct" => "10", "Nov" => "11", "Dec" => "12" ); # Mappinghash Severity my %Log2Syslog_Severity = ( "0" => "Emergency", "1" => "Alert", "2" => "Critical", "3" => "Error", "4" => "Warning", "5" => "Notice", "6" => "Informational", "7" => "Debug", "Emergency" => "0", "Alert" => "1", "Critical" => "2", "Error" => "3", "Warning" => "4", "Notice" => "5", "Informational" => "6", "Debug" => "7" ); # Mappinghash Facility my %Log2Syslog_Facility = ( "0" => "kernel", "1" => "user", "2" => "mail", "3" => "system", "4" => "security", "5" => "syslog", "6" => "printer", "7" => "network", "8" => "UUCP", "9" => "clock", "10" => "security", "11" => "FTP", "12" => "NTP", "13" => "log_audit", "14" => "log_alert", "15" => "clock", "16" => "local0", "17" => "local1", "18" => "local2", "19" => "local3", "20" => "local4", "21" => "local5", "22" => "local6", "23" => "local7" ); # Längenvorgaben nach RFC3164 my %RFC3164len = ("TAG" => 32, # max. Länge TAG-Feld "DL" => 1024 # max. Lange Message insgesamt ); # Längenvorgaben nach RFC5425 my %RFC5425len = ("DL" => 8192, # max. Lange Message insgesamt mit TLS "HST" => 255, # max. Länge Hostname "ID" => 48, # max. Länge APP-NAME bzw. Ident "PID" => 128, # max. Länge Proc-ID "MID" => 32 # max. Länge MSGID ); ############################################################################### sub Log2Syslog_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "Log2Syslog_Define"; $hash->{UndefFn} = "Log2Syslog_Undef"; $hash->{DeleteFn} = "Log2Syslog_Delete"; $hash->{GetFn} = "Log2Syslog_Get"; $hash->{AttrFn} = "Log2Syslog_Attr"; $hash->{NotifyFn} = "Log2Syslog_eventlog"; $hash->{ReadFn} = "Log2Syslog_Read"; $hash->{AttrList} = "addStateEvent:1,0 ". "disable:1,0 ". "addTimestamp:0,1 ". "outputFields:sortable,PRIVAL,FAC,SEV,TS,HOST,DATE,TIME,ID,PID,MID,SDFIELD,CONT ". "logFormat:BSD,IETF ". "parseProfile:BSD,IETF,raw,ParseFn ". "parseFn:textField-long ". "ssldebug:0,1,2,3 ". "TLS:1,0 ". "timeout ". "protocol:UDP,TCP ". "port ". "rateCalcRerun ". $readingFnAttributes ; return undef; } ############################################################################### sub Log2Syslog_Define($@) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; return "Error: Perl module ".$MissModulSocket." is missing. Install it on Debian with: sudo apt-get install libio-socket-multicast-perl" if($MissModulSocket); return "Error: Perl module ".$MissModulNDom." is missing." if($MissModulNDom); # Example Sender: define splunklog Log2Syslog splunk.myds.me ident:Prod event:.* fhem:.* # Example Collector: define SyslogServer Log2Syslog delete($hash->{HELPER}{EVNTLOG}); delete($hash->{HELPER}{FHEMLOG}); delete($hash->{HELPER}{IDENT}); $hash->{MYHOST} = hostfqdn(); # FQDN eigener Host if(int(@a)-3 < 0){ # Einrichtung Servermode (Collector) Log3 ($name, 3, "Log2Syslog $name - entering Syslog servermode ..."); $hash->{MODEL} = "Collector"; Log2Syslog_initServer("$name,global"); } else { # Sendermode $hash->{MODEL} = "Sender"; Log2Syslog_setidrex($hash,$a[3]) if($a[3]); Log2Syslog_setidrex($hash,$a[4]) if($a[4]); Log2Syslog_setidrex($hash,$a[5]) if($a[5]); 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($@); return "Bad regexp: starting with *" if((defined($hash->{HELPER}{EVNTLOG}) && $hash->{HELPER}{EVNTLOG} =~ m/^\*/) || (defined($hash->{HELPER}{FHEMLOG}) && $hash->{HELPER}{FHEMLOG} =~ m/^\*/)); # nur Events dieser Devices an NotifyFn weiterleiten, NOTIFYDEV wird gesetzt wenn möglich notifyRegexpChanged($hash, $hash->{HELPER}{EVNTLOG}) if($hash->{HELPER}{EVNTLOG}); $hash->{PEERHOST} = $a[2]; # Destination Host (Syslog Server) } $hash->{SEQNO} = 1; # PROCID in IETF, wird kontinuierlich hochgezählt $hash->{VERSION} = $Log2SyslogVn; $logInform{$hash->{NAME}} = "Log2Syslog_fhemlog"; # Funktion die in hash %loginform für $name eingetragen wird $hash->{HELPER}{SSLVER} = "n.a."; # Initialisierung $hash->{HELPER}{SSLALGO} = "n.a."; # Initialisierung $hash->{HELPER}{LTIME} = time(); # Init Timestmp f. Ratenbestimmung $hash->{HELPER}{OLDSEQNO} = $hash->{SEQNO}; # Init Sequenznummer f. Ratenbestimmung $hash->{HELPER}{OLDSTATE} = "initialized"; readingsBeginUpdate($hash); readingsBulkUpdate($hash, "SSL_Version", "n.a."); readingsBulkUpdate($hash, "SSL_Algorithm", "n.a."); readingsBulkUpdate($hash, "Transfered_logs_per_minute", 0); readingsBulkUpdate($hash, "state", "initialized") if($hash->{MODEL}=~/Sender/); readingsEndUpdate($hash,1); Log2Syslog_trate($hash); # regelm. Berechnung Transfer Rate starten return undef; } ################################################################################################# # Syslog Collector (Server-Mode) initialisieren # (im Collector Model) ################################################################################################# sub Log2Syslog_initServer($) { my ($a) = @_; my ($name,$global) = split(",",$a); my $hash = $defs{$name}; RemoveInternalTimer($hash, "Log2Syslog_initServer"); return if(IsDisabled($name)); if($init_done != 1) { InternalTimer(gettimeofday()+5, "Log2Syslog_initServer", "$name,$global", 0); return; } # Inititialisierung FHEM ist fertig -> Attribute geladen my $port = AttrVal($name, "TLS", 0)?AttrVal($name, "port", 6514):AttrVal($name, "port", 1514); my $protocol = lc(AttrVal($name, "protocol", "udp")); my $lh = $global ? ($global eq "global"? undef : $global) : ($hash->{IPV6} ? "::1" : "127.0.0.1"); Log3 $hash, 3, "Log2Syslog $name - Opening socket ..."; $hash->{SERVERSOCKET} = IO::Socket::INET->new( Domain => ($hash->{IPV6} ? AF_INET6() : AF_UNSPEC), # Linux bug LocalHost => $lh, Proto => $protocol, LocalPort => $port, ReuseAddr => 1 ); if(!$hash->{SERVERSOCKET}) { my $err = "Can't open Syslog Collector at $port: $!"; Log3 ($hash, 1, "Log2Syslog $name - $err"); ReadingsSingleUpdateValue ($hash, 'state', $err, 1); return; } $hash->{FD} = $hash->{SERVERSOCKET}->fileno(); $hash->{PORT} = $hash->{SERVERSOCKET}->sockport(); $hash->{PROTOCOL} = $protocol; $hash->{SEQNO} = 1; # PROCID wird kontinuierlich pro empfangenen Datensatz hochgezählt $hash->{HELPER}{OLDSEQNO} = $hash->{SEQNO}; # Init Sequenznummer f. Ratenbestimmung $hash->{INTERFACE} = $lh?$lh:"global"; Log3 ($hash, 3, "Log2Syslog $name - port $hash->{PORT}/$protocol opened for Syslog Collector on interface \"$hash->{INTERFACE}\""); ReadingsSingleUpdateValue ($hash, "state", "initialized", 1); delete($readyfnlist{"$name.$port"}); $selectlist{"$name.$port"} = $hash; return; } ################################################################################################# # Syslog Collector Daten empfangen # (im Collector Model) ################################################################################################# # called from the global loop, when the select for hash->{FD} reports data sub Log2Syslog_Read($) { my ($hash) = @_; my $name = $hash->{NAME}; my $socket = $hash->{SERVERSOCKET}; my $st = ReadingsVal($name,"state","active"); my $pp = AttrVal($name, "parseProfile", "IETF"); my ($err,$data,$ts,$phost,$pl); return if(IsDisabled($name) || $hash->{MODEL} !~ /Collector/); if($pp =~ /BSD/) { # BSD-Format unless($socket->recv($data, $RFC3164len{DL})) { # ungültige BSD-Payload return if(length($data) == 0); Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - received ".length($data)." bytes, but a BSD-message has to be 1024 bytes or less."); Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - Seq \"$hash->{SEQNO}\" invalid data: $data"); $st = "receive error - see logfile"; } else { # parse Payload ($err,$phost,$ts,$pl) = Log2Syslog_parsePayload($hash,$data); $hash->{SEQNO}++; if($err) { $st = "parse error - see logfile"; } else { $st = "active"; $pl = "$phost: $pl"; Log2Syslog_Trigger($hash,$ts,$pl); } } } elsif($pp =~ /IETF/) { # IETF-Format unless($socket->recv($data, $RFC5425len{DL})) { # ungültige IETF-Payload return if(length($data) == 0); Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - received ".length($data)." bytes, but a IETF-message has to be 8192 bytes or less."); Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - Seq \"$hash->{SEQNO}\" invalid data: $data"); $st = "receive error - see logfile"; } else { # parse Payload ($err,$phost,$ts,$pl) = Log2Syslog_parsePayload($hash,$data); $hash->{SEQNO}++; if($err) { $st = "parse error - see logfile"; } else { $st = "active"; $pl = "$phost: $pl"; Log2Syslog_Trigger($hash,$ts,$pl); } } } else { # raw oder User eigenes Format $socket->recv($data, 8192); ($err,$phost,$ts,$pl) = Log2Syslog_parsePayload($hash,$data); $hash->{SEQNO}++; if($err) { $st = "parse error - see logfile"; } else { $st = "active"; $pl = "$phost: $pl"; Log2Syslog_Trigger($hash,$ts,$pl); } } # readingsSingleUpdate($hash, "state", $st, 1) if($st ne OldValue($name)); my $evt = ($st eq $hash->{HELPER}{OLDSTATE})?0:1; readingsSingleUpdate($hash, "state", $st, $evt); $hash->{HELPER}{OLDSTATE} = $st; return; } ############################################################################### # Parsen Payload für Syslog-Server # (im Collector Model) ############################################################################### sub Log2Syslog_parsePayload($$) { my ($hash,$data) = @_; my $name = $hash->{NAME}; my $pp = AttrVal($name, "parseProfile", "IETF"); my $severity = ""; my $facility = ""; my @evf = split(",",AttrVal($name, "outputFields", "FAC,SEV,ID,CONT")); # auszugebene Felder im Event/Reading my ($Mmm,$dd,$delimiter,$day,$ietf,$err,$pl); # Hash zur Umwandlung Felder in deren Variablen my ($prival,$ts,$host,$date,$time,$id,$pid,$mid,$sdfield,$cont); my $fac = ""; my $sev = ""; my %fh = (PRIVAL => \$prival, FAC => \$fac, SEV => \$sev, TS => \$ts, HOST => \$host, DATE => \$date, TIME => \$time, ID => \$id, PID => \$pid, MID => \$mid, SDFIELD => \$sdfield, CONT => \$cont, DATA => \$data ); Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name - ### new Syslog message Parsing ### "); # Sender Host / IP-Adresse ermitteln, $phost wird Reading im Event my ($phost,$paddr) = Log2Syslog_evalPeer($hash); $phost = $phost?$phost:$paddr; Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name - raw message -> $data"); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); # Istzeit Ableitung $year = $year+1900; if ($pp =~ /raw/) { Log2Syslog_Log3slog($name, 4, "$name - $data"); $ts = TimeNow(); $pl = $data; } elsif($pp eq "BSD") { # BSD Protokollformat https://tools.ietf.org/html/rfc3164 # Beispiel data "<$prival>$month $day $time $myhost $id: : $otp" $data =~ /^<(?\d{1,3})>(((((\s*(?\w{3})\s*)?(?\d{1,2})\s*)?(?\d{2}:\d{2}:\d{2})\s*)?(?\S+)\s*)?(?[a-zA-Z_0-9.]*))?(?\W*)(?.*)$/; $prival = $+{prival}; # must $Mmm = $+{month}; # should $dd = $+{day}; # should $time = $+{time}; # should $host = $+{host}; # should $id = $+{id}; # should $delimiter = $+{delimiter}; # should $cont = $+{cont}; # should if(!$prival) { $err = 1; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - error parse msg -> $data"); } else { if( $Mmm && $dd && $time =~ /(d{2}:\d{2}:\d{2})/ ) { my $month = $Log2Syslog_BSDMonth{$Mmm}; $day = (length($dd) == 1)?("0".$dd):$dd; $ts = "$year-$month-$day $time"; } if(looks_like_number($prival)) { $facility = int($prival/8) if($prival >= 0 && $prival <= 191); $severity = $prival-($facility*8); $fac = $Log2Syslog_Facility{$facility}; $sev = $Log2Syslog_Severity{$severity}; } else { $err = 1; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - error parse msg -> $data"); } Log2Syslog_Log3slog($name, 4, "$name - parsed message -> FAC: $fac, SEV: $sev, TS: $ts, HOST: $host, ID: $id, CONT: $cont"); $host = "" if($host eq "-"); $phost = $host?$host:$phost; # Payload zusammenstellen für Event/Reading $pl = ""; my $i = 0; foreach my $f (@evf) { if(${$fh{$f}}) { $pl .= " || " if($i); $pl .= "$f: ".${$fh{$f}}; $i++; } } } } elsif($pp eq "IETF") { # IETF Protokollformat https://tools.ietf.org/html/rfc5424 # Beispiel data "<$prival>1 $tim $host $id $pid $mid - : $otp"; $data =~ /^<(?\d{1,3})>(?\d+)\s(?\d{4}-\d{2}-\d{2})T(?\d{2}:\d{2}:\d{2})\S*\s(?\S*)\s(?\S*)\s(?\S*)\s(?\S*)\s(?\[.*\]|-)\s(?.*)$/; $prival = $+{prival}; # must $ietf = $+{ietf}; # must $date = $+{date}; # must $time = $+{time}; # must $host = $+{host}; # should $id = $+{id}; # should $pid = $+{pid}; # should $mid = $+{mid}; # should $sdfield = $+{sdfield}; # must $cont = $+{cont}; # should if(!$prival || !$ietf || !$date || !$time) { $err = 1; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - error parse msg -> $data"); } else { $ts = "$date $time"; $cont =~ s/^:?(.*)$/$1/ if(lc($mid) eq "fhem"); # Modul Sender setzt vor $cont ein ":" (wegen Synology Compatibilität) if(looks_like_number($prival)) { $facility = int($prival/8) if($prival >= 0 && $prival <= 191); $severity = $prival-($facility*8); $fac = $Log2Syslog_Facility{$facility}; $sev = $Log2Syslog_Severity{$severity}; } else { $err = 1; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - error parse msg -> $data"); } # Längenbegrenzung nach RFC5424 $id = substr($id,0, ($RFC5425len{ID}-1)); $pid = substr($pid,0, ($RFC5425len{PID}-1)); $mid = substr($mid,0, ($RFC5425len{MID}-1)); $host = substr($host,0, ($RFC5425len{HST}-1)); Log2Syslog_Log3slog($name, 4, "$name - parsed message -> FAC: $fac, SEV: $sev, TS: $ts, HOST: $host, ID: $id, PID: $pid, MID: $mid, SDFIELD: $sdfield, CONT: $cont"); $host = "" if($host eq "-"); $phost = $host?$host:$phost; # Payload zusammenstellen für Event/Reading $pl = ""; my $i = 0; foreach my $f (@evf) { if(${$fh{$f}}) { $pl .= " || " if($i); $pl .= "$f: ".${$fh{$f}}; $i++; } } } } elsif($pp eq "ParseFn") { # user spezifisches Parsing my $parseFn = AttrVal( $name, "parseFn", "" ); $ts = TimeNow(); if( $parseFn =~ m/^\s*(\{.*\})\s*$/s ) { $parseFn = $1; } else { $parseFn = ''; } if($parseFn ne '') { my $PRIVAL = ""; my $TS = $ts; my $DATE = ""; my $TIME = ""; my $HOST = ""; my $ID = ""; my $PID = ""; my $MID = ""; my $CONT = ""; my $FAC = ""; my $SEV = ""; my $DATA = $data; my $SDFIELD = ""; eval $parseFn; if($@) { Log3 $name, 1, "DbLog $name -> error parseFn: $@"; $err = 1; } $prival = $PRIVAL if($PRIVAL =~ /\d{1,3}/); $date = $DATE if($DATE =~ /^(\d{4})-(\d{2})-(\d{2})$/); $time = $TIME if($TIME =~ /^(\d{2}):(\d{2}):(\d{2})$/); $ts = ($TS =~ /^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})$/)?$TS:($date && $time)?"$date $time":$ts; $host = $HOST if(defined $HOST); $id = $ID if(defined $ID); $pid = $PID if(defined $PID); $mid = $MID if(defined $MID); $cont = $CONT if(defined $CONT); $fac = $FAC if(defined $FAC); $sev = $SEV if(defined $SEV); $sdfield = $SDFIELD if(defined $SDFIELD); if($prival && looks_like_number($prival)) { $facility = int($prival/8) if($prival >= 0 && $prival <= 191); $severity = $prival-($facility*8); $fac = $Log2Syslog_Facility{$facility}; $sev = $Log2Syslog_Severity{$severity}; } else { $err = 1; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - error parse msg -> $data"); } Log2Syslog_Log3slog($name, 4, "$name - parsed message -> FAC: $fac, SEV: $sev, TS: $ts, HOST: $host, ID: $id, PID: $pid, MID: $mid, CONT: $cont"); $phost = $host?$host:$phost; # auszugebene Felder im Event/Reading my $ef = "PRIVAL,FAC,SEV,TS,HOST,DATE,TIME,ID,PID,MID,SDFIELD,CONT"; @evf = split(",",AttrVal($name, "outputFields", $ef)); # Payload zusammenstellen für Event/Reading $pl = ""; my $i = 0; foreach my $f (@evf) { if(${$fh{$f}}) { $pl .= " || " if($i); $pl .= "$f: ".${$fh{$f}}; $i++; } } } else { $err = 1; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - no parseFn defined."); } } if(AttrVal($name, "TLS", 0)) { # wenn Transport Layer Security (TLS) -> Transport Mapping for Syslog https://tools.ietf.org/pdf/rfc5425.pdf } return ($err,$phost,$ts,$pl); } ################################################################################################# # Syslog Collector Events erzeugen # (im Collector Model) ################################################################################################# sub Log2Syslog_Trigger($$$) { my ($hash,$ts,$pl) = @_; my $name = $hash->{NAME}; my $no_replace = 1; # Ersetzung von Events durch das Attribut eventMap verhindern if($hash->{CHANGED}) { push @{$hash->{CHANGED}}, $pl; } else { $hash->{CHANGED}[0] = $pl; } if($hash->{CHANGETIME}) { push @{$hash->{CHANGETIME}}, $ts; } else { $hash->{CHANGETIME}[0] = $ts; } my $ret = DoTrigger($name, undef, $no_replace); return; } ############################################################################### # Undef Funktion ############################################################################### sub Log2Syslog_Undef($$) { my ($hash, $name) = @_; RemoveInternalTimer($hash); if($hash->{MODEL} =~ /Collector/) { Log2Syslog_downServer($hash); } return undef; } ############################################################################### # Collector-Socket schließen ############################################################################### sub Log2Syslog_downServer($) { my ($hash) = @_; my $name = $hash->{NAME}; my $port = $hash->{PORT}; my $protocol = $hash->{PROTOCOL}; return if(!$hash->{SERVERSOCKET}); Log3 $hash, 3, "Log2Syslog $name - Closing socket $protocol/$port ..."; my $ret = $hash->{SERVERSOCKET}->close(); Log3 $hash, 1, "Log2Syslog $name - Can't close Syslog Collector at port $port: $!" if(!$ret); delete($hash->{SERVERSOCKET}); delete($selectlist{"$name.$port"}); delete($readyfnlist{"$name.$port"}); delete($hash->{FD}); return; } ############################################################################### # Delete Funktion ############################################################################### sub Log2Syslog_Delete($$) { my ($hash, $arg) = @_; delete $logInform{$hash->{NAME}}; return undef; } ############################################################################### # Get ############################################################################### sub Log2Syslog_Get($@) { my ($hash, @a) = @_; return "\"get X\" needs at least an argument" if ( @a < 2 ); my $name = $a[0]; my $opt = $a[1]; my $prop = $a[2]; my $getlist = "Unknown argument $opt, choose one of ". "certinfo:noArg " ; return if(IsDisabled($name)); my($sock,$cert,@certs); if ($opt =~ /certinfo/) { if(ReadingsVal($name,"SSL_Version","n.a.") ne "n.a.") { $sock = Log2Syslog_setsock($hash); if(defined($sock)) { $cert = $sock->dump_peer_certificate(); Log2Syslog_closesock($hash,$sock); } } return $cert if($cert); return "no SSL session has been created"; } else { return "$getlist"; } 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 ($cmd eq "set" && $hash->{MODEL} !~ /Collector/ && $aName =~ /parseProfile|parseFn|outputFields/) { return "\"$aName\" is only valid for model \"Collector\""; } if ($cmd eq "set" && $hash->{MODEL} =~ /Collector/ && $aName =~ /addTimestamp|addStateEvent|protocol|logFormat|timeout|TLS/) { return "\"$aName\" is only valid for model \"Sender\""; } if ($aName eq "disable") { if($cmd eq "set") { $do = $aVal?1:0; } $do = 0 if($cmd eq "del"); my $val = $do?"disabled":"active"; readingsSingleUpdate($hash, "state", $val, 1); } if ($aName eq "TLS") { if($cmd eq "set") { $do = ($aVal) ? 1 : 0; } $do = 0 if($cmd eq "del"); if ($do == 0) { $hash->{HELPER}{SSLVER} = "n.a."; $hash->{HELPER}{SSLALGO} = "n.a."; readingsSingleUpdate($hash, "SSL_Version", "n.a.", 1); readingsSingleUpdate($hash, "SSL_Algorithm", "n.a.", 1); } } if ($aName =~ /rateCalcRerun/) { unless ($aVal =~ /^[0-9]+$/) { return "Value of $aName is not valid. Use only figures 0-9 without decimal places !";} $_[3] = 60 if($aVal < 60); RemoveInternalTimer($hash, "Log2Syslog_trate"); InternalTimer(gettimeofday()+5, "Log2Syslog_trate", $hash, 0); } if ($cmd eq "set" && $aName =~ /port|timeout/) { if($aVal !~ m/^\d+$/) { return " The Value of \"$aName\" is not valid. Use only figures !";} if($aName =~ /port/ && $hash->{MODEL} =~ /Collector/ && $init_done) { return "$aName \"$aVal\" is not valid because privileged ports are only usable by super users. Use a port grater than 1023." if($aVal < 1024); Log2Syslog_downServer($hash); RemoveInternalTimer($hash, "Log2Syslog_initServer"); InternalTimer(gettimeofday()+1.5, "Log2Syslog_initServer", "$name,global", 0); } } if ($cmd eq "set" && $aName =~ /parseFn/) { $_[3] = "{$aVal}" if($aVal !~ m/^\{.*\}$/s); $aVal = $_[3]; my %specials = ( "%DATA" => "1", "%PRIVAL" => "1", "%TS" => "1", "%DATE" => "1", "%TIME" => "1", "%HOST" => "1", "%ID" => "1", "%PID" => "1", "%MID" => "1", "%CONT" => "1", "%FAC" => "1", "%SDFIELD" => "1", "%SEV" => "1" ); my $err = perlSyntaxCheck($aVal, %specials); return $err if($err); } if ($cmd eq "set" && $aName =~ /parseProfile/ && $aVal =~ /ParseFn/) { return "You have to define a parse-function via attribute \"parseFn\" first !" if(!AttrVal($name,"parseFn","")); } if ($cmd eq "del" && $aName =~ /parseFn/ && AttrVal($name,"parseProfile","") eq "ParseFn" ) { return "You use a parse-function via attribute \"parseProfile\". Please change/delete attribute \"parseProfile\" first !"; } return; } ################################################################################# # Eventlogging ################################################################################# sub Log2Syslog_eventlog($$) { # $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,$data,$pid); return if(IsDisabled($name) || !$rex || $hash->{MODEL} !~ /Sender/); 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}; $sock = Log2Syslog_setsock($hash); if(defined($sock)) { for (my $i = 0; $i < $max; $i++) { my $txt = $events->[$i]; $txt = "" if(!defined($txt)); $txt = Log2Syslog_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 = Log2Syslog_setprival($txt); ($data,$pid) = Log2Syslog_setpayload($hash,$prival,$date,$time,$otp,"event"); next if(!$data); my $ret = syswrite $sock, $data."\n"; if($ret && $ret > 0) { Log2Syslog_Log3slog($name, 4, "$name - Payload sequence $pid sent\n"); } else { my $err = $!; Log2Syslog_Log3slog($name, 4, "$name - Warning - Payload sequence $pid NOT sent: $err\n"); my $st = "write error: $err"; my $evt = ($st eq $hash->{HELPER}{OLDSTATE})?0:1; readingsSingleUpdate($hash, "state", $st, $evt); $hash->{HELPER}{OLDSTATE} = $st; } } } Log2Syslog_closesock($hash,$sock); } return ""; } ################################################################################# # FHEM system logging ################################################################################# sub Log2Syslog_fhemlog($$) { my ($name,$raw) = @_; my $hash = $defs{$name}; my $rex = $hash->{HELPER}{FHEMLOG}; my ($prival,$sock,$err,$ret,$data,$pid); return if(IsDisabled($name) || !$rex || $hash->{MODEL} !~ /Sender/); my ($date,$time,$vbose,undef,$txt) = split(" ",$raw,5); $txt = Log2Syslog_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 = Log2Syslog_setprival($txt,$vbose); ($data,$pid) = Log2Syslog_setpayload($hash,$prival,$date,$time,$otp,"fhem"); return if(!$data); $sock = Log2Syslog_setsock($hash); if (defined($sock)) { $ret = syswrite $sock, $data."\n" if($data); if($ret && $ret > 0) { Log2Syslog_Log3slog($name, 4, "$name - Payload sequence $pid sent\n"); } else { my $err = $!; Log2Syslog_Log3slog($name, 4, "$name - Warning - Payload sequence $pid NOT sent: $err\n"); my $st = "write error: $err"; my $evt = ($st eq $hash->{HELPER}{OLDSTATE})?0:1; readingsSingleUpdate($hash, "state", $st, $evt); $hash->{HELPER}{OLDSTATE} = $st; } Log2Syslog_closesock($hash,$sock); } } return; } ############################################################################### # Helper für ident & Regex setzen ############################################################################### sub Log2Syslog_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 Log2Syslog_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 =~ s/€/EUR/g; $txt =~ tr/ A-Za-z0-9!"#$%&'()*+,-.\/:;<=>?@[\]^_`{|}~//cd; return($txt); } ############################################################################### # erstelle Socket ############################################################################### sub Log2Syslog_setsock ($) { my ($hash) = @_; my $name = $hash->{NAME}; my $host = $hash->{PEERHOST}; my $port = AttrVal($name, "TLS", 0)?AttrVal($name, "port", 6514):AttrVal($name, "port", 514); my $protocol = lc(AttrVal($name, "protocol", "udp")); my $st = ReadingsVal($name,"state","active"); my $timeout = AttrVal($name, "timeout", 0.5); my $ssldbg = AttrVal($name, "ssldebug", 0); my ($sock,$lo,$sslver,$sslalgo); return undef if($init_done != 1 || $hash->{MODEL} !~ /Sender/); if(AttrVal($name, "TLS", 0)) { # TLS gesicherte Verbindung # TLS Transport nach RFC5425 https://tools.ietf.org/pdf/rfc5425.pdf $attr{$name}{protocol} = "TCP" if(AttrVal($name, "protocol", "UDP") ne "TCP"); $sslver = "n.a."; $sslalgo = "n.a."; eval "use IO::Socket::SSL"; if($@) { $st = "$@"; } else { $sock = IO::Socket::INET->new(PeerHost => $host, PeerPort => $port, Proto => 'tcp', Blocking => 0); if (!$sock) { $st = "unable open socket for $host, $protocol, $port: $!"; } else { $sock->blocking(1); $IO::Socket::SSL::DEBUG = $ssldbg; eval { IO::Socket::SSL->start_SSL($sock, SSL_verify_mode => 0, SSL_version => "TLSv1_2:!TLSv1_1:!SSLv3:!SSLv23:!SSLv2", SSL_hostname => $host, SSL_veriycn_scheme => "rfc5425", SSL_veriycn_publicsuffix => '', Timeout => $timeout ) || undef $sock; }; $IO::Socket::SSL::DEBUG = 0; if($@) { $st = "SSL error: $@"; undef $sock; } elsif (!$sock) { $st = "SSL error: ".IO::Socket::SSL::errstr(); undef $sock; } else { $sslver = $sock->get_sslversion(); $sslalgo = $sock->get_fingerprint(); $sslalgo = (split("\\\$",$sslalgo))[0]; $lo = "Socket opened for Host: $host, Protocol: $protocol, Port: $port, TLS: 0"; $st = "active"; } } } } else { # erstellt ungesicherte Socket Verbindung $sslver = "n.a."; $sslalgo = "n.a."; $sock = new IO::Socket::INET (PeerHost => $host, PeerPort => $port, Proto => $protocol, Timeout => $timeout ); if (!$sock) { undef $sock; $st = "unable open socket for $host, $protocol, $port: $!"; } else { $sock->blocking(0); $st = "active"; # Logausgabe (nur in das fhem Logfile !) $lo = "Socket opened for Host: $host, Protocol: $protocol, Port: $port, TLS: 0"; } } my $evt = ($st eq $hash->{HELPER}{OLDSTATE})?0:1; readingsSingleUpdate($hash, "state", $st, $evt); $hash->{HELPER}{OLDSTATE} = $st; if($sslver ne $hash->{HELPER}{SSLVER}) { readingsSingleUpdate($hash, "SSL_Version", $sslver, 1); $hash->{HELPER}{SSLVER} = $sslver; } if($sslalgo ne $hash->{HELPER}{SSLALGO}) { readingsSingleUpdate($hash, "SSL_Algorithm", $sslalgo, 1); $hash->{HELPER}{SSLALGO} = $sslalgo; } Log2Syslog_Log3slog($name, 5, "$name - $lo") if($lo); return($sock); } ############################################################################### # Socket schließen ############################################################################### sub Log2Syslog_closesock($$) { my ($hash,$sock) = @_; shutdown($sock, 1); if(AttrVal($hash->{NAME}, "TLS", 0)) { $sock->close(SSL_no_shutdown => 1); } else { $sock->close(); } return; } ############################################################################### # set PRIVAL (severity & facility) ############################################################################### sub Log2Syslog_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 Log2Syslog_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; return undef,undef if(!$otp); my $pid = $hash->{SEQNO}; # PayloadID zur Nachverfolgung der Eventabfolge $hash->{SEQNO}++; my ($year,$month,$day) = split("-",$date); if ($lf eq "BSD") { # BSD Protokollformat https://tools.ietf.org/html/rfc3164 $time = (split(/\./,$time))[0] if($time =~ m/\./); # 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 $ident: : $otp"; use warnings; $data = substr($data,0, ($RFC3164len{DL}-1)); # Länge Total begrenzen } if ($lf eq "IETF") { # IETF Protokollformat https://tools.ietf.org/html/rfc5424 my $IETFver = 1; # Version von syslog Protokoll Spec RFC5424 my $mid = "FHEM"; # message ID, identify protocol of message, e.g. for firewall filter my $tim = $date."T".$time; my $sdfield = "[version\@Log2Syslog version=\"$hash->{VERSION}\"]"; $otp = Encode::encode_utf8($otp); # Längenbegrenzung nach RFC5424 $ident = substr($ident,0, ($RFC5425len{ID}-1)); $pid = substr($pid,0, ($RFC5425len{PID}-1)); $mid = substr($mid,0, ($RFC5425len{MID}-1)); $myhost = substr($myhost,0, ($RFC5425len{HST}-1)); no warnings 'uninitialized'; if ($IETFver == 1) { $data = "<$prival>$IETFver $tim $myhost $ident $pid $mid $sdfield :$otp"; } use warnings; } if($data =~ /\s$/){$data =~ s/\s$//;} my $dl = length($data); # Länge muss ! für TLS stimmen, sonst keine Ausgabe ! # wenn Transport Layer Security (TLS) -> Transport Mapping for Syslog https://tools.ietf.org/pdf/rfc5425.pdf if(AttrVal($name, "TLS", 0)) { $data = "$dl $data"; $data = substr($data,0, ($RFC5425len{DL}-1)); # Länge Total begrenzen Log2Syslog_Log3slog($name, 4, "$name - SSL-Payload created with length: ".(($dl>($RFC5425len{DL}-1))?($RFC5425len{DL}-1):$dl) ); } my $ldat = ($dl>130)?(substr($data,0, 130)." ..."):$data; Log2Syslog_Log3slog($name, 4, "$name - Payload sequence $pid created:\n$ldat"); return($data,$pid); } ############################################################################### # eigene Log3-Ableitung - Schleife vermeiden ############################################################################### sub Log2Syslog_Log3slog($$$) { 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; } ############################################################################### # Bestimmung Übertragungsrate ############################################################################### sub Log2Syslog_trate($) { my ($hash) = @_; my $name = $hash->{NAME}; my $rerun = AttrVal($name, "rateCalcRerun", 60); if ($hash->{HELPER}{LTIME}+60 <= time()) { my $div = (time()-$hash->{HELPER}{LTIME})/60; my $spm = sprintf "%.0f", ($hash->{SEQNO} - $hash->{HELPER}{OLDSEQNO})/$div; $hash->{HELPER}{OLDSEQNO} = $hash->{SEQNO}; $hash->{HELPER}{LTIME} = time(); my $ospm = ReadingsVal($name, "Transfered_logs_per_minute", 0); if($spm != $ospm) { readingsSingleUpdate($hash, "Transfered_logs_per_minute", $spm, 1); } else { readingsSingleUpdate($hash, "Transfered_logs_per_minute", $spm, 0); } } RemoveInternalTimer($hash, "Log2Syslog_trate"); InternalTimer(gettimeofday()+$rerun, "Log2Syslog_trate", $hash, 0); return; } ############################################################################### # Peer IP-Adresse und Host ermitteln (Sender der Message) ############################################################################### sub Log2Syslog_evalPeer($) { my ($hash) = @_; my $name = $hash->{NAME}; my $socket = $hash->{SERVERSOCKET}; my($pport, $pipaddr) = sockaddr_in($socket->peername); my $phost = gethostbyaddr($pipaddr, AF_INET); my $paddr = inet_ntoa($pipaddr); Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name - message peerhost: $phost, $paddr"); return ($phost,$paddr); } 1; =pod =item helper =item summary forwards FHEM system logs and/or events to a syslog server or act as an syslog server itself =item summary_DE sendet FHEM Systemlogs und/oder Events an einen Syslog-Server bzw. agiert selbst als Syslog-Server =begin html Log2Syslog Send FHEM system log entries and/or FHEM events to an external syslog server. The syslog protocol has been implemented according the specifications of RFC5424 (IETF), RFC3164 (BSD) and the TLS transport protocol according to RFC5425. 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 Severity 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 The structure of the payload differs dependent of the used logFormat. logFormat IETF: "<PRIVAL>VERSION TIME MYHOST IDENT PID MID [SD-FIELD] :MESSAGE" PRIVAL priority value (coded from "facility" and "severity") TIME timestamp according to RFC5424 MYHOST Internal MYHOST IDENT ident-Tag from DEF if set, or else the own device name. The statement will be completed by "_fhem" (FHEM-Log) respectively "_event" (Event-Log). PID sequential Payload-ID MID fix value "FHEM" MESSAGE the dataset to transfer logFormat BSD: "<PRIVAL>MONAT TAG TIME MYHOST IDENT: : MESSAGE" PRIVAL priority value (coded from "facility" and "severity") MONAT month according to RFC3164 TAG day of month according to RFC3164 TIME timestamp according to RFC3164 MYHOST Internal MYHOST IDENT ident-Tag from DEF if set, or else the own device name. The statement will be completed by "_fhem" (FHEM-Log) respectively "_event" (Event-Log). MESSAGE the dataset to transfer Get certinfo Show informations about the server certificate if a TLS-session was created (Reading "SSL_Version" isn't "n.a."). Attributes addTimestamp [0|1] If set to 1, fhem timestamps will be logged too. Default behavior is to not log these timestamps, because syslog uses own timestamps. Maybe useful if mseclog is activated in fhem. Example output (raw) of a Splunk syslog server: Aug 18 21:26:55 fhemtest.myds.me 1 2017-08-18T21:26:55 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:55 USV state: OL Aug 18 21:26:54 fhemtest.myds.me 1 2017-08-18T21:26:54 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:54 Bezug state: done Aug 18 21:26:54 fhemtest.myds.me 1 2017-08-18T21:26:54 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:54 recalc_Bezug state: Next: 21:31:59 addStateEvent [0|1] If set to 1, events will be completed with "state" if a state-event appears. Default behavior is without getting "state". disable [0|1] disables the device. logFormat [BSD|IETF] Set the syslog protocol format. Default value is "IETF" if not specified. protocol [TCP|UDP] Sets the socket protocol which should be used. You can choose UDP or TCP. Default value is "UDP" if not specified. port The used port. For a Sender the default-port is 514. A Collector (Syslog-Server) uses the port 1514 per default. rateCalcRerun Rerun cycle for calculation of log transfer rate (Reading "Transfered_logs_per_minute") in seconds. Default is 60 seconds. ssldebug Debugging level of SSL messages. 0 - No debugging (default). 1 - Print out errors from IO::Socket::SSL and ciphers from Net::SSLeay. 2 - Print also information about call flow from IO::Socket::SSL and progress information from Net::SSLeay. 3 - Print also some data dumps from IO::Socket::SSL and from Net::SSLeay. TLS A secured connection to Syslog-server is used. The protocol will be switched to TCP automatically. timeout Timeout for connection to the destination syslog server (TCP). Only valid in Sender-mode. Default: 0.5 seconds. verbose To avoid loops, the output of verbose level of the Log2Syslog-Devices will only be reported into the local FHEM Logfile and no forwarded. Readings SSL_Algorithm used SSL algorithm if SSL is enabled and active SSL_Version the used TLS-version if encryption is enabled and is active Transfered_logs_per_minute the average number of forwarded logs/events per minute =end html =begin html_DE Log2Syslog Sendet das Modul FHEM Systemlog Einträge und/oder Events an einen externen Syslog-Server weiter oder agiert als Syslog-Server um Syslog-Meldungen anderer Geräte zu empfangen. Die Implementierung des Syslog-Protokolls erfolgte entsprechend den Vorgaben von RFC5424 (IETF), RFC3164 (BSD) sowie dem TLS Transport Protokoll nach RFC5425. 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 und Verwendung Je nach Verwendungszweck kann ein Syslog-Server (MODEL Collector) oder ein Syslog-Client (MODEL Sender) definiert werden. Der Collector empfängt Meldungen im Syslog-Format anderer Geräte und generiert daraus Events zur Weiterverarbeitung in FHEM. Das Sender-Device leitet FHEM Systemlog Einträge und/oder Events an einen externen Syslog-Server weiter. Der Collector (Syslog-Server) Definition eines Collectors define <name> Log2Syslog Die Definition benötigt keine weiteren Parameter. In der Grundeinstellung wird der Syslog-Server mit dem Port=1514/UDP und dem Parsingprofil "IETF" initialisiert. Mit dem Attribut "parseProfile" können alternativ andere Formate (z.B. BSD) ausgewählt werden. Der Syslog-Server ist sofort betriebsbereit, parst die Syslog-Daten entsprechend der Richlinien nach RFC5424 und generiert aus den eingehenden Syslog-Meldungen FHEM-Events. Beispiel für einen Collector: define SyslogServer Log2Syslog Im Eventmonitor können die generierten Events kontrolliert werden. Beispiel von generierten Events mit Attribut parseProfile=IETF: 2018-07-31 17:07:24.382 Log2Syslog SyslogServer HOST: fhem.myds.me || FAC: syslog || SEV: Notice || ID: Prod_event || CONT: USV state: OL 2018-07-31 17:07:24.858 Log2Syslog SyslogServer HOST: fhem.myds.me || FAC: syslog || SEV: Notice || ID: Prod_event || CONT: HMLAN2 loadLvl: low Zwischen den einzelnen Feldern wird der Trenner "||" verwendet. Die Bedeutung der Felder in diesem Beispiel sind: HOST der Sender des Datensatzes FAC Facility (Kategorie) nach RFC5424 SEV Severity (Schweregrad) nach RFC5424 ID Ident-Tag CONT der Nachrichtenteil der empfangenen Meldung Der Timestamp der generierten Events wird aus den Syslogmeldungen geparst. Sollte diese Information nicht mitgeliefert werden, wird der aktuelle Timestamp des Systems verwendet. Der Name des Readings im generierten Event entspricht dem aus der Syslogmeldung geparsten Hostnamen. Ist der Hostname in der Meldung nicht enthalten, wird die IP-Adresse des Senders aus dem Netzwerk Interface abgerufen und der Hostname ermittelt sofern möglich. In diesem Fall wird der ermittelte Hostname bzw. die IP-Adresse als Reading im Event genutzt. Nach der Definition des Collectors werden die Syslog-Meldungen im IETF-Format gemäß RFC5424 erwartet. Werden die Daten nicht in diesem Format geliefert, erscheint im Reading "state" die Meldung "parse error - see logfile" und die empfangenen Syslog-Daten werden im Logfile im raw-Format ausgegeben. In diesem Fall kann mit dem Attribut "parseProfile" ein anderes vordefiniertes Parsing-Profil eingestellt bzw. ein eigenes Profil definiert werden. Zur Definition einer eigenen Parsingfunktion wird "parseProfile = ParseFn" eingestellt und im Attribut "parseFn" eine spezifische Parsingfunktion hinterlegt. Die im Event verwendeten Felder und deren Reihenfolge können aus einem Wertevorrat mit dem Attribut "outputFields" bestimmt werden. Je nach verwendeten Parsingprofil können alle oder nur eine Untermenge der verfügbaren Felder verwendet werden. Näheres dazu in der Beschreibung des Attributes "parseProfile". Der Sender (Syslog-Client) Definition eines Senders 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>] optionaler 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 übersteuert 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 für einen Sender: 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 [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 4: CamTER - CAMID already set - ignore get camid Der Aufbau der Payload unterscheidet sich je nach verwendeten logFormat. logFormat IETF: "<PRIVAL>VERSION TIME MYHOST IDENT PID MID [SD-FIELD] :MESSAGE" PRIVAL Priority Wert (kodiert aus "facility" und "severity") TIME Timestamp nach RFC5424 MYHOST Internal MYHOST IDENT Ident-Tag aus DEF wenn angegeben, sonst der eigene Devicename. Die Angabe wird mit "_fhem" (FHEM-Log) bzw. "_event" (Event-Log) ergänzt. PID fortlaufende Payload-ID MID fester Wert "FHEM" [SD-FIELD] Structured Data Feld. Enthält Informationen zur verwendeten Modulversion (die Klammern "[]" sind Bestandteil des Feldes) MESSAGE der zu übertragende Datensatz logFormat BSD: "<PRIVAL>MONAT TAG TIME MYHOST IDENT: : MESSAGE" PRIVAL Priority Wert (kodiert aus "facility" und "severity") MONAT Monatsangabe nach RFC3164 TAG Tag des Monats nach RFC3164 TIME Zeitangabe nach RFC3164 MYHOST Internal MYHOST IDENT Ident-Tag aus DEF wenn angegeben, sonst der eigene Devicename. Die Angabe wird mit "_fhem" (FHEM-Log) bzw. "_event" (Event-Log) ergänzt. MESSAGE der zu übertragende Datensatz Get certinfo Zeigt Informationen zum Serverzertifikat wenn eine TLS-Session aufgebaut wurde (Reading "SSL_Version" ist nicht "n.a."). Attribute addTimestamp Das Attribut ist nur für "Sender" verwendbar. Wenn gesetzt, werden FHEM Timestamps im Content-Feld der Syslog-Meldung mit übertragen. Per default werden die Timestamps nicht im Content-Feld hinzugefügt, da innerhalb der Syslog-Meldungen im IETF- bzw. BSD-Format bereits Zeitstempel gemäß RFC-Vorgabe erstellt werden. Die Einstellung kann hilfeich sein wenn mseclog in FHEM aktiviert ist. Beispielausgabe (raw) eines Splunk Syslog Servers: Aug 18 21:26:55 fhemtest.myds.me 1 2017-08-18T21:26:55 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:55 USV state: OL Aug 18 21:26:54 fhemtest.myds.me 1 2017-08-18T21:26:54 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:54 Bezug state: done Aug 18 21:26:54 fhemtest.myds.me 1 2017-08-18T21:26:54 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:54 recalc_Bezug state: Next: 21:31:59 addStateEvent Das Attribut ist nur für "Sender" verwendbar. Wenn gesetzt, werden state-events mit dem Reading "state" ergänzt. Die Standardeinstellung ist ohne state-Ergänzung. disable Das Device wird aktiviert | aktiviert. logFormat [ BSD | IETF ] Das Attribut ist nur für "Sender" verwendbar. Es stellt das Protokollformat ein. (default: "IETF") outputFields Über eine sortierbare Liste können die gewünschten Felder zur Eventgenerierung ausgewählt werden. Die abhängig vom Attribut "parseProfil" sinnvoll verwendbaren Felder und deren Bedeutung ist der Beschreibung des Attributs "parseProfil" zu entnehmen. Ist "outputFields" nicht gesetzt, wird ein vordefinierter Satz Felder zur Eventgenerierung verwendet. parseFn {<Parsefunktion>} Das Attribut ist nur für Device-MODEL "Collector" verwendbar. Es wird die eingegebene Perl-Funktion auf die empfangene Syslog-Meldung angewendet. Der Funktion werden folgende Variablen übergeben die zur Verarbeitung und zur Werterückgabe genutzt werden können. Leer übergebene Variablen sind als "" gekennzeichnet. Das erwartete Rückgabeformat einer Variable wird in "()" angegeben sofern sie Restriktionen unterliegt. Ansonsten ist die Variable frei verfügbar. $PRIVAL "" (0 ... 191) $FAC "" (0 ... 23) $SEV "" (0 ... 7) $TS Zeitstempel (YYYY-MM-DD hh:mm:ss) $HOST "" $DATE "" (YYYY-MM-DD) $TIME "" (hh:mm:ss) $ID "" $PID "" $MID "" $SDFIELD "" $CONT "" $DATA übergebene Rohdaten der Syslog-Mitteilung (keine Rückgabeauswertung!) Die Variablennamen korrespondieren mit den Feldnamen und deren ursprünglicher Bedeutung angegeben im Attribut "parseProfile" (Erläuterung der Felddaten). parseProfile [ BSD | IETF | ParseFn | raw ] Auswahl eines Parsing-Profiles. Das Attribut ist nur für Device-MODEL "Collector" verwendbar. BSD Parsing der Meldungen im BSD-Format nach RFC3164 IETF Parsing der Meldungen im IETF-Format nach RFC5424 (default) ParseFn Verwendung einer eigenen spezifischen Parsingfunktion im Attribut "parseFn". raw kein Parsing, die Meldungen werden wie empfangen in ein Event umgesetzt Die geparsten Informationen werden in Feldern zur Verfügung gestellt. Die im Event erscheinenden Felder und deren Reihenfolge können mit dem Attribut "outputFields" bestimmt werden. Abhängig vom verwendeten "parseProfile" werden die folgenden Felder mit Werten gefüllt und es ist dementsprechend auch nur sinnvoll die benannten Felder in Attribut "outputFields" zu verwenden. Im raw-Profil werden die empfangenen Daten ohne Parsing in ein Event umgewandelt. Die sinnvoll im Attribut "outputFields" verwendbaren Felder des jeweilgen Profils sind: BSD PRIVAL,FAC,SEV,TS,HOST,ID,CONT IETF PRIVAL,FAC,SEV,TS,HOST,DATE,TIME,ID,PID,MID,SDFIELD,CONT ParseFn PRIVAL,FAC,SEV,TS,HOST,DATE,TIME,ID,PID,MID,SDFIELD,CONT raw keine Auswahl sinnvoll, es wird immer die Originalmeldung in einen Event umgesetzt Erläuterung der Felddaten: PRIVAL kodierter Priority Wert (kodiert aus "facility" und "severity") FAC Kategorie (Facility) SEV Schweregrad der Meldung (Severity) TS Zeitstempel aus Datum und Zeit (YYYY-MM-DD hh:mm:ss) HOST Hostname / Ip-Adresse des Senders DATE Datum (YYYY-MM-DD) TIME Zeit (hh:mm:ss) ID Gerät oder Applikation welche die Meldung gesendet hat PID Programm-ID, oft belegt durch Prozessname bzw. Prozess-ID MID Typ der Mitteilung (beliebiger String) SDFIELD Metadaten über die empfangene Syslog-Mitteilung CONT Inhalt der Meldung DATA empfangene Rohdaten protocol [ TCP | UDP ] Setzt den Protokolltyp der verwendet werden soll. Es kann UDP oder TCP gewählt werden (MODEL Sender). Standard ist "UDP" wenn nichts spezifiziert ist. Ein Syslog-Server (MODEL Collector) verwendet UDP. port <Port> Der verwendete Port. Für einen Sender ist der default-Port 514, für einen Collector (Syslog-Server) der Port 1514. rateCalcRerun <Zeit in Sekunden> Wiederholungszyklus für die Bestimmung der Log-Transferrate (Reading "Transfered_logs_per_minute") in Sekunden (>=60). Eingegebene Werte <60 Sekunden werden automatisch auf 60 Sekunden korrigiert. Default sind 60 Sekunden. ssldebug Debugging Level von SSL Messages. 0 - Kein Debugging (default). 1 - Ausgabe Errors von from IO::Socket::SSL und ciphers von Net::SSLeay. 2 - zusätzliche Ausgabe von Informationen über den Protokollfluss von IO::Socket::SSL und Fortschritinformationen von Net::SSLeay. 3 - zusätzliche Ausgabe einiger Dumps von IO::Socket::SSL und Net::SSLeay. TLS Das Attribut ist nur für "Sender" verwendbar. Es wird eine gesicherte Verbindung zum Syslog-Server aufgebaut. Das Protokoll schaltet automatisch auf TCP um. timeout Das Attribut ist nur für "Sender" verwendbar. Timeout für die Verbindung zum Syslog-Server (TCP). Default: 0.5s. verbose Die Ausgaben der Verbose-Level von Log2Syslog-Devices werden ausschließlich im lokalen FHEM Logfile ausgegeben und nicht weitergeleitet um Schleifen zu vermeiden. Readings SSL_Algorithm der verwendete SSL Algorithmus wenn SSL eingeschaltet und aktiv ist SSL_Version die verwendete TLS-Version wenn die Verschlüsselung aktiv ist Transfered_logs_per_minute die durchschnittliche Anzahl der übertragenen/empfangenen Logs/Events pro Minute =end html_DE =cut
apt-get install libio-socket-multicast-perl (only on Debian based installations)
define <name> Log2Syslog <destination host> [ident:<ident>] [event:<regexp>] [fhem:<regexp>]
define splunklog Log2Syslog fhemtest 192.168.2.49 ident:Test event:.* fhem:.*
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
certinfo
addTimestamp [0|1]
Aug 18 21:26:55 fhemtest.myds.me 1 2017-08-18T21:26:55 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:55 USV state: OL Aug 18 21:26:54 fhemtest.myds.me 1 2017-08-18T21:26:54 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:54 Bezug state: done Aug 18 21:26:54 fhemtest.myds.me 1 2017-08-18T21:26:54 fhemtest.myds.me Test_event 13339 FHEM - : 2017-08-18 21:26:54 recalc_Bezug state: Next: 21:31:59
addStateEvent [0|1]
disable [0|1]
logFormat [BSD|IETF]
protocol [TCP|UDP]
port
rateCalcRerun
ssldebug
TLS
timeout
verbose
apt-get install libio-socket-multicast-perl (auf Debian Linux Systemen)
define <name> Log2Syslog
define SyslogServer Log2Syslog
2018-07-31 17:07:24.382 Log2Syslog SyslogServer HOST: fhem.myds.me || FAC: syslog || SEV: Notice || ID: Prod_event || CONT: USV state: OL 2018-07-31 17:07:24.858 Log2Syslog SyslogServer HOST: fhem.myds.me || FAC: syslog || SEV: Notice || ID: Prod_event || CONT: HMLAN2 loadLvl: low
define <name> Log2Syslog <Zielhost> [ident:<ident>] [event:<regexp>] [fhem:<regexp>]
Aug 18 21:06:46 fhemtest.myds.me 1 2017-08-18T21:06:46 fhemtest.myds.me Test_event 13339 FHEM [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 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 [version@Log2Syslog version="4.2.0"] : 4: CamTER - CAMID already set - ignore get camid