Skip to content

Commit 6cd28cb

Browse files
author
Quentin Rousseau
committed
Add more template functions
1 parent 5839e0b commit 6cd28cb

File tree

3 files changed

+95
-5
lines changed

3 files changed

+95
-5
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,17 @@ You can tell logspout to only include certain containers by setting filter param
5959
--volume=/var/run/docker.sock:/var/run/docker.sock \
6060
gliderlabs/logspout \
6161
raw://192.168.10.10:5000?filter.name=*_db
62-
62+
6363
$ docker run \
6464
--volume=/var/run/docker.sock:/var/run/docker.sock \
6565
gliderlabs/logspout \
6666
raw://192.168.10.10:5000?filter.id=3b6ba57db54a
67-
67+
6868
$ docker run \
6969
--volume=/var/run/docker.sock:/var/run/docker.sock \
7070
gliderlabs/logspout \
7171
raw://192.168.10.10:5000?filter.sources=stdout%2Cstderr
72-
72+
7373
# Forward logs from containers with both label 'a' starting with 'x', and label 'b' ending in 'y'.
7474
$ docker run \
7575
--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
156156
* `SYSLOG_TAG` - datum for tag field (default `{{.ContainerName}}+route.Options["append_tag"]`)
157157
* `SYSLOG_TIMESTAMP` - datum for timestamp field (default `{{.Timestamp}}`)
158158

159+
##### Built-in Template Functions
160+
161+
There are a few built in functions as well:
162+
163+
* `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 . "-"}}`
164+
* `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 }}`
165+
* `split $string $sep` - Splits a string into an array using a separator string. Alias for [`strings.Split`][go.string.Split]. `{{ split .Container.Config.Hostname "." }}`
166+
167+
159168
#### Raw Format
160169

161170
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 <name of your compose file>
233242
```
234243

235244
More information about services and their mode of deployment can be found here:
236-
https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/
245+
https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/
237246

238247
## Modules
239248

adapters/syslog/syslog.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ var (
2626
econnResetErrStr string
2727
)
2828

29+
var funcs = template.FuncMap{
30+
"join": strings.Join,
31+
"replace": strings.Replace,
32+
"split": strings.Split,
33+
}
34+
2935
func init() {
3036
hostname, _ = os.Hostname()
3137
econnResetErrStr = fmt.Sprintf("write: %s", syscall.ECONNRESET.Error())
@@ -107,7 +113,7 @@ func NewSyslogAdapter(route *router.Route) (router.LogAdapter, error) {
107113
default:
108114
return nil, errors.New("unsupported syslog format: " + format)
109115
}
110-
tmpl, err := template.New("syslog").Parse(tmplStr)
116+
tmpl, err := template.New("syslog").Funcs(funcs).Parse(tmplStr)
111117
if err != nil {
112118
return nil, err
113119
}

adapters/syslog/syslog_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"testing"
1515
"text/template"
1616
"time"
17+
"bytes"
1718

1819
docker "github.com/fsouza/go-dockerclient"
1920
"github.com/gliderlabs/logspout/router"
@@ -107,6 +108,64 @@ func TestSyslogReconnectOnClose(t *testing.T) {
107108
}
108109
}
109110

111+
func TestSyslogReplaceFunc(t *testing.T) {
112+
in := "{{ replace \"oink oink oink\" \"k\" \"ky\" 2}}"
113+
os.Setenv("SYSLOG_STRUCTURED_DATA", in)
114+
adapter, err := newDummyAdapter()
115+
if err != nil {
116+
t.Fatal(err)
117+
}
118+
119+
out := new(bytes.Buffer)
120+
err = adapter.(*Adapter).tmpl.Execute(out, "")
121+
122+
if err != nil {
123+
log.Fatalf("template error: %s\n", err)
124+
}
125+
126+
expected := "<PRIORITY>1 TIMESTAMP HOSTNAME TAG PID - [oinky oinky oink] DATA\n"
127+
check(t, adapter.(*Adapter).tmpl, expected, out.String())
128+
}
129+
130+
func TestSyslogJoinFunc(t *testing.T) {
131+
array := []string{"foo", "bar"}
132+
in := "{{ join . \"-\" }}"
133+
os.Setenv("SYSLOG_STRUCTURED_DATA", in)
134+
adapter, err := newDummyAdapter()
135+
if err != nil {
136+
t.Fatal(err)
137+
}
138+
139+
out := new(bytes.Buffer)
140+
err = adapter.(*Adapter).tmpl.Execute(out, array)
141+
142+
if err != nil {
143+
log.Fatalf("template error: %s\n", err)
144+
}
145+
146+
expected := "<PRIORITY>1 TIMESTAMP HOSTNAME TAG PID - [foo-bar] DATA\n"
147+
check(t, adapter.(*Adapter).tmpl, expected, out.String())
148+
}
149+
150+
func TestSyslogSplitFunc(t *testing.T) {
151+
in := "{{ index (split \"foo/bar\" \"/\") 1 }}"
152+
os.Setenv("SYSLOG_STRUCTURED_DATA", in)
153+
adapter, err := newDummyAdapter()
154+
if err != nil {
155+
t.Fatal(err)
156+
}
157+
158+
out := new(bytes.Buffer)
159+
err = adapter.(*Adapter).tmpl.Execute(out, "")
160+
161+
if err != nil {
162+
log.Fatalf("template error: %s\n", err)
163+
}
164+
165+
expected := "<PRIORITY>1 TIMESTAMP HOSTNAME TAG PID - [bar] DATA\n"
166+
check(t, adapter.(*Adapter).tmpl, expected, out.String())
167+
}
168+
110169
func TestHostnameDoesNotHaveLineFeed(t *testing.T) {
111170
if err := ioutil.WriteFile(hostHostnameFilename, []byte(badHostnameContent), 0777); err != nil {
112171
t.Fatal(err)
@@ -117,6 +176,22 @@ func TestHostnameDoesNotHaveLineFeed(t *testing.T) {
117176
}
118177
}
119178

179+
func newDummyAdapter()(router.LogAdapter, error) {
180+
os.Setenv("SYSLOG_PRIORITY", "PRIORITY")
181+
os.Setenv("SYSLOG_TIMESTAMP", "TIMESTAMP")
182+
os.Setenv("SYSLOG_PID", "PID")
183+
os.Setenv("SYSLOG_HOSTNAME", "HOSTNAME")
184+
os.Setenv("SYSLOG_TAG", "TAG")
185+
os.Setenv("SYSLOG_DATA", "DATA")
186+
done := make(chan string)
187+
addr, sock, srvWG := startServer("tcp", "", done)
188+
defer srvWG.Wait()
189+
defer os.Remove(addr)
190+
defer sock.Close()
191+
route := &router.Route{Adapter: "syslog+tcp", Address: addr}
192+
return NewSyslogAdapter(route)
193+
}
194+
120195
func startServer(n, la string, done chan<- string) (addr string, sock io.Closer, wg *sync.WaitGroup) {
121196
if n == "udp" || n == "tcp" {
122197
la = "127.0.0.1:0"

0 commit comments

Comments
 (0)