Server: monitor dnsmasq, create events for DHCP lease trigger, fix teelogger

This commit is contained in:
MaMe82
2018-10-26 11:41:24 +02:00
parent c05452c459
commit b15be2bf8f
6 changed files with 236 additions and 136 deletions

View File

@@ -178,7 +178,7 @@ func ConstructEventTrigger(triggerType common_web.EvtTriggerType) *pb.Event {
}
}
func ConstructEventTriggerDHCPLease(iface, mac, ip string) *pb.Event {
func ConstructEventTriggerDHCPLease(iface, mac, ip string, hostname string) *pb.Event {
return &pb.Event{
Type: common_web.EVT_TRIGGER,
Values: []*pb.EventValue{
@@ -186,6 +186,7 @@ func ConstructEventTriggerDHCPLease(iface, mac, ip string) *pb.Event {
{Val: &pb.EventValue_Tstring{Tstring: iface}},
{Val: &pb.EventValue_Tstring{Tstring: mac}},
{Val: &pb.EventValue_Tstring{Tstring: ip}},
{Val: &pb.EventValue_Tstring{Tstring: hostname}},
},
}
}

View File

@@ -1,24 +1,27 @@
package service
import (
"errors"
"fmt"
//"github.com/docker/libcontainer/netlink"
"github.com/mame82/P4wnP1_go/netlink"
"net"
"log"
"io/ioutil"
"os"
"fmt"
pb "github.com/mame82/P4wnP1_go/proto"
"errors"
"github.com/mame82/P4wnP1_go/service/util"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"sync"
)
var (
ErrUnmanagedInterface = errors.New("Not a managed network interface")
)
func NewNetworkManager() (nm *NetworkManager, err error){
func NewNetworkManager(rootService *Service) (nm *NetworkManager, err error){
nm = &NetworkManager{
rootSvc: rootService,
ManagedInterfaces: make(map[string]*NetworkInterfaceManager),
}
@@ -42,10 +45,11 @@ func NewNetworkManager() (nm *NetworkManager, err error){
type NetworkManager struct {
ManagedInterfaces map[string]*NetworkInterfaceManager
rootSvc *Service
}
func (nm *NetworkManager) AddManagedInterface(startupConfig *pb.EthernetInterfaceSettings) (err error) {
nim,err := NewNetworkInterfaceManager(startupConfig.Name, startupConfig)
nim,err := NewNetworkInterfaceManager(nm, startupConfig.Name, startupConfig)
if err != nil { return err }
nm.ManagedInterfaces[startupConfig.Name] = nim
return
@@ -78,14 +82,41 @@ type NetworkInterfaceState struct {
// ToDo: interface watcher (up/down --> auto redeploy)
type NetworkInterfaceManager struct {
nm *NetworkManager
InterfaceName string
state *NetworkInterfaceState
CmdDnsmasq *exec.Cmd
mutexDnsmasq *sync.Mutex
LoggerDnsmasq *util.TeeLogger
leaseMonitor *dnsmasqLeaseMonitor
}
func (nim *NetworkInterfaceManager) GetState() (res *NetworkInterfaceState) {
return nim.state
}
func (nim *NetworkInterfaceManager) OnHandedOutDhcpLease(lease *DhcpLease) {
fmt.Printf("Lease monitor %s LEASE: %v\n", nim.InterfaceName, lease)
// should never happen (dnsmasq output parsing error otherwise)
if nim.InterfaceName != lease.Iface {
fmt.Println("Interface of handed out DHCP lease doesn't match managed interface, ignoring ...")
return
}
//generate trigger event
nim.nm.rootSvc.SubSysEvent.Emit(ConstructEventTriggerDHCPLease(lease.Iface, lease.Mac.String(), lease.Ip.String(), lease.Host))
}
func (nim *NetworkInterfaceManager) OnReceivedDhcpRelease(release *DhcpLease) {
fmt.Printf("Lease monitor %s RELEASE: %v\n", nim.InterfaceName, release)
// should never happen (dnsmasq output parsing error otherwise)
if nim.InterfaceName != release.Iface {
fmt.Println("Interface for received DHCP release doesn't match managed interface, ignoring ...")
return
}
}
func (nim *NetworkInterfaceManager) ReDeploy() (err error) {
/*
if settings, existing := ServiceState.StoredNetworkSettings[ifName]; existing {
@@ -110,10 +141,8 @@ func (nim *NetworkInterfaceManager) DeploySettings(settings *pb.EthernetInterfac
}
//stop DHCP server / client if still running
running, _, err := IsDHCPServerRunning(settings.Name)
if (err == nil) && running {StopDHCPServer(settings.Name)}
running, _, err = IsDHCPClientRunning(settings.Name)
if (err == nil) && running {StopDHCPClient(settings.Name)}
nim.StopDHCPServer()
nim.StopDHCPClient()
switch settings.Mode {
case pb.EthernetInterfaceSettings_MANUAL:
@@ -172,7 +201,7 @@ func (nim *NetworkInterfaceManager) DeploySettings(settings *pb.EthernetInterfac
err = DHCPCreateConfigFile(settings.DhcpServerSettings, confName)
if err != nil {return err}
//stop already running DHCPServers for the interface
StopDHCPServer(ifName)
nim.StopDHCPServer()
//special case: if the interface name is USB_ETHERNET_BRIDGE_NAME, we delete the old lease file
// the flushing of still running leases is needed, as after USB reinit, RNDIS hosts aren't guaranteed to
@@ -189,7 +218,7 @@ func (nim *NetworkInterfaceManager) DeploySettings(settings *pb.EthernetInterfac
}
//start the DHCP server
err = StartDHCPServer(ifName, confName)
err = nim.StartDHCPServer(confName)
if err != nil {return err}
} else {
log.Printf("Setting Interface %s to DOWN\n", iface.Name)
@@ -206,7 +235,7 @@ func (nim *NetworkInterfaceManager) DeploySettings(settings *pb.EthernetInterfac
err = netlink.NetworkSetMulticast(iface, true)
if err != nil { return err }
StartDHCPClient(settings.Name)
nim.StartDHCPClient()
} else {
log.Printf("Setting Interface %s to DOWN\n", iface.Name)
err = netlink.NetworkLinkDown(iface)
@@ -224,40 +253,23 @@ func (nim *NetworkInterfaceManager) DeploySettings(settings *pb.EthernetInterfac
return nil
}
func NewNetworkInterfaceManager(ifaceName string, startupSettings *pb.EthernetInterfaceSettings) (nim *NetworkInterfaceManager, err error) {
func NewNetworkInterfaceManager(nm *NetworkManager, ifaceName string, startupSettings *pb.EthernetInterfaceSettings) (nim *NetworkInterfaceManager, err error) {
nim = &NetworkInterfaceManager{
nm: nm,
InterfaceName: ifaceName,
state: &NetworkInterfaceState{},
mutexDnsmasq: &sync.Mutex{},
LoggerDnsmasq: util.NewTeeLogger(false),
}
nim.leaseMonitor = NewDnsmasqLeaseMonitor(nim)
//nim.LoggerDnsmasq.SetPrefix("dnsmasq-" + ifaceName + ": ")
nim.LoggerDnsmasq.AddOutput(nim.leaseMonitor)
nim.state.CurrentSettings = startupSettings
nim.ReDeploy()
// Deploy startup configuration, to have an initial, defined state
/*
// Startup settings (always DHCP client, Interface up)
nim.state.CurrentSettings = &pb.EthernetInterfaceSettings{
Name: ifaceName,
Mode: pb.EthernetInterfaceSettings_DHCP_CLIENT,
Enabled: true,
SettingsInUse: true,
DhcpServerSettings: &pb.DHCPServerSettings{
CallbackScript: "",
DoNotBindInterface: false,
LeaseFile: nameLeaseFileDHCPSrv(ifaceName),
ListenInterface: ifaceName,
ListenPort: 0,
NotAuthoritative: false,
Options: map[uint32]string{
3: "",
6: "",
},
Ranges: []*pb.DHCPServerRange{},
StaticHosts: []*pb.DHCPServerStaticHost{},
},
}
*/
return
}

View File

@@ -3,17 +3,22 @@
package service
import (
pb "github.com/mame82/P4wnP1_go/proto"
"bufio"
"bytes"
"errors"
"fmt"
"github.com/mame82/P4wnP1_go/common_web"
pb "github.com/mame82/P4wnP1_go/proto"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"log"
"errors"
"regexp"
"strconv"
"syscall"
"strings"
"github.com/mame82/P4wnP1_go/common_web"
"syscall"
"time"
)
/*
@@ -35,14 +40,13 @@ func leaseFileDHCPSrv(s *pb.DHCPServerSettings) (lf string) {
return common_web.NameLeaseFileDHCPSrv(s.ListenInterface) //default lease file
}
func NameConfigFileDHCPSrv(nameIface string) string {
return fmt.Sprintf("/tmp/dnsmasq_%s.conf", nameIface)
}
func StartDHCPClient(nameIface string) (err error) {
func (nim *NetworkInterfaceManager) StartDHCPClient() (err error) {
nameIface := nim.InterfaceName
log.Printf("Starting DHCP client for interface '%s'...\n", nameIface)
//check if interface is valid
@@ -65,7 +69,8 @@ func StartDHCPClient(nameIface string) (err error) {
return nil
}
func IsDHCPClientRunning(nameIface string) (running bool, pid int, err error) {
func (nim *NetworkInterfaceManager) IsDHCPClientRunning() (running bool, pid int, err error) {
nameIface := nim.InterfaceName
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return false, 0, errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
@@ -102,8 +107,9 @@ func IsDHCPClientRunning(nameIface string) (running bool, pid int, err error) {
}
func StopDHCPClient(nameIface string) (err error) {
log.Printf("Stoping DHCP client for interface '%s'...\n", nameIface)
func (nim *NetworkInterfaceManager) StopDHCPClient() (err error) {
nameIface := nim.InterfaceName
log.Printf("Stopping DHCP client for interface '%s'...\n", nameIface)
//check if interface is valid
if_exists,_ := CheckInterfaceExistence(nameIface)
@@ -125,10 +131,20 @@ func StopDHCPClient(nameIface string) (err error) {
return nil
}
func (nim *NetworkInterfaceManager) StartDHCPServer(configPath string) (err error) {
nim.mutexDnsmasq.Lock()
defer nim.mutexDnsmasq.Unlock()
//stop dnsmasq if already running
if nim.CmdDnsmasq != nil {
// avoid deadlock
nim.mutexDnsmasq.Unlock()
nim.StopDHCPServer()
nim.mutexDnsmasq.Lock()
}
func StartDHCPServer(nameIface string, configPath string) (err error) {
log.Printf("Starting DHCP server for interface '%s' with config '%s'...\n", nameIface, configPath)
nameIface := nim.InterfaceName
log.Printf("Starting dnsmasq for interface '%s' with config '%s'...\n", nameIface, configPath)
//check if interface is valid
if_exists,_ := CheckInterfaceExistence(nameIface)
@@ -136,91 +152,46 @@ func StartDHCPServer(nameIface string, configPath string) (err error) {
return errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
}
//Check if there's already a DHCP server running for the given interface
running, _, err := IsDHCPServerRunning(nameIface)
if err != nil { return errors.New(fmt.Sprintf("Error fetching state of DHCP server: %v\n", err)) }
if running {return errors.New(fmt.Sprintf("Error starting DHCP server for interface '%s', there is already a DHCP server running\n", nameIface))}
//We use the run command and allow dnsmasq to daemonize
proc := exec.Command("/usr/sbin/dnsmasq", "-x", pidFileDHCPSrv(nameIface), "-C", configPath)
//dnsmasq_out, err := proc.CombinedOutput()
err = proc.Run()
if err != nil { return err}
//fmt.Printf("Dnsmasq out %s\n", dnsmasq_out)
nim.CmdDnsmasq = exec.Command("/usr/sbin/dnsmasq", "--log-facility=-", "-k", "-x", pidFileDHCPSrv(nameIface), "-C", configPath)
nim.CmdDnsmasq.Stdout = nim.LoggerDnsmasq.LogWriter
nim.CmdDnsmasq.Stderr = nim.LoggerDnsmasq.LogWriter
err = nim.CmdDnsmasq.Start()
if err != nil {
nim.CmdDnsmasq.Wait()
return errors.New(fmt.Sprintf("Error starting dnsmasq '%v'", err))
}
log.Printf("... DHCP server for interface '%s' started\n", nameIface)
return nil
}
func (nim *NetworkInterfaceManager) StopDHCPServer() (err error) {
eSuccess := fmt.Sprintf("... dnsmasq for interface '%s' stopped", nim.InterfaceName)
eCantStop := fmt.Sprintf("... couldn't terminate dnsmasq for interface '%s'", nim.InterfaceName)
func IsDHCPServerRunning(nameIface string) (running bool, pid int, err error) {
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return false, 0, errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
log.Println("... killing dnsmasq")
nim.mutexDnsmasq.Lock()
defer nim.mutexDnsmasq.Unlock()
if nim.CmdDnsmasq == nil {
log.Printf("... dnsmasq for interface '%s' isn't running, no need to stop it\n", nim.InterfaceName)
return nil
}
pid_file := pidFileDHCPSrv(nameIface)
err = ProcSoftKill(nim.CmdDnsmasq, time.Second)
if err != nil { return errors.New(eCantStop) }
//Check if the pidFile exists
if _, err := os.Stat(pid_file); os.IsNotExist(err) {
return false, 0,nil //file doesn't exist, so we assume dnsmasq isn't running
}
//File exists, read the PID
content, err := ioutil.ReadFile(pid_file)
if err != nil { return false, 0, err}
pid, err = strconv.Atoi(strings.TrimSuffix(string(content), "\n"))
if err != nil { return false, 0, errors.New(fmt.Sprintf("Error parsing PID file %s: %v", pid_file, err))}
//With PID given, check if the process is indeed running (pid_file could stay, even if the dnsmasq process has died already)
err_kill := syscall.Kill(pid, 0) //sig 0: doesn't send a signal, but error checking is still performed
switch err_kill{
case nil:
//ToDo: Check if the running process image is indeed dnsmasq
return true, pid, nil //Process is running
case syscall.ESRCH:
//Process doesn't exist
return false, pid, nil
case syscall.EPERM:
//process exists, but we have no access permission
return true, pid, err_kill
default:
return false, pid, err_kill
}
}
func StopDHCPServer(nameIface string) (err error) {
//don't check if interface is valid, to allow closing of orphaned DHCP procs (interface went down while running)
log.Printf("Stopping DHCP server for interface '%s' ...\n", nameIface)
running,pid,err := IsDHCPServerRunning(nameIface)
if err != nil { return }
if running {
//send SIGTERM
err = syscall.Kill(pid, syscall.SIGTERM)
if err != nil { return }
} else {
log.Printf("... DHCP server for interface '%s' wasn't started\n", nameIface)
}
running,pid,err = IsDHCPServerRunning(nameIface)
if err != nil { return }
if (running) {
log.Printf("... couldn't terminate DHCP server for interface '%s'\n", nameIface)
} else {
log.Printf("... DHCP server for interface '%s' stopped\n", nameIface)
}
//Delete PID file
os.Remove(pidFileDHCPSrv(nameIface))
//Deleting leaseFile
os.Remove(common_web.NameLeaseFileDHCPSrv(nameIface))
nim.CmdDnsmasq = nil
log.Println(eSuccess)
return nil
}
func DHCPCreateConfigFile(s *pb.DHCPServerSettings, filename string) (err error) {
file_content, err := DHCPCreateConfigFileString(s)
if err != nil {return}
@@ -245,12 +216,19 @@ func DHCPCreateConfigFileString(s *pb.DHCPServerSettings) (config string, err er
//Iterate over Ranges
for _, pRange := range s.Ranges {
//ToDo: regex check for leaseTime
/*
If the lease time is
given, then leases will be given for that length of time. The
lease time is in seconds, or minutes (eg 45m) or hours (eg 1h)
or "infinite". If not given, the default lease time is one
hour. The minimum lease time is two minutes
*/
//ToDo: check rangeLower + rangeUpper to be valid IP addresses
if len(pRange.LeaseTime) > 0 {
config += fmt.Sprintf("dhcp-range=%s,%s,%s\n", pRange.RangeLower, pRange.RangeUpper, pRange.LeaseTime)
} else {
//default to 5 minute lease
config += fmt.Sprintf("dhcp-range=%s,%s,5m\n", pRange.RangeLower, pRange.RangeUpper)
config += fmt.Sprintf("dhcp-range=%s,%s\n", pRange.RangeLower, pRange.RangeUpper)
}
}
@@ -282,3 +260,86 @@ func DHCPCreateConfigFileString(s *pb.DHCPServerSettings) (config string, err er
return
}
// Lease/Release tracker
var reLease = regexp.MustCompile(".*DHCPACK\\((.*)\\) ([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}) ([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}) (.*)")
var reRelease = regexp.MustCompile(".*DHCPRELEASE\\((.*)\\) ([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}) ([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})")
type DhcpLease struct {
Release bool
Iface string
Ip net.IP
Mac net.HardwareAddr
Host string //only used for lease, not release
}
type dnsmasqLeaseMonitor struct {
nim *NetworkInterfaceManager
}
func (m *dnsmasqLeaseMonitor) Write(p []byte) (n int, err error) {
/*
dnsmasq-wlan0: 16:53:49 dnsmasq-dhcp[1855]: 1450307105 DHCPACK(wlan0) 172.24.0.18 34:e6:xx:xx:xx:xx who-knows
dnsmasq-wlan0: 16:53:58 dnsmasq-dhcp[1855]: 4200697351 DHCPRELEASE(wlan0) 172.24.0.18 34:e6:xx:xx:xx:xx
*/
lineScanner := bufio.NewScanner(bytes.NewReader(p))
lineScanner.Split(bufio.ScanLines)
for lineScanner.Scan() {
line := string(lineScanner.Bytes())
switch {
case strings.Contains(line, "DHCPACK"):
//fmt.Printf("Lease monitor %s LEASE: %s\n", m.nim.InterfaceName, line)
leaseMatches := reLease.FindStringSubmatch(line)
if len(leaseMatches) > 3 {
lease := &DhcpLease{}
lease.Iface = leaseMatches[1]
lease.Ip = net.ParseIP(leaseMatches[2])
mac,errP := net.ParseMAC(leaseMatches[3])
if errP != nil { continue } //ignore if mac address couldn't be parsed
lease.Mac = mac
if len(leaseMatches) > 4 {
//assume 4th match is hostname
lease.Host = leaseMatches[4]
}
m.nim.OnHandedOutDhcpLease(lease)
}
/*
for i,m := range leaseMatches {
fmt.Printf("\tRegex lease %d: %s\n", i, m)
}
*/
case strings.Contains(line, "DHCPRELEASE"):
//fmt.Printf("Lease monitor %s RELEASE: %s\n", m.nim.InterfaceName, line)
leaseMatches := reRelease.FindStringSubmatch(line)
if len(leaseMatches) > 3 {
release := &DhcpLease{}
release.Iface = leaseMatches[1]
release.Ip = net.ParseIP(leaseMatches[2])
mac,errP := net.ParseMAC(leaseMatches[3])
if errP != nil { continue } //ignore if mac address couldn't be parsed
release.Mac = mac
release.Release = true
m.nim.OnReceivedDhcpRelease(release)
}
}
}
return len(p),nil
}
func NewDnsmasqLeaseMonitor(nim *NetworkInterfaceManager) *dnsmasqLeaseMonitor {
return &dnsmasqLeaseMonitor{
nim: nim,
}
}

View File

@@ -135,7 +135,7 @@ func NewService() (svc *Service, err error) {
svc.SubSysEvent = NewEventManager(20)
svc.SubSysLed = NewLedService()
svc.SubSysNetwork, err = NewNetworkManager()
svc.SubSysNetwork, err = NewNetworkManager(svc) //Depends on EvenSubSys
if err != nil {
return nil, err
}

View File

@@ -400,7 +400,7 @@ func (tam *TriggerActionManager) executeActionStartHidScript(evt *pb.Event, ta *
case triggerTypeGpioIn:
gpioPin := ta.Trigger.(*pb.TriggerAction_GpioIn).GpioIn.GpioNum
gpioPinName := pb.GPIONum_name[int32(gpioPin)]
preScript += fmt.Sprintf("var GPIO_PIN=%s;\n", gpioPinName)
preScript += fmt.Sprintf("var GPIO_PIN='%s';\n", gpioPinName)
case triggerTypeGroupReceiveSequence:
groupName := ta.Trigger.(*pb.TriggerAction_GroupReceiveSequence).GroupReceiveSequence.GroupName
values := ta.Trigger.(*pb.TriggerAction_GroupReceiveSequence).GroupReceiveSequence.Values
@@ -425,12 +425,14 @@ func (tam *TriggerActionManager) executeActionStartHidScript(evt *pb.Event, ta *
iface := evt.Values[1].GetTstring()
mac := evt.Values[2].GetTstring()
ip := evt.Values[3].GetTstring()
preScript += fmt.Sprintf("var DHCP_LEASE_IFACE=%s;\n", iface)
preScript += fmt.Sprintf("var DHCP_LEASE_MAC=%s;\n", mac)
preScript += fmt.Sprintf("var DHCP_LEASE_IP=%s;\n", ip)
host := evt.Values[4].GetTstring()
preScript += fmt.Sprintf("var DHCP_LEASE_IFACE='%s';\n", iface)
preScript += fmt.Sprintf("var DHCP_LEASE_MAC='%s';\n", mac)
preScript += fmt.Sprintf("var DHCP_LEASE_IP='%s';\n", ip)
preScript += fmt.Sprintf("var DHCP_LEASE_HOST='%s';\n", host)
case triggerTypeSshLogin:
loginUser := evt.Values[1].GetTstring()
preScript += fmt.Sprintf("var SSH_LOGIN_USER=%s;\n", loginUser)
preScript += fmt.Sprintf("var SSH_LOGIN_USER='%s';\n", loginUser)
}
@@ -490,10 +492,12 @@ func (tam *TriggerActionManager) executeActionBashScript(evt *pb.Event, ta *pb.T
iface := evt.Values[1].GetTstring()
mac := evt.Values[2].GetTstring()
ip := evt.Values[3].GetTstring()
host := evt.Values[4].GetTstring()
env = append(env,
fmt.Sprintf("DHCP_LEASE_IFACE=%s", iface),
fmt.Sprintf("DHCP_LEASE_MAC=%s", mac),
fmt.Sprintf("DHCP_LEASE_IP=%s", ip),
fmt.Sprintf("DHCP_LEASE_HOST=\"%s\"", host),
)
case triggerTypeSshLogin:
loginUser := evt.Values[1].GetTstring()
@@ -530,7 +534,8 @@ func (tam *TriggerActionManager) executeActionLog(evt *pb.Event, ta *pb.TriggerA
iface := evt.Values[1].GetTstring()
mac := evt.Values[2].GetTstring()
ip := evt.Values[3].GetTstring()
logMessage += fmt.Sprintf(" (DHCP_LEASE_IFACE=%s, DHCP_LEASE_MAC=%s, DHCP_LEASE_IP=%s)", iface, mac, ip)
host := evt.Values[4].GetTstring()
logMessage += fmt.Sprintf(" (DHCP_LEASE_IFACE=%s, DHCP_LEASE_MAC=%s, DHCP_LEASE_IP=%s, DHCP_LEASE_HOST='%s')", iface, mac, ip, host)
case triggerTypeSshLogin:
loginUser := evt.Values[1].GetTstring()
logMessage += fmt.Sprintf(" (SSH_LOGIN_USER=%s)", loginUser)

View File

@@ -1,10 +1,12 @@
package util
import (
"bufio"
"bytes"
"io"
"log"
"sync"
"os"
"sync"
)
type TeeLogger struct {
@@ -18,11 +20,29 @@ type sublogger struct {
*TeeLogger
}
/*
// struct to present an additional io.Writer, wrapping TeeLogger to ise its Print() method
func (sl sublogger) Write(p []byte) (n int, err error) {
sl.TeeLogger.Print(string(p))
return len(p), nil
}
*/
// struct to present an additional io.Writer, wrapping TeeLogger to ise its Print() method
func (sl sublogger) Write(p []byte) (n int, err error) {
//fmt.Printf("%s: %s", lw.Prefix, string(p))
lineScanner := bufio.NewScanner(bytes.NewReader(p))
lineScanner.Split(bufio.ScanLines)
for lineScanner.Scan() {
sl.TeeLogger.Print(string(lineScanner.Bytes()))
//fmt.Printf("%s: %s\n", lw.Prefix, string(lineScanner.Bytes()))
}
return len(p),nil
}
func NewTeeLogger(addStdout bool) (res *TeeLogger) {
res = &TeeLogger{
@@ -38,6 +58,7 @@ func NewTeeLogger(addStdout bool) (res *TeeLogger) {
res.LogWriter = sublogger{ TeeLogger: res }
res.SetFlags(log.Ltime)
res.SetOutput(res)
log.Println()
return res
}