Skip to content

Commit

Permalink
log driver - add ability to interpolate container context into the lo…
Browse files Browse the repository at this point in the history
…g tag field

Signed-off-by: Philip Monroe <[email protected]>
  • Loading branch information
phil-monroe committed Sep 16, 2015
1 parent 5cbcbfc commit 3be7146
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 110 deletions.
72 changes: 72 additions & 0 deletions daemon/logger/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package logger

import (
"fmt"
"os"
"strings"
"time"
)

// Context provides enough information for a logging driver to do its function.
type Context struct {
Config map[string]string
ContainerID string
ContainerName string
ContainerEntrypoint string
ContainerArgs []string
ContainerImageID string
ContainerImageName string
ContainerCreated time.Time
LogPath string
}

// Hostname returns the hostname from the underlying OS.
func (ctx *Context) Hostname() (string, error) {
hostname, err := os.Hostname()
if err != nil {
return "", fmt.Errorf("logger: can not resolve hostname: %v", err)
}
return hostname, nil
}

// Command returns the command that the container being logged was
// started with. The Entrypoint is prepended to the container
// arguments.
func (ctx *Context) Command() string {
terms := []string{ctx.ContainerEntrypoint}
for _, arg := range ctx.ContainerArgs {
terms = append(terms, arg)
}
command := strings.Join(terms, " ")
return command
}

// ID Returns the Container ID shortened to 12 characters.
func (ctx *Context) ID() string {
return ctx.ContainerID[:12]
}

// FullID is an alias of ContainerID.
func (ctx *Context) FullID() string {
return ctx.ContainerID
}

// Name returns the ContainerName without a preceding '/'.
func (ctx *Context) Name() string {
return ctx.ContainerName[1:]
}

// ImageID returns the ContainerImageID shortened to 12 characters.
func (ctx *Context) ImageID() string {
return ctx.ContainerImageID[:12]
}

// ImageFullID is an alias of ContainerID.
func (ctx *Context) ImageFullID() string {
return ctx.ContainerImageID
}

// ImageName is an alias of ContainerImageName
func (ctx *Context) ImageName() string {
return ctx.ContainerImageName
}
37 changes: 0 additions & 37 deletions daemon/logger/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package logger

import (
"fmt"
"os"
"strings"
"sync"
"time"
)

// Creator builds a logging driver instance with given context.
Expand All @@ -15,40 +12,6 @@ type Creator func(Context) (Logger, error)
// logging implementation.
type LogOptValidator func(cfg map[string]string) error

// Context provides enough information for a logging driver to do its function.
type Context struct {
Config map[string]string
ContainerID string
ContainerName string
ContainerEntrypoint string
ContainerArgs []string
ContainerImageID string
ContainerImageName string
ContainerCreated time.Time
LogPath string
}

// Hostname returns the hostname from the underlying OS.
func (ctx *Context) Hostname() (string, error) {
hostname, err := os.Hostname()
if err != nil {
return "", fmt.Errorf("logger: can not resolve hostname: %v", err)
}
return hostname, nil
}

// Command returns the command that the container being logged was
// started with. The Entrypoint is prepended to the container
// arguments.
func (ctx *Context) Command() string {
terms := []string{ctx.ContainerEntrypoint}
for _, arg := range ctx.ContainerArgs {
terms = append(terms, arg)
}
command := strings.Join(terms, " ")
return command
}

type logdriverFactory struct {
registry map[string]Creator
optValidator map[string]LogOptValidator
Expand Down
33 changes: 7 additions & 26 deletions daemon/logger/fluentd/fluentd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
package fluentd

import (
"bytes"
"fmt"
"math"
"net"
"strconv"
"strings"
"text/template"

"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/logger/loggerutils"
"github.com/fluent/fluent-logger-golang/fluent"
)

Expand All @@ -23,12 +22,6 @@ type fluentd struct {
writer *fluent.Fluent
}

type receiver struct {
ID string
FullID string
Name string
}

const (
name = "fluentd"
defaultHostName = "localhost"
Expand All @@ -48,10 +41,14 @@ func init() {
func parseConfig(ctx logger.Context) (string, int, string, error) {
host := defaultHostName
port := defaultPort
tag := "docker." + ctx.ContainerID[:12]

config := ctx.Config

tag, err := loggerutils.ParseLogTag(ctx, "docker.{{.ID}}")
if err != nil {
return "", 0, "", err
}

if address := config["fluentd-address"]; address != "" {
if h, p, err := net.SplitHostPort(address); err != nil {
if !strings.Contains(err.Error(), "missing port in address") {
Expand All @@ -68,23 +65,6 @@ func parseConfig(ctx logger.Context) (string, int, string, error) {
}
}

if config["fluentd-tag"] != "" {
receiver := &receiver{
ID: ctx.ContainerID[:12],
FullID: ctx.ContainerID,
Name: ctx.ContainerName,
}
tmpl, err := template.New("tag").Parse(config["fluentd-tag"])
if err != nil {
return "", 0, "", err
}
buf := new(bytes.Buffer)
if err := tmpl.Execute(buf, receiver); err != nil {
return "", 0, "", err
}
tag = buf.String()
}

return host, port, tag, nil
}

Expand Down Expand Up @@ -130,6 +110,7 @@ func ValidateLogOpt(cfg map[string]string) error {
switch key {
case "fluentd-address":
case "fluentd-tag":
case "tag":
default:
return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key)
}
Expand Down
10 changes: 9 additions & 1 deletion daemon/logger/gelf/gelf.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/Graylog2/go-gelf/gelf"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/logger/loggerutils"
"github.com/docker/docker/pkg/urlutil"
)

Expand Down Expand Up @@ -64,14 +65,20 @@ func New(ctx logger.Context) (logger.Logger, error) {
// remove trailing slash from container name
containerName := bytes.TrimLeft([]byte(ctx.ContainerName), "/")

// parse log tag
tag, err := loggerutils.ParseLogTag(ctx, "")
if err != nil {
return nil, err
}

fields := gelfFields{
hostname: hostname,
containerID: ctx.ContainerID,
containerName: string(containerName),
imageID: ctx.ContainerImageID,
imageName: ctx.ContainerImageName,
command: ctx.Command(),
tag: ctx.Config["gelf-tag"],
tag: tag,
created: ctx.ContainerCreated,
}

Expand Down Expand Up @@ -135,6 +142,7 @@ func ValidateLogOpt(cfg map[string]string) error {
switch key {
case "gelf-address":
case "gelf-tag":
case "tag":
default:
return fmt.Errorf("unknown log opt '%s' for gelf log driver", key)
}
Expand Down
46 changes: 46 additions & 0 deletions daemon/logger/loggerutils/log_tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package loggerutils

import (
"bytes"
"fmt"
"text/template"

"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/logger"
)

// ParseLogTag generates a context aware tag for consistency across different
// log drivers based on the context of the running container.
func ParseLogTag(ctx logger.Context, defaultTemplate string) (string, error) {
tagTemplate := lookupTagTemplate(ctx, defaultTemplate)

tmpl, err := template.New("log-tag").Parse(tagTemplate)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
if err := tmpl.Execute(buf, &ctx); err != nil {
return "", err
}

return buf.String(), nil
}

func lookupTagTemplate(ctx logger.Context, defaultTemplate string) string {
tagTemplate := ctx.Config["tag"]

deprecatedConfigs := []string{"syslog-tag", "gelf-tag", "fluentd-tag"}
for i := 0; tagTemplate == "" && i < len(deprecatedConfigs); i++ {
cfg := deprecatedConfigs[i]
if ctx.Config[cfg] != "" {
tagTemplate = ctx.Config[cfg]
logrus.Warn(fmt.Sprintf("Using log tag from deprecated log-opt '%s'. Please use: --log-opt tag=\"%s\"", cfg, tagTemplate))
}
}

if tagTemplate == "" {
tagTemplate = defaultTemplate
}

return tagTemplate
}
58 changes: 58 additions & 0 deletions daemon/logger/loggerutils/log_tag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package loggerutils

import (
"testing"

"github.com/docker/docker/daemon/logger"
)

func TestParseLogTagDefaultTag(t *testing.T) {
ctx := buildContext(map[string]string{})
tag, e := ParseLogTag(ctx, "{{.ID}}")
assertTag(t, e, tag, ctx.ID())
}

func TestParseLogTag(t *testing.T) {
ctx := buildContext(map[string]string{"tag": "{{.ImageName}}/{{.Name}}/{{.ID}}"})
tag, e := ParseLogTag(ctx, "{{.ID}}")
assertTag(t, e, tag, "test-image/test-container/container-ab")
}

func TestParseLogTagSyslogTag(t *testing.T) {
ctx := buildContext(map[string]string{"syslog-tag": "{{.ImageName}}/{{.Name}}/{{.ID}}"})
tag, e := ParseLogTag(ctx, "{{.ID}}")
assertTag(t, e, tag, "test-image/test-container/container-ab")
}

func TestParseLogTagGelfTag(t *testing.T) {
ctx := buildContext(map[string]string{"gelf-tag": "{{.ImageName}}/{{.Name}}/{{.ID}}"})
tag, e := ParseLogTag(ctx, "{{.ID}}")
assertTag(t, e, tag, "test-image/test-container/container-ab")
}

func TestParseLogTagFluentdTag(t *testing.T) {
ctx := buildContext(map[string]string{"fluentd-tag": "{{.ImageName}}/{{.Name}}/{{.ID}}"})
tag, e := ParseLogTag(ctx, "{{.ID}}")
assertTag(t, e, tag, "test-image/test-container/container-ab")
}

// Helpers

func buildContext(cfg map[string]string) logger.Context {
return logger.Context{
ContainerID: "container-abcdefghijklmnopqrstuvwxyz01234567890",
ContainerName: "/test-container",
ContainerImageID: "image-abcdefghijklmnopqrstuvwxyz01234567890",
ContainerImageName: "test-image",
Config: cfg,
}
}

func assertTag(t *testing.T, e error, tag string, expected string) {
if e != nil {
t.Fatalf("Error generating tag: %q", e)
}
if tag != expected {
t.Fatalf("Wrong tag: %q, should be %q", tag, expected)
}
}
8 changes: 5 additions & 3 deletions daemon/logger/syslog/syslog.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/logger/loggerutils"
"github.com/docker/docker/pkg/urlutil"
)

Expand Down Expand Up @@ -61,9 +62,9 @@ func init() {
// the context. Supported context configuration variables are
// syslog-address, syslog-facility, & syslog-tag.
func New(ctx logger.Context) (logger.Logger, error) {
tag := ctx.Config["syslog-tag"]
if tag == "" {
tag = ctx.ContainerID[:12]
tag, err := loggerutils.ParseLogTag(ctx, "{{.ID}}")
if err != nil {
return nil, err
}

proto, address, err := parseAddress(ctx.Config["syslog-address"])
Expand Down Expand Up @@ -146,6 +147,7 @@ func ValidateLogOpt(cfg map[string]string) error {
case "syslog-address":
case "syslog-facility":
case "syslog-tag":
case "tag":
default:
return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
}
Expand Down
Loading

0 comments on commit 3be7146

Please sign in to comment.