From 6cd28cbb1dd1da64c9989f59b7bbe8bcefa2a616 Mon Sep 17 00:00:00 2001 From: Quentin Rousseau Date: Mon, 19 Mar 2018 16:47:20 -0700 Subject: [PATCH] Add more template functions --- README.md | 17 ++++++-- adapters/syslog/syslog.go | 8 +++- adapters/syslog/syslog_test.go | 75 ++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f078d6af..ca70b46d 100644 --- a/README.md +++ b/README.md @@ -59,17 +59,17 @@ You can tell logspout to only include certain containers by setting filter param --volume=/var/run/docker.sock:/var/run/docker.sock \ gliderlabs/logspout \ raw://192.168.10.10:5000?filter.name=*_db - + $ docker run \ --volume=/var/run/docker.sock:/var/run/docker.sock \ gliderlabs/logspout \ raw://192.168.10.10:5000?filter.id=3b6ba57db54a - + $ docker run \ --volume=/var/run/docker.sock:/var/run/docker.sock \ gliderlabs/logspout \ raw://192.168.10.10:5000?filter.sources=stdout%2Cstderr - + # Forward logs from containers with both label 'a' starting with 'x', and label 'b' ending in 'y'. $ docker run \ --volume=/var/run/docker.sock:/var/run/docker.sock \ @@ -156,6 +156,15 @@ Logspout relies on the Docker API to retrieve container logs. A failure in the A * `SYSLOG_TAG` - datum for tag field (default `{{.ContainerName}}+route.Options["append_tag"]`) * `SYSLOG_TIMESTAMP` - datum for timestamp field (default `{{.Timestamp}}`) +##### Built-in Template Functions + +There are a few built in functions as well: + +* `join $string[] $sep` - Join concatenates the elements of a to create a single string. The separator string sep is placed between elements in the resulting string. Alias for [`strings.Join`][go.string.Join]. `{{ join . "-"}}` +* `replace $string $old $new $count` - Replaces all occurrences of a string within another string. Alias for [`strings.Replace`][go.string.Replace]. `{{ replace .Container.Config.Hostname "-" "_" -1 }}` +* `split $string $sep` - Splits a string into an array using a separator string. Alias for [`strings.Split`][go.string.Split]. `{{ split .Container.Config.Hostname "." }}` + + #### Raw Format The raw adapter has a function `toJSON` that can be used to format the message/fields to generate JSON-like output in a simple way, or full JSON output. @@ -233,7 +242,7 @@ docker stack deploy --compose-file ``` More information about services and their mode of deployment can be found here: -https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/ +https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/ ## Modules diff --git a/adapters/syslog/syslog.go b/adapters/syslog/syslog.go index 72fd4b4b..24f17f7d 100644 --- a/adapters/syslog/syslog.go +++ b/adapters/syslog/syslog.go @@ -26,6 +26,12 @@ var ( econnResetErrStr string ) +var funcs = template.FuncMap{ + "join": strings.Join, + "replace": strings.Replace, + "split": strings.Split, +} + func init() { hostname, _ = os.Hostname() econnResetErrStr = fmt.Sprintf("write: %s", syscall.ECONNRESET.Error()) @@ -107,7 +113,7 @@ func NewSyslogAdapter(route *router.Route) (router.LogAdapter, error) { default: return nil, errors.New("unsupported syslog format: " + format) } - tmpl, err := template.New("syslog").Parse(tmplStr) + tmpl, err := template.New("syslog").Funcs(funcs).Parse(tmplStr) if err != nil { return nil, err } diff --git a/adapters/syslog/syslog_test.go b/adapters/syslog/syslog_test.go index d6dcca40..b89077cc 100644 --- a/adapters/syslog/syslog_test.go +++ b/adapters/syslog/syslog_test.go @@ -14,6 +14,7 @@ import ( "testing" "text/template" "time" + "bytes" docker "github.com/fsouza/go-dockerclient" "github.com/gliderlabs/logspout/router" @@ -107,6 +108,64 @@ func TestSyslogReconnectOnClose(t *testing.T) { } } +func TestSyslogReplaceFunc(t *testing.T) { + in := "{{ replace \"oink oink oink\" \"k\" \"ky\" 2}}" + os.Setenv("SYSLOG_STRUCTURED_DATA", in) + adapter, err := newDummyAdapter() + if err != nil { + t.Fatal(err) + } + + out := new(bytes.Buffer) + err = adapter.(*Adapter).tmpl.Execute(out, "") + + if err != nil { + log.Fatalf("template error: %s\n", err) + } + + expected := "1 TIMESTAMP HOSTNAME TAG PID - [oinky oinky oink] DATA\n" + check(t, adapter.(*Adapter).tmpl, expected, out.String()) +} + +func TestSyslogJoinFunc(t *testing.T) { + array := []string{"foo", "bar"} + in := "{{ join . \"-\" }}" + os.Setenv("SYSLOG_STRUCTURED_DATA", in) + adapter, err := newDummyAdapter() + if err != nil { + t.Fatal(err) + } + + out := new(bytes.Buffer) + err = adapter.(*Adapter).tmpl.Execute(out, array) + + if err != nil { + log.Fatalf("template error: %s\n", err) + } + + expected := "1 TIMESTAMP HOSTNAME TAG PID - [foo-bar] DATA\n" + check(t, adapter.(*Adapter).tmpl, expected, out.String()) +} + +func TestSyslogSplitFunc(t *testing.T) { + in := "{{ index (split \"foo/bar\" \"/\") 1 }}" + os.Setenv("SYSLOG_STRUCTURED_DATA", in) + adapter, err := newDummyAdapter() + if err != nil { + t.Fatal(err) + } + + out := new(bytes.Buffer) + err = adapter.(*Adapter).tmpl.Execute(out, "") + + if err != nil { + log.Fatalf("template error: %s\n", err) + } + + expected := "1 TIMESTAMP HOSTNAME TAG PID - [bar] DATA\n" + check(t, adapter.(*Adapter).tmpl, expected, out.String()) +} + func TestHostnameDoesNotHaveLineFeed(t *testing.T) { if err := ioutil.WriteFile(hostHostnameFilename, []byte(badHostnameContent), 0777); err != nil { t.Fatal(err) @@ -117,6 +176,22 @@ func TestHostnameDoesNotHaveLineFeed(t *testing.T) { } } +func newDummyAdapter()(router.LogAdapter, error) { + os.Setenv("SYSLOG_PRIORITY", "PRIORITY") + os.Setenv("SYSLOG_TIMESTAMP", "TIMESTAMP") + os.Setenv("SYSLOG_PID", "PID") + os.Setenv("SYSLOG_HOSTNAME", "HOSTNAME") + os.Setenv("SYSLOG_TAG", "TAG") + os.Setenv("SYSLOG_DATA", "DATA") + done := make(chan string) + addr, sock, srvWG := startServer("tcp", "", done) + defer srvWG.Wait() + defer os.Remove(addr) + defer sock.Close() + route := &router.Route{Adapter: "syslog+tcp", Address: addr} + return NewSyslogAdapter(route) +} + func startServer(n, la string, done chan<- string) (addr string, sock io.Closer, wg *sync.WaitGroup) { if n == "udp" || n == "tcp" { la = "127.0.0.1:0"