Merge pull request #15384 from phil-monroe/15058-include-name-in-syslog-tag

log driver - Interpolate fields into log tag
Upstream-commit: 4dfa996cc246691de5e6f509939b96e8643ac228
Component: engine
This commit is contained in:
Brian Goff
2015-09-16 23:33:34 -04:00
13 changed files with 292 additions and 110 deletions
@@ -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
}
@@ -2,10 +2,7 @@ package logger
import (
"fmt"
"os"
"strings"
"sync"
"time"
)
// Creator builds a logging driver instance with given context.
@@ -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
@@ -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"
)
@@ -23,12 +22,6 @@ type fluentd struct {
writer *fluent.Fluent
}
type receiver struct {
ID string
FullID string
Name string
}
const (
name = "fluentd"
defaultHostName = "localhost"
@@ -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") {
@@ -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
}
@@ -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)
}
+9 -1
View File
@@ -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"
)
@@ -64,6 +65,12 @@ 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,
@@ -71,7 +78,7 @@ func New(ctx logger.Context) (logger.Logger, error) {
imageID: ctx.ContainerImageID,
imageName: ctx.ContainerImageName,
command: ctx.Command(),
tag: ctx.Config["gelf-tag"],
tag: tag,
created: ctx.ContainerCreated,
}
@@ -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)
}
@@ -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
}
@@ -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)
}
}
@@ -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"
)
@@ -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"])
@@ -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)
}