1
0
Fork 0
forked from boyska/circolog

Compare commits

...

23 commits

Author SHA1 Message Date
0dc1188701
Ignore build/ 2019-05-30 10:14:06 +02:00
9c8a66100f Merge branch 'releaseutils' 2019-05-29 00:12:09 +02:00
db33746f26 Merge branch '5-socketpaths' 2019-05-29 00:12:03 +02:00
bc2a197192 release.sh makes everything for a release 2019-05-29 00:11:36 +02:00
4b4b330341 release bash script 2019-05-28 20:43:49 +02:00
86d719949e FIX #5 safer default paths for sockets: /tmp/ 2019-05-28 18:42:20 +02:00
f7caefdae1
Document socket activation. 2019-05-03 14:26:16 +02:00
b0cd779d61 Supports datagram socket activation 2019-05-03 12:05:27 +02:00
7937d8b4c3
Ignore .vim files 2019-05-03 10:48:28 +02:00
3e27cad5b1
Allow socket activation from systemd. 2019-05-03 10:47:45 +02:00
a2de164a10
Change revision of go-syslog. 2019-05-03 10:45:35 +02:00
9728b236ed
Clean sockets at exit. 2019-05-03 10:32:18 +02:00
3d463823e3 Merge remote-tracking branch 'origin/set-syslog-fmt' 2019-05-02 13:39:42 +02:00
52ff939375 Revert "Merge remote-tracking branch 'blallo/set-syslog-fmt'"
This reverts commit 07f4246c80, reversing
changes made to 6da968177b.
2019-05-02 13:39:21 +02:00
6854a2f676 FIX da813a3: now uses the intended go-syslog 2019-05-02 12:42:48 +02:00
07f4246c80 Merge remote-tracking branch 'blallo/set-syslog-fmt'
adds -log-fmt to circologd

refs #23
2019-05-02 12:32:13 +02:00
55827916bb
Add flag to set syslog format. 2019-05-02 12:28:33 +02:00
6da968177b Merge branch 'master' of git.lattuga.net:boyska/circolog 2019-05-02 11:45:35 +02:00
0e6b078ad6
Refactor log-fmt as flag.Value. Move in formatter pkg. 2019-05-02 11:20:08 +02:00
a990564f0b Merge branch '18-bug-tail-close' 2019-05-02 10:50:33 +02:00
76a6381516
Add flag to set syslog format. 2019-05-01 15:55:41 +02:00
aea09d94bf refs #18 cleans sockets 2019-04-30 17:56:13 +02:00
5717c7ca29 FIX #18 server close connections
I am not sure this really fixes the problem (but it seems to)
2019-04-30 17:45:44 +02:00
11 changed files with 244 additions and 25 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.*.vim
/build

View file

@ -54,8 +54,8 @@ func (b *BoolAuto) Set(s string) error {
} }
func main() { func main() {
addr := flag.String("addr", "localhost:9080", "http service address") queryAddr := flag.String("addr", "", "http service address")
querySocket := flag.String("socket", "", "Path to a unix domain socket for the HTTP server") querySocket := flag.String("socket", "/tmp/circologd-query.sock", "Path to a unix domain socket for the HTTP server")
backlogLimit := flag.Int("n", -1, "Limit the backlog length, defaults to no limit (-1)") backlogLimit := flag.Int("n", -1, "Limit the backlog length, defaults to no limit (-1)")
var format formatter.Format var format formatter.Format
format = formatter.FormatSyslog format = formatter.FormatSyslog
@ -75,7 +75,7 @@ func main() {
signal.Notify(interrupt, os.Interrupt) signal.Notify(interrupt, os.Interrupt)
var d *websocket.Dialer var d *websocket.Dialer
u := url.URL{Scheme: "ws", u := url.URL{Scheme: "ws",
Host: *addr, // ignored in case of -socket; see the Dialer below Host: *queryAddr, // ignored in case of -socket; see the Dialer below
Path: "/ws", Path: "/ws",
} }
q := u.Query() q := u.Query()
@ -84,7 +84,7 @@ func main() {
q.Set("l", strconv.Itoa(*backlogLimit)) q.Set("l", strconv.Itoa(*backlogLimit))
} }
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
if *querySocket != "" { if *queryAddr == "" {
d = &websocket.Dialer{ d = &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) { NetDial: func(network, addr string) (net.Conn, error) {
return net.Dial("unix", *querySocket) return net.Dial("unix", *querySocket)
@ -95,7 +95,7 @@ func main() {
log.Printf("connecting to %s", *querySocket) log.Printf("connecting to %s", *querySocket)
} else { } else {
d = websocket.DefaultDialer d = websocket.DefaultDialer
log.Printf("connecting to %s", *addr) log.Printf("connecting to %s", *queryAddr)
} }
c, _, err := d.Dial(u.String(), nil) c, _, err := d.Dial(u.String(), nil)
@ -149,7 +149,7 @@ func main() {
select { select {
case <-done: case <-done:
log.Println("Successfully close") log.Println("Successfully close")
case <-time.After(1 * time.Second): case <-time.After(5 * time.Second):
log.Println("Forced close") log.Println("Forced close")
} }
return return

View file

@ -0,0 +1,39 @@
package main
import (
"net"
"github.com/coreos/go-systemd/activation"
)
func Listeners() ([]net.Listener, error) {
files := activation.Files(false)
listeners := make([]net.Listener, len(files))
for i, f := range files {
if pc, err := net.FileListener(f); err == nil {
listeners[i] = pc
f.Close()
}
}
return listeners, nil
}
// PacketConns returns a slice containing a net.PacketConn for each matching socket type
// passed to this process.
//
// The order of the file descriptors is preserved in the returned slice.
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
func PacketConns() ([]net.PacketConn, error) {
files := activation.Files(false)
conns := make([]net.PacketConn, len(files))
for i, f := range files {
if pc, err := net.FilePacketConn(f); err == nil {
conns[i] = pc
f.Close()
}
}
return conns, nil
}

View file

@ -153,6 +153,15 @@ func getWSHandler(hub circolog.Hub) http.HandlerFunc {
hub.Unregister <- c hub.Unregister <- c
conn.Close() conn.Close()
}() }()
go func() {
for {
_, _, err := conn.ReadMessage()
if err != nil {
conn.Close()
return
}
}
}()
for { for {
select { select {
case message, ok := <-c.Messages: case message, ok := <-c.Messages:

View file

@ -12,25 +12,36 @@ import (
"time" "time"
"git.lattuga.net/boyska/circolog" "git.lattuga.net/boyska/circolog"
"git.lattuga.net/boyska/circolog/formatter"
"github.com/coreos/go-systemd/daemon" "github.com/coreos/go-systemd/daemon"
syslog "gopkg.in/mcuadros/go-syslog.v2" syslog "gopkg.in/mcuadros/go-syslog.v2"
) )
var socketsToRemove []string
func cleanSocket(socket string) { func cleanSocket(socket string) {
if err := os.Remove(socket); err != nil { if err := os.Remove(socket); err != nil {
fmt.Fprintln(os.Stderr, "Error cleaning", socket, ":", err) fmt.Fprintln(os.Stderr, "Error cleaning", socket, ":", err)
} }
} }
func removeAtExit(socket string) {
socketsToRemove = append(socketsToRemove, socket)
}
func main() { func main() {
var err error var err error
syslogSocketPath := flag.String("syslogd-socket", "", "The socket to listen to syslog addresses") var syslogSocket SyslogSocket
var logFmt formatter.SyslogRFC
logFmt.Format = syslog.Automatic
flag.Var(&syslogSocket, "syslogd-socket", "The socket to listen to syslog addresses")
// dumpSocketPath := flag.String("dump-socket", "/run/buffer.sock", "The socket that user will connect to in order to receive logs") // dumpSocketPath := flag.String("dump-socket", "/run/buffer.sock", "The socket that user will connect to in order to receive logs")
bufsize := flag.Int("buffer-size", 1000, "Number of messages to keep") bufsize := flag.Int("buffer-size", 1000, "Number of messages to keep")
syslogAddr := flag.String("syslog-addr", "127.0.0.1:9514", "Address:port where to listen for syslog messages") syslogAddr := flag.String("syslog-addr", "127.0.0.1:9514", "Address:port where to listen for syslog messages")
queryAddr := flag.String("query-addr", "127.0.0.1:9080", "Address:port where to bind the query service") queryAddr := flag.String("query-addr", "", "Address:port where to bind the query service")
querySocket := flag.String("query-socket", "", "Path to a unix domain socket for the HTTP server; recommended for security reasons!") querySocket := flag.String("query-socket", "/tmp/circologd-query.sock", "Path to a unix domain socket for the HTTP server; recommended for security reasons!")
ctlSocket := flag.String("ctl-socket", "/tmp/circologd-ctl.sock", "Path to a unix domain socket for the control server; leave empty to disable") ctlSocket := flag.String("ctl-socket", "/tmp/circologd-ctl.sock", "Path to a unix domain socket for the control server; leave empty to disable")
flag.Var(&logFmt, "log-fmt", "Log messages format. If not set, defaults to automatic choice. Allowed values: rfc3164, rfc5424, auto.")
verbose := flag.Bool("verbose", false, "Print more output executing the daemon") verbose := flag.Bool("verbose", false, "Print more output executing the daemon")
debug := flag.Bool("debug", false, "Print debugging info executing the daemon") debug := flag.Bool("debug", false, "Print debugging info executing the daemon")
flag.Parse() flag.Parse()
@ -43,15 +54,27 @@ func main() {
go hub.Run() go hub.Run()
server := syslog.NewServer() server := syslog.NewServer()
server.SetFormat(syslog.Automatic) server.SetFormat(logFmt.Format)
fmt.Printf("Syslog format set to: %s\n", logFmt.String())
server.SetHandler(handler) server.SetHandler(handler)
if *syslogSocketPath != "" { if syslogSocket.isSocketActivated {
if err = server.ListenUnixgram(*syslogSocketPath); err != nil { fmt.Printf("Binding to socket `%s` [syslog]\n", syslogSocket.String())
if syslogSocket.Listener != nil {
fmt.Println("(stream)")
server.Listen(syslogSocket.Listener)
} else {
fmt.Println("(datagram)", syslogSocket.Conn)
server.ListenDgram(syslogSocket.Conn)
}
} else {
syslogSocketPath := syslogSocket.Path
if syslogSocketPath != "" {
if err = server.ListenUnixgram(syslogSocketPath); err != nil {
fmt.Fprintln(os.Stderr, "argh", err) fmt.Fprintln(os.Stderr, "argh", err)
os.Exit(1) os.Exit(1)
} }
defer cleanSocket(*syslogSocketPath) fmt.Printf("Binding socket `%s` [syslog]\n", syslogSocketPath)
fmt.Printf("Binding socket `%s` [syslog]\n", *syslogSocketPath) removeAtExit(syslogSocketPath)
} else { } else {
fmt.Printf("Binding address `%s` [syslog]\n", *syslogAddr) fmt.Printf("Binding address `%s` [syslog]\n", *syslogAddr)
if err = server.ListenUDP(*syslogAddr); err != nil { if err = server.ListenUDP(*syslogAddr); err != nil {
@ -59,20 +82,21 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} }
}
if err = server.Boot(); err != nil { if err = server.Boot(); err != nil {
fmt.Fprintln(os.Stderr, "argh", err) fmt.Fprintln(os.Stderr, "argh", err)
os.Exit(1) os.Exit(1)
} }
httpQueryServer := http.Server{Handler: setupHTTP(hub)} httpQueryServer := http.Server{Handler: setupHTTP(hub)}
if *querySocket != "" { if *queryAddr == "" {
fmt.Printf("Binding address `%s` [http]\n", *querySocket) fmt.Printf("Binding address `%s` [http]\n", *querySocket)
unixListener, err := net.Listen("unix", *querySocket) unixListener, err := net.Listen("unix", *querySocket)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "Error binding HTTP unix domain socket", err) fmt.Fprintln(os.Stderr, "Error binding HTTP unix domain socket", err)
return return
} }
defer cleanSocket(*querySocket) removeAtExit(*querySocket)
go func() { go func() {
if err := httpQueryServer.Serve(unixListener); err != nil && err != http.ErrServerClosed { if err := httpQueryServer.Serve(unixListener); err != nil && err != http.ErrServerClosed {
fmt.Fprintln(os.Stderr, "error binding", *querySocket, ":", err) fmt.Fprintln(os.Stderr, "error binding", *querySocket, ":", err)
@ -97,7 +121,7 @@ func main() {
fmt.Fprintln(os.Stderr, "Error binding HTTP unix domain socket", err) fmt.Fprintln(os.Stderr, "Error binding HTTP unix domain socket", err)
return return
} }
defer cleanSocket(*ctlSocket) removeAtExit(*ctlSocket)
go func() { go func() {
if err := httpCtlServer.Serve(unixListener); err != nil && err != http.ErrServerClosed { if err := httpCtlServer.Serve(unixListener); err != nil && err != http.ErrServerClosed {
fmt.Fprintln(os.Stderr, "error binding:", err) fmt.Fprintln(os.Stderr, "error binding:", err)
@ -147,6 +171,9 @@ func main() {
if err := httpCtlServer.Shutdown(nil); err != nil { if err := httpCtlServer.Shutdown(nil); err != nil {
fmt.Fprintln(os.Stderr, "Error closing control server:", err) fmt.Fprintln(os.Stderr, "Error closing control server:", err)
} }
for _, socket := range socketsToRemove {
cleanSocket(socket)
}
return return
} }
} }

50
cmd/circologd/sockets.go Normal file
View file

@ -0,0 +1,50 @@
package main
import (
"net"
)
// SyslogSocket is a struct eventually containing a net.Listener
// ready with messages, and a Path in case the Listener is not present.
type SyslogSocket struct {
Listener net.Listener
Conn net.PacketConn
Path string
isSocketActivated bool
}
// Set from command-line
func (s *SyslogSocket) Set(v string) error {
err := s.getActivationSocket()
if err == nil && (s.Conn != nil || s.Listener != nil) {
s.isSocketActivated = true
}
if !s.isSocketActivated {
s.Path = v
}
return nil
}
func (s *SyslogSocket) String() string {
if s.isSocketActivated {
return "systemd-provided"
}
return s.Path
}
func (s *SyslogSocket) getActivationSocket() error {
conns, err := PacketConns()
if err == nil && len(conns) > 0 && conns[0] != nil {
s.Conn = conns[0]
return nil
}
listeners, err := Listeners()
if err != nil {
return err
}
if len(listeners) == 0 {
return nil
}
s.Listener = listeners[0]
return nil
}

View file

@ -69,4 +69,26 @@ Here is a working unit for this:
### journald with socket activation ### journald with socket activation
To run circologd as non-root, while listening on a root-owned socket (`/run/systemd/journal/syslog`) use To run circologd as non-root, while listening on a root-owned socket (`/run/systemd/journal/syslog`) use
socket activation socket activation. Create a unit in `/etc/systemd/system/circolog.service`:
[Unit]
Description=In-memory logging
[Service]
User=nobody
Group=nogroup
ExecStart=/usr/local/sbin/circologd -syslogd-socket "" -buffer-size 2000 -query-socket /run/circolog/query.sock
[Install]
WantedBy=multi-user.target
Then symlink the `syslog.service` unit to the newly created one:
ln -sf /etc/systemd/system/circolog.service /etc/systemd/system/syslog.service
and restart the service:
systemctl daemon-reload
systemctl restart syslog.service
Now circolog is activated and receives messages from `journald`.

View file

@ -4,5 +4,6 @@ nav:
- Install: install.md - Install: install.md
- Queries: query.md - Queries: query.md
- Hacking: hacking.md - Hacking: hacking.md
- Systemd: systemd.md
repo_url: https://git.lattuga.net/boyska/circolog repo_url: https://git.lattuga.net/boyska/circolog
repo_name: 'Repository' repo_name: 'Repository'

49
formatter/rfc.go Normal file
View file

@ -0,0 +1,49 @@
package formatter
import (
"errors"
syslog "gopkg.in/mcuadros/go-syslog.v2"
"gopkg.in/mcuadros/go-syslog.v2/format"
)
// SyslogRFC is the formatter that the server should use
type SyslogRFC struct{ format.Format }
func (rfc *SyslogRFC) Set(v string) error {
newval, err := parseRFCValue(v)
if err != nil {
return err
}
rfc.Format = newval
return nil
}
func (rfc *SyslogRFC) String() string {
switch {
case rfc.Format == syslog.Automatic:
return "auto"
case rfc.Format == syslog.RFC3164:
return "rfc3164"
case rfc.Format == syslog.RFC5424:
return "rfc5424"
}
return ""
}
func parseRFCValue(v string) (format.Format, error) {
switch {
case v == "rfc3164":
return syslog.RFC3164, nil
case v == "rfc5424":
return syslog.RFC5424, nil
case v == "auto":
return syslog.Automatic, nil
default:
return nil, ErrRFCNotSupported
}
}
// ErrRFCNotSupported is raised if the supplied rfc string is
// not recognized.
var ErrRFCNotSupported = errors.New("RFC not known")

20
release.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash
set -u
for goosarch in $(go tool dist list | grep -vw -e aix -e js/wasm -e plan9 -e solaris -e android -e nacl)
do
mkdir -p "build/$goosarch"
goos=$(cut -d/ -f 1 <<<$goosarch)
goarch=$(cut -d/ -f 2 <<<$goosarch)
for cmd in cmd/*; do
GOOS=${goos} GOARCH=${goarch} go build -o "build/$goos/$goarch/$(basename $cmd)" ./$cmd
done
done
find build/ -type f|cut -d/ -f 1-3|uniq|while read -r dir; do
find $dir/ -type f -executable | xargs sha1sum > $dir/SHA1SUMS.txt
# TODO: touch to last commit date maybe
find build -exec touch -d @1234567890 {} \;
zip -q -X -j -r "circolog-$(git describe --tags --always)-$(cut -d/ -f 2-3 <<<"$dir"|tr / -)" "$dir"
done

@ -1 +1 @@
Subproject commit a127d826d6c27489d377b3c080bd256d8f8093a6 Subproject commit 166aad3f993ce4a67bf486e62d637c834c8a8fe6