From 85ccd655436fd021d590a4cb6c30a68676ab0164 Mon Sep 17 00:00:00 2001 From: boyska Date: Fri, 11 Jan 2019 19:36:59 +0100 Subject: [PATCH 1/5] receives data from multiple formats however, data structure is variable; this makes templates unnecessary complex. It would be better to convert everything to rfc5424 (at some point) --- cmd/circologd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/circologd/main.go b/cmd/circologd/main.go index 41fb95b..f0a2de2 100644 --- a/cmd/circologd/main.go +++ b/cmd/circologd/main.go @@ -43,7 +43,7 @@ func main() { go hub.Run() server := syslog.NewServer() - server.SetFormat(syslog.RFC5424) + server.SetFormat(syslog.Automatic) server.SetHandler(handler) if *syslogSocketPath != "" { if err = server.ListenUnixgram(*syslogSocketPath); err != nil { From aeceda5caaef4c1818401e40db13482a9727b35d Mon Sep 17 00:00:00 2001 From: boyska Date: Sun, 24 Mar 2019 19:54:41 +0100 Subject: [PATCH 2/5] circolog.Message: for readers refs #14 --- cmd/circolog-tail/main.go | 4 ++-- cmd/circologd/http_log.go | 5 ++--- formatter/format.go | 16 ++++++++-------- hub.go | 30 ++++++++++++++++++++++++++---- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/cmd/circolog-tail/main.go b/cmd/circolog-tail/main.go index 3c44afe..eb446ae 100644 --- a/cmd/circolog-tail/main.go +++ b/cmd/circolog-tail/main.go @@ -11,12 +11,12 @@ import ( "strconv" "time" + "git.lattuga.net/boyska/circolog" "git.lattuga.net/boyska/circolog/filtering" "git.lattuga.net/boyska/circolog/formatter" "github.com/gorilla/websocket" isatty "github.com/mattn/go-isatty" "github.com/mgutz/ansi" - "gopkg.in/mcuadros/go-syslog.v2/format" "gopkg.in/mgo.v2/bson" ) @@ -112,7 +112,7 @@ func main() { log.Println("close:", err) return } - var parsed format.LogParts + var parsed circolog.Message if err := bson.Unmarshal(serialized, &parsed); err != nil { log.Println("invalid BSON", err) continue diff --git a/cmd/circologd/http_log.go b/cmd/circologd/http_log.go index 37077de..be09f19 100644 --- a/cmd/circologd/http_log.go +++ b/cmd/circologd/http_log.go @@ -11,7 +11,6 @@ import ( "git.lattuga.net/boyska/circolog" "git.lattuga.net/boyska/circolog/formatter" "github.com/gorilla/websocket" - "gopkg.in/mcuadros/go-syslog.v2/format" ) func setupHTTP(hub circolog.Hub) *http.ServeMux { @@ -100,7 +99,7 @@ func getHTTPHandler(hub circolog.Hub) http.HandlerFunc { opts.Nofollow = true client := circolog.Client{ - Messages: make(chan format.LogParts, 20), + Messages: make(chan circolog.Message, 20), Options: opts, } hub.Register <- client @@ -141,7 +140,7 @@ func getWSHandler(hub circolog.Hub) http.HandlerFunc { return } client := circolog.Client{ - Messages: make(chan format.LogParts, 20), + Messages: make(chan circolog.Message, 20), Options: opts, } hub.Register <- client diff --git a/formatter/format.go b/formatter/format.go index eda0a41..997531d 100644 --- a/formatter/format.go +++ b/formatter/format.go @@ -8,13 +8,13 @@ import ( "text/template" "time" + "git.lattuga.net/boyska/circolog" "github.com/mgutz/ansi" - "gopkg.in/mcuadros/go-syslog.v2/format" "gopkg.in/mgo.v2/bson" ) // Formatter is an interface, so that multiple implementations can exist -type Formatter func(format.LogParts) string +type Formatter func(circolog.Message) string var tmplFuncs template.FuncMap var syslogTmpl *template.Template @@ -56,11 +56,11 @@ func init() { }, } syslogTmpl = template.Must(template.New("syslog").Funcs(tmplFuncs).Parse( - "{{color \"yellow\" (rfc822 (index . \"timestamp\")) }} {{index . \"hostname\"}} " + - "{{index . \"app_name\" | autoColor}}" + + "{{color \"yellow\" (rfc822 (index . \"time\")) }} {{index . \"hostname\"}} " + + "{{index . \"prog\" | autoColor}}" + "{{ if (ne (index . \"proc_id\") \"-\")}}[{{index . \"proc_id\"}}]{{end}}: " + - "{{ sevName (index . \"severity\") }} " + - "{{index . \"message\"}}", + "{{ sevName (index . \"sev\") }} " + + "{{index . \"msg\"}}", )) } @@ -87,7 +87,7 @@ func (rf Format) String() string { return "" } -func (rf Format) WriteFormatted(w io.Writer, msg format.LogParts) error { +func (rf Format) WriteFormatted(w io.Writer, msg circolog.Message) error { return WriteFormatted(w, rf, msg) } @@ -110,7 +110,7 @@ const ( FormatBSON = iota ) -func WriteFormatted(w io.Writer, f Format, msg format.LogParts) error { +func WriteFormatted(w io.Writer, f Format, msg circolog.Message) error { switch f { case FormatSyslog: return syslogTmpl.Execute(w, msg) diff --git a/hub.go b/hub.go index 7f01ec8..7d5d099 100644 --- a/hub.go +++ b/hub.go @@ -10,13 +10,19 @@ import ( "gopkg.in/mcuadros/go-syslog.v2/format" ) +// Message is currently an alias for format.Logparts, but this is only temporary; sooner or later, a real struct will be used +// The advantage of having an explicit Message is to clear out what data we are sending to circolog "readers" +// This is not necessarily (and not in practice) the same structure that we receive from logging programs +type Message format.LogParts + // Client represent a client connected via websocket. Its most important field is the messages channel, where // new messages are sent. type Client struct { - Messages chan format.LogParts // only hub should write/close this + Messages chan Message // only hub should write/close this Options ClientOptions } +// ClientOptions is a struct containing connection options for every reader type ClientOptions struct { BacklogLength int // how many past messages the client wants to receive upon connection Nofollow bool // if Nofollow is true, the hub will not keep this client permanently. Rather, it will send every message to "Messages" and close the channel. Use this if you want to get the messages one-shot @@ -100,7 +106,7 @@ func (h *Hub) register(cl Client) { item := buf.Value if item != nil { select { // send with short timeout - case cl.Messages <- item.(format.LogParts): + case cl.Messages <- item.(Message): break case <-time.After(500 * time.Millisecond): close(cl.Messages) @@ -117,6 +123,21 @@ func (h *Hub) register(cl Client) { } } +// logEntryToMessage converts messages received from writers to the format we promise to readers +func logEntryToMessage(orig format.LogParts) Message { + m := Message{} + // it currently only supports RFC5424. TODO: support RFC3164 + m["prog"] = orig["app_name"] + m["client"] = orig["client"] + m["hostname"] = orig["hostname"] + m["proc_id"] = orig["proc_id"] + m["msg"] = orig["message"] + m["facility"] = orig["facility"] + m["time"] = orig["timestamp"] + m["sev"] = orig["severity"] + return m +} + // Run is hub main loop; keeps everything going func (h *Hub) Run() { active := true @@ -133,11 +154,12 @@ func (h *Hub) Run() { } case msg := <-h.LogMessages: if active == true && filter.Validate(msg) { - h.circbuf.Value = msg + newmsg := logEntryToMessage(msg) + h.circbuf.Value = newmsg h.circbuf = h.circbuf.Next() for client := range h.clients { select { // send without blocking - case client.Messages <- msg: + case client.Messages <- newmsg: break default: break From 46a031695c0414137dfbf9aa3dcfff71d6b54506 Mon Sep 17 00:00:00 2001 From: boyska Date: Sun, 24 Mar 2019 20:00:17 +0100 Subject: [PATCH 3/5] supports rfc3164 also, align hostname to docs -> host --- formatter/format.go | 2 +- hub.go | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/formatter/format.go b/formatter/format.go index 997531d..3396378 100644 --- a/formatter/format.go +++ b/formatter/format.go @@ -56,7 +56,7 @@ func init() { }, } syslogTmpl = template.Must(template.New("syslog").Funcs(tmplFuncs).Parse( - "{{color \"yellow\" (rfc822 (index . \"time\")) }} {{index . \"hostname\"}} " + + "{{color \"yellow\" (rfc822 (index . \"time\")) }} {{index . \"host\"}} " + "{{index . \"prog\" | autoColor}}" + "{{ if (ne (index . \"proc_id\") \"-\")}}[{{index . \"proc_id\"}}]{{end}}: " + "{{ sevName (index . \"sev\") }} " + diff --git a/hub.go b/hub.go index 7d5d099..a59bd84 100644 --- a/hub.go +++ b/hub.go @@ -126,15 +126,24 @@ func (h *Hub) register(cl Client) { // logEntryToMessage converts messages received from writers to the format we promise to readers func logEntryToMessage(orig format.LogParts) Message { m := Message{} - // it currently only supports RFC5424. TODO: support RFC3164 - m["prog"] = orig["app_name"] - m["client"] = orig["client"] - m["hostname"] = orig["hostname"] - m["proc_id"] = orig["proc_id"] - m["msg"] = orig["message"] - m["facility"] = orig["facility"] - m["time"] = orig["timestamp"] - m["sev"] = orig["severity"] + if orig["version"] == 1 { // RFC5424 + m["prog"] = orig["app_name"] + m["client"] = orig["client"] + m["host"] = orig["hostname"] + m["proc_id"] = orig["proc_id"] + m["msg"] = orig["message"] + m["facility"] = orig["facility"] + m["time"] = orig["timestamp"] + m["sev"] = orig["severity"] + } else { //RFC3164 + m["prog"] = orig["tag"] + m["client"] = orig["client"] + m["host"] = orig["hostname"] + m["msg"] = orig["content"] + m["sev"] = orig["severity"] + m["time"] = orig["timestamp"] + m["proc_id"] = "-" + } return m } From fefd2d7e5ccae6f4374a2ab3bad850cdb8443c48 Mon Sep 17 00:00:00 2001 From: boyska Date: Mon, 25 Mar 2019 01:59:48 +0100 Subject: [PATCH 4/5] systemd docs --- docs/docs/systemd.md | 51 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/docs/docs/systemd.md b/docs/docs/systemd.md index 8d7c783..eac05ef 100644 --- a/docs/docs/systemd.md +++ b/docs/docs/systemd.md @@ -1,7 +1,24 @@ -unit systemd con utente dinamico: - - no adduser/altre conf - - utente con pochi permessi - - accesso consentito al gruppo adm +## A simple start + +The bare minimum you need to get circologd on a systemd-based system is this unit. +Other options with more features or more security are provided below + + [Unit] + Description=In-memory logging + + [Service] + User=root + Group=adm + ExecStart=/usr/local/sbin/circologd -syslogd-socket /run/circolog/syslog.sock -buffer-size 2000 -query-socket /run/circolog/query.sock + + [Install] + WantedBy=multi-user.target + + +## A better unit + +This is another unit, which has several security features, such as `DynamicUser`, filesystem restrictions, and +more. [Unit] Description=In-memory logging @@ -27,3 +44,29 @@ unit systemd con utente dinamico: [Install] WantedBy=multi-user.target +## Journald + +None of those are integrated with journald, however. The simplest way to integrate with journald is the +following. + +First of all, ensure `ForwardToSyslog=yes` in `/etc/systemd/journald.conf`. +Then, you need to run circologd as root and bind it [to a special +address](https://www.freedesktop.org/software/systemd/man/journald.conf.html#Forwarding%20to%20traditional%20syslog%20daemons). +Ok, you don't strictly _need_ to run it as root, but that's the easiest way to run it. +Here is a working unit for this: + + [Unit] + Description=In-memory logging + + [Service] + User=root + Group=adm + ExecStart=/usr/local/sbin/circologd -syslogd-socket /run/systemd/journal/syslog -buffer-size 2000 -query-socket /run/circolog/query.sock + + [Install] + WantedBy=multi-user.target + +### journald with socket activation + +To run circologd as non-root, while listening on a root-owned socket (`/run/systemd/journal/syslog`) use +socket activation From ec3934501a785231f2f106c289080d3b17ae92d1 Mon Sep 17 00:00:00 2001 From: boyska Date: Mon, 25 Mar 2019 02:46:03 +0100 Subject: [PATCH 5/5] filtering based on reader-Messages --- cmd/circolog-tail/main.go | 4 ++-- cmd/circologd/http_log.go | 5 +++-- data/data.go | 32 ++++++++++++++++++++++++++++++++ filtering/filter.go | 15 ++------------- formatter/format.go | 8 ++++---- hub.go | 38 +++++--------------------------------- 6 files changed, 48 insertions(+), 54 deletions(-) create mode 100644 data/data.go diff --git a/cmd/circolog-tail/main.go b/cmd/circolog-tail/main.go index eb446ae..4d13950 100644 --- a/cmd/circolog-tail/main.go +++ b/cmd/circolog-tail/main.go @@ -11,7 +11,7 @@ import ( "strconv" "time" - "git.lattuga.net/boyska/circolog" + "git.lattuga.net/boyska/circolog/data" "git.lattuga.net/boyska/circolog/filtering" "git.lattuga.net/boyska/circolog/formatter" "github.com/gorilla/websocket" @@ -112,7 +112,7 @@ func main() { log.Println("close:", err) return } - var parsed circolog.Message + var parsed data.Message if err := bson.Unmarshal(serialized, &parsed); err != nil { log.Println("invalid BSON", err) continue diff --git a/cmd/circologd/http_log.go b/cmd/circologd/http_log.go index be09f19..712d15a 100644 --- a/cmd/circologd/http_log.go +++ b/cmd/circologd/http_log.go @@ -9,6 +9,7 @@ import ( "time" "git.lattuga.net/boyska/circolog" + "git.lattuga.net/boyska/circolog/data" "git.lattuga.net/boyska/circolog/formatter" "github.com/gorilla/websocket" ) @@ -99,7 +100,7 @@ func getHTTPHandler(hub circolog.Hub) http.HandlerFunc { opts.Nofollow = true client := circolog.Client{ - Messages: make(chan circolog.Message, 20), + Messages: make(chan data.Message, 20), Options: opts, } hub.Register <- client @@ -140,7 +141,7 @@ func getWSHandler(hub circolog.Hub) http.HandlerFunc { return } client := circolog.Client{ - Messages: make(chan circolog.Message, 20), + Messages: make(chan data.Message, 20), Options: opts, } hub.Register <- client diff --git a/data/data.go b/data/data.go new file mode 100644 index 0000000..314fdb1 --- /dev/null +++ b/data/data.go @@ -0,0 +1,32 @@ +package data + +import "gopkg.in/mcuadros/go-syslog.v2/format" + +// Message is currently an alias for format.Logparts, but this is only temporary; sooner or later, a real struct will be used +// The advantage of having an explicit Message is to clear out what data we are sending to circolog "readers" +// This is not necessarily (and not in practice) the same structure that we receive from logging programs +type Message format.LogParts + +// LogEntryToMessage converts messages received from writers to the format we promise to readers +func LogEntryToMessage(orig format.LogParts) Message { + m := Message{} + if orig["version"] == 1 { // RFC5424 + m["prog"] = orig["app_name"] + m["client"] = orig["client"] + m["host"] = orig["hostname"] + m["proc_id"] = orig["proc_id"] + m["msg"] = orig["message"] + m["facility"] = orig["facility"] + m["time"] = orig["timestamp"] + m["sev"] = orig["severity"] + } else { //RFC3164 + m["prog"] = orig["tag"] + m["client"] = orig["client"] + m["host"] = orig["hostname"] + m["msg"] = orig["content"] + m["sev"] = orig["severity"] + m["time"] = orig["timestamp"] + m["proc_id"] = "-" + } + return m +} diff --git a/filtering/filter.go b/filtering/filter.go index 10889b7..25b26bd 100644 --- a/filtering/filter.go +++ b/filtering/filter.go @@ -6,6 +6,7 @@ import ( "fmt" "os" + "git.lattuga.net/boyska/circolog/data" "github.com/araddon/qlbridge/datasource" "github.com/araddon/qlbridge/expr" "github.com/araddon/qlbridge/value" @@ -40,11 +41,10 @@ func (e *ExprValue) Set(value string) error { } // Validate answers the question whether to include a log line or not. -func (e *ExprValue) Validate(logLine map[string]interface{}) bool { +func (e *ExprValue) Validate(line data.Message) bool { if e.node == nil { return true } - line := translateMap(logLine) context := datasource.NewContextSimpleNative(line) val, ok := vm.Eval(context, e.node) if !ok || val == nil { // errors when evaluating @@ -56,14 +56,3 @@ func (e *ExprValue) Validate(logLine map[string]interface{}) bool { fmt.Fprintln(os.Stderr, "WARNING: The 'where' expression doesn't return a boolean") return false } - -func translateMap(lineInput map[string]interface{}) map[string]interface{} { - lineOutput := make(map[string]interface{}) - lineOutput["prog"] = lineInput["app_name"] - lineOutput["msg"] = lineInput["message"] - lineOutput["facility"] = lineInput["facility"] - lineOutput["host"] = lineInput["hostname"] - lineOutput["time"] = lineInput["timestamp"] - lineOutput["sev"] = lineInput["severity"] - return lineOutput -} diff --git a/formatter/format.go b/formatter/format.go index 3396378..a037c1c 100644 --- a/formatter/format.go +++ b/formatter/format.go @@ -8,13 +8,13 @@ import ( "text/template" "time" - "git.lattuga.net/boyska/circolog" + "git.lattuga.net/boyska/circolog/data" "github.com/mgutz/ansi" "gopkg.in/mgo.v2/bson" ) // Formatter is an interface, so that multiple implementations can exist -type Formatter func(circolog.Message) string +type Formatter func(data.Message) string var tmplFuncs template.FuncMap var syslogTmpl *template.Template @@ -87,7 +87,7 @@ func (rf Format) String() string { return "" } -func (rf Format) WriteFormatted(w io.Writer, msg circolog.Message) error { +func (rf Format) WriteFormatted(w io.Writer, msg data.Message) error { return WriteFormatted(w, rf, msg) } @@ -110,7 +110,7 @@ const ( FormatBSON = iota ) -func WriteFormatted(w io.Writer, f Format, msg circolog.Message) error { +func WriteFormatted(w io.Writer, f Format, msg data.Message) error { switch f { case FormatSyslog: return syslogTmpl.Execute(w, msg) diff --git a/hub.go b/hub.go index a59bd84..0894d3b 100644 --- a/hub.go +++ b/hub.go @@ -6,19 +6,15 @@ import ( "os" "time" + "git.lattuga.net/boyska/circolog/data" "git.lattuga.net/boyska/circolog/filtering" "gopkg.in/mcuadros/go-syslog.v2/format" ) -// Message is currently an alias for format.Logparts, but this is only temporary; sooner or later, a real struct will be used -// The advantage of having an explicit Message is to clear out what data we are sending to circolog "readers" -// This is not necessarily (and not in practice) the same structure that we receive from logging programs -type Message format.LogParts - // Client represent a client connected via websocket. Its most important field is the messages channel, where // new messages are sent. type Client struct { - Messages chan Message // only hub should write/close this + Messages chan data.Message // only hub should write/close this Options ClientOptions } @@ -106,7 +102,7 @@ func (h *Hub) register(cl Client) { item := buf.Value if item != nil { select { // send with short timeout - case cl.Messages <- item.(Message): + case cl.Messages <- item.(data.Message): break case <-time.After(500 * time.Millisecond): close(cl.Messages) @@ -123,30 +119,6 @@ func (h *Hub) register(cl Client) { } } -// logEntryToMessage converts messages received from writers to the format we promise to readers -func logEntryToMessage(orig format.LogParts) Message { - m := Message{} - if orig["version"] == 1 { // RFC5424 - m["prog"] = orig["app_name"] - m["client"] = orig["client"] - m["host"] = orig["hostname"] - m["proc_id"] = orig["proc_id"] - m["msg"] = orig["message"] - m["facility"] = orig["facility"] - m["time"] = orig["timestamp"] - m["sev"] = orig["severity"] - } else { //RFC3164 - m["prog"] = orig["tag"] - m["client"] = orig["client"] - m["host"] = orig["hostname"] - m["msg"] = orig["content"] - m["sev"] = orig["severity"] - m["time"] = orig["timestamp"] - m["proc_id"] = "-" - } - return m -} - // Run is hub main loop; keeps everything going func (h *Hub) Run() { active := true @@ -162,8 +134,8 @@ func (h *Hub) Run() { delete(h.clients, cl) } case msg := <-h.LogMessages: - if active == true && filter.Validate(msg) { - newmsg := logEntryToMessage(msg) + newmsg := data.LogEntryToMessage(msg) + if active == true && filter.Validate(newmsg) { h.circbuf.Value = newmsg h.circbuf = h.circbuf.Next() for client := range h.clients {