Skip to content

Commit

Permalink
Add more template functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentin Rousseau committed Mar 20, 2018
1 parent 5839e0b commit 6cd28cb
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 5 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -233,7 +242,7 @@ docker stack deploy --compose-file <name of your 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

Expand Down
8 changes: 7 additions & 1 deletion adapters/syslog/syslog.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
}
Expand Down
75 changes: 75 additions & 0 deletions adapters/syslog/syslog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"testing"
"text/template"
"time"
"bytes"

docker "github.com/fsouza/go-dockerclient"
"github.com/gliderlabs/logspout/router"
Expand Down Expand Up @@ -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 := "<PRIORITY>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 := "<PRIORITY>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 := "<PRIORITY>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)
Expand All @@ -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"
Expand Down

0 comments on commit 6cd28cb

Please sign in to comment.