implement remote syslog as a platform feature
To configure, run the following command in an interactive shell (e.g. via breakglass, or when mounting the permanent partition of the SD card on the host): mkdir /perm/remote_syslog echo 10.0.0.76:514 > /perm/remote_syslog/target I recommend using a (static) IP address for increased reliability, so that remote syslog works even when DNS does not. fixes #50
This commit is contained in:
parent
25d06ba514
commit
6beb2e16aa
@ -116,6 +116,8 @@ func Boot(userBuildTimestamp string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initRemoteSyslog()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
108
supervise.go
108
supervise.go
@ -3,19 +3,84 @@ package gokrazy
|
|||||||
import (
|
import (
|
||||||
"container/ring"
|
"container/ring"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"log/syslog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// remoteSyslogError throttles printing error messages about remote
|
||||||
|
// syslog. Since a remote syslog writer is created for stdout and stderr of each
|
||||||
|
// supervised process, error messages during early boot spam the serial console
|
||||||
|
// without limiting. When the value is 0, a log message can be printed. A
|
||||||
|
// background goroutine resets the value to 0 once a second.
|
||||||
|
var remoteSyslogError uint32
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go func() {
|
||||||
|
for range time.Tick(1 * time.Second) {
|
||||||
|
atomic.StoreUint32(&remoteSyslogError, 0)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteSyslogWriter struct {
|
||||||
|
raddr, tag string
|
||||||
|
|
||||||
|
lines *lineRingBuffer
|
||||||
|
|
||||||
|
syslogMu sync.Mutex
|
||||||
|
syslog io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *remoteSyslogWriter) establish() {
|
||||||
|
for {
|
||||||
|
sl, err := syslog.Dial("udp", w.raddr, syslog.LOG_INFO, w.tag)
|
||||||
|
if err != nil {
|
||||||
|
if atomic.SwapUint32(&remoteSyslogError, 1) == 0 {
|
||||||
|
log.Printf("remote syslog: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.syslogMu.Lock()
|
||||||
|
defer w.syslogMu.Unlock()
|
||||||
|
// replay buffer in case any messages were sent before the connection
|
||||||
|
// could be established (before the network is ready)
|
||||||
|
for _, line := range w.lines.Lines() {
|
||||||
|
sl.Write([]byte(line + "\n"))
|
||||||
|
}
|
||||||
|
// send all future writes to syslog
|
||||||
|
w.syslog = sl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *remoteSyslogWriter) Lines() []string {
|
||||||
|
return w.lines.Lines()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *remoteSyslogWriter) Write(b []byte) (int, error) {
|
||||||
|
w.lines.Write(b)
|
||||||
|
w.syslogMu.Lock()
|
||||||
|
defer w.syslogMu.Unlock()
|
||||||
|
if w.syslog != nil {
|
||||||
|
w.syslog.Write(b)
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
type lineRingBuffer struct {
|
type lineRingBuffer struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
remainder string
|
remainder string
|
||||||
@ -58,12 +123,17 @@ func (lrb *lineRingBuffer) Lines() []string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type lineswriter interface {
|
||||||
|
io.Writer
|
||||||
|
Lines() []string
|
||||||
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
stopped bool
|
stopped bool
|
||||||
stoppedMu sync.RWMutex
|
stoppedMu sync.RWMutex
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
Stdout *lineRingBuffer
|
Stdout lineswriter
|
||||||
Stderr *lineRingBuffer
|
Stderr lineswriter
|
||||||
started time.Time
|
started time.Time
|
||||||
startedMu sync.RWMutex
|
startedMu sync.RWMutex
|
||||||
attempt uint64
|
attempt uint64
|
||||||
@ -143,6 +213,35 @@ func (s *service) RSS() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var syslogRaddr string
|
||||||
|
|
||||||
|
func initRemoteSyslog() {
|
||||||
|
b, err := ioutil.ReadFile("/perm/remote_syslog/target")
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raddr := strings.TrimSpace(string(b))
|
||||||
|
log.Printf("sending process stdout/stderr to remote syslog %s", raddr)
|
||||||
|
syslogRaddr = raddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogWriter(tag string) lineswriter {
|
||||||
|
lb := newLineRingBuffer(100)
|
||||||
|
if syslogRaddr == "" {
|
||||||
|
return lb
|
||||||
|
}
|
||||||
|
wr := &remoteSyslogWriter{
|
||||||
|
raddr: syslogRaddr,
|
||||||
|
tag: tag,
|
||||||
|
lines: lb,
|
||||||
|
}
|
||||||
|
go wr.establish()
|
||||||
|
return wr
|
||||||
|
}
|
||||||
|
|
||||||
func isDontSupervise(err error) bool {
|
func isDontSupervise(err error) bool {
|
||||||
ee, ok := err.(*exec.ExitError)
|
ee, ok := err.(*exec.ExitError)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -158,8 +257,9 @@ func isDontSupervise(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func supervise(s *service) {
|
func supervise(s *service) {
|
||||||
s.Stdout = newLineRingBuffer(100)
|
tag := filepath.Base(s.cmd.Path)
|
||||||
s.Stderr = newLineRingBuffer(100)
|
s.Stdout = newLogWriter(tag)
|
||||||
|
s.Stderr = newLogWriter(tag)
|
||||||
l := log.New(s.Stderr, "", log.LstdFlags|log.Ldate|log.Ltime)
|
l := log.New(s.Stderr, "", log.LstdFlags|log.Ldate|log.Ltime)
|
||||||
attempt := 0
|
attempt := 0
|
||||||
for {
|
for {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user