Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more Go template functions #385

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,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 @@ -201,6 +201,15 @@ If you use multiline logging with raw, it's recommended to json encode the Data
* `MULTILINE_FLUSH_AFTER` - maximum time between the first and last lines of a multiline log entry in milliseconds (default: 500)
* `MULTILINE_SEPARATOR` - separator between lines for output (default: `\n`)

##### 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 @@ -286,7 +295,7 @@ docker stack deploy --compose-file <name of your compose file> STACK
```

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/

### TLS Settings
logspout supports modification of the client TLS settings via environment variables described below:
Expand Down
8 changes: 7 additions & 1 deletion adapters/syslog/syslog.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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 @@ -108,7 +114,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 @@ -13,6 +13,7 @@ import (
"testing"
"text/template"
"time"
"bytes"

_ "github.com/gliderlabs/logspout/transports/tcp"
_ "github.com/gliderlabs/logspout/transports/tls"
Expand Down Expand Up @@ -99,6 +100,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 @@ -109,6 +168,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