github.com/LazyboyChen7/engine@v17.12.1-ce-rc2+incompatible/daemon/logger/gelf/gelf.go (about)

     1  // Package gelf provides the log driver for forwarding server logs to
     2  // endpoints that support the Graylog Extended Log Format.
     3  package gelf
     4  
     5  import (
     6  	"compress/flate"
     7  	"encoding/json"
     8  	"fmt"
     9  	"net"
    10  	"net/url"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/Graylog2/go-gelf/gelf"
    15  	"github.com/docker/docker/daemon/logger"
    16  	"github.com/docker/docker/daemon/logger/loggerutils"
    17  	"github.com/docker/docker/pkg/urlutil"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  const name = "gelf"
    22  
    23  type gelfLogger struct {
    24  	writer   gelf.Writer
    25  	info     logger.Info
    26  	hostname string
    27  	rawExtra json.RawMessage
    28  }
    29  
    30  func init() {
    31  	if err := logger.RegisterLogDriver(name, New); err != nil {
    32  		logrus.Fatal(err)
    33  	}
    34  	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
    35  		logrus.Fatal(err)
    36  	}
    37  }
    38  
    39  // New creates a gelf logger using the configuration passed in on the
    40  // context. The supported context configuration variable is gelf-address.
    41  func New(info logger.Info) (logger.Logger, error) {
    42  	// parse gelf address
    43  	address, err := parseAddress(info.Config["gelf-address"])
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	// collect extra data for GELF message
    49  	hostname, err := info.Hostname()
    50  	if err != nil {
    51  		return nil, fmt.Errorf("gelf: cannot access hostname to set source field")
    52  	}
    53  
    54  	// parse log tag
    55  	tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	extra := map[string]interface{}{
    61  		"_container_id":   info.ContainerID,
    62  		"_container_name": info.Name(),
    63  		"_image_id":       info.ContainerImageID,
    64  		"_image_name":     info.ContainerImageName,
    65  		"_command":        info.Command(),
    66  		"_tag":            tag,
    67  		"_created":        info.ContainerCreated,
    68  	}
    69  
    70  	extraAttrs, err := info.ExtraAttributes(func(key string) string {
    71  		if key[0] == '_' {
    72  			return key
    73  		}
    74  		return "_" + key
    75  	})
    76  
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	for k, v := range extraAttrs {
    82  		extra[k] = v
    83  	}
    84  
    85  	rawExtra, err := json.Marshal(extra)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	var gelfWriter gelf.Writer
    91  	if address.Scheme == "udp" {
    92  		gelfWriter, err = newGELFUDPWriter(address.Host, info)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  	} else if address.Scheme == "tcp" {
    97  		gelfWriter, err = newGELFTCPWriter(address.Host, info)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  
   103  	return &gelfLogger{
   104  		writer:   gelfWriter,
   105  		info:     info,
   106  		hostname: hostname,
   107  		rawExtra: rawExtra,
   108  	}, nil
   109  }
   110  
   111  // create new TCP gelfWriter
   112  func newGELFTCPWriter(address string, info logger.Info) (gelf.Writer, error) {
   113  	gelfWriter, err := gelf.NewTCPWriter(address)
   114  	if err != nil {
   115  		return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err)
   116  	}
   117  
   118  	if v, ok := info.Config["gelf-tcp-max-reconnect"]; ok {
   119  		i, err := strconv.Atoi(v)
   120  		if err != nil || i < 0 {
   121  			return nil, fmt.Errorf("gelf-tcp-max-reconnect must be a positive integer")
   122  		}
   123  		gelfWriter.MaxReconnect = i
   124  	}
   125  
   126  	if v, ok := info.Config["gelf-tcp-reconnect-delay"]; ok {
   127  		i, err := strconv.Atoi(v)
   128  		if err != nil || i < 0 {
   129  			return nil, fmt.Errorf("gelf-tcp-reconnect-delay must be a positive integer")
   130  		}
   131  		gelfWriter.ReconnectDelay = time.Duration(i)
   132  	}
   133  
   134  	return gelfWriter, nil
   135  }
   136  
   137  // create new UDP gelfWriter
   138  func newGELFUDPWriter(address string, info logger.Info) (gelf.Writer, error) {
   139  	gelfWriter, err := gelf.NewUDPWriter(address)
   140  	if err != nil {
   141  		return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err)
   142  	}
   143  
   144  	if v, ok := info.Config["gelf-compression-type"]; ok {
   145  		switch v {
   146  		case "gzip":
   147  			gelfWriter.CompressionType = gelf.CompressGzip
   148  		case "zlib":
   149  			gelfWriter.CompressionType = gelf.CompressZlib
   150  		case "none":
   151  			gelfWriter.CompressionType = gelf.CompressNone
   152  		default:
   153  			return nil, fmt.Errorf("gelf: invalid compression type %q", v)
   154  		}
   155  	}
   156  
   157  	if v, ok := info.Config["gelf-compression-level"]; ok {
   158  		val, err := strconv.Atoi(v)
   159  		if err != nil {
   160  			return nil, fmt.Errorf("gelf: invalid compression level %s, err %v", v, err)
   161  		}
   162  		gelfWriter.CompressionLevel = val
   163  	}
   164  
   165  	return gelfWriter, nil
   166  }
   167  
   168  func (s *gelfLogger) Log(msg *logger.Message) error {
   169  	level := gelf.LOG_INFO
   170  	if msg.Source == "stderr" {
   171  		level = gelf.LOG_ERR
   172  	}
   173  
   174  	m := gelf.Message{
   175  		Version:  "1.1",
   176  		Host:     s.hostname,
   177  		Short:    string(msg.Line),
   178  		TimeUnix: float64(msg.Timestamp.UnixNano()/int64(time.Millisecond)) / 1000.0,
   179  		Level:    int32(level),
   180  		RawExtra: s.rawExtra,
   181  	}
   182  	logger.PutMessage(msg)
   183  
   184  	if err := s.writer.WriteMessage(&m); err != nil {
   185  		return fmt.Errorf("gelf: cannot send GELF message: %v", err)
   186  	}
   187  	return nil
   188  }
   189  
   190  func (s *gelfLogger) Close() error {
   191  	return s.writer.Close()
   192  }
   193  
   194  func (s *gelfLogger) Name() string {
   195  	return name
   196  }
   197  
   198  // ValidateLogOpt looks for gelf specific log option gelf-address.
   199  func ValidateLogOpt(cfg map[string]string) error {
   200  	address, err := parseAddress(cfg["gelf-address"])
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	for key, val := range cfg {
   206  		switch key {
   207  		case "gelf-address":
   208  		case "tag":
   209  		case "labels":
   210  		case "env":
   211  		case "env-regex":
   212  		case "gelf-compression-level":
   213  			if address.Scheme != "udp" {
   214  				return fmt.Errorf("compression is only supported on UDP")
   215  			}
   216  			i, err := strconv.Atoi(val)
   217  			if err != nil || i < flate.DefaultCompression || i > flate.BestCompression {
   218  				return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key)
   219  			}
   220  		case "gelf-compression-type":
   221  			if address.Scheme != "udp" {
   222  				return fmt.Errorf("compression is only supported on UDP")
   223  			}
   224  			switch val {
   225  			case "gzip", "zlib", "none":
   226  			default:
   227  				return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key)
   228  			}
   229  		case "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay":
   230  			if address.Scheme != "tcp" {
   231  				return fmt.Errorf("%q is only valid for TCP", key)
   232  			}
   233  			i, err := strconv.Atoi(val)
   234  			if err != nil || i < 0 {
   235  				return fmt.Errorf("%q must be a positive integer", key)
   236  			}
   237  		default:
   238  			return fmt.Errorf("unknown log opt %q for gelf log driver", key)
   239  		}
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  func parseAddress(address string) (*url.URL, error) {
   246  	if address == "" {
   247  		return nil, fmt.Errorf("gelf-address is a required parameter")
   248  	}
   249  	if !urlutil.IsTransportURL(address) {
   250  		return nil, fmt.Errorf("gelf-address should be in form proto://address, got %v", address)
   251  	}
   252  	url, err := url.Parse(address)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	// we support only udp
   258  	if url.Scheme != "udp" && url.Scheme != "tcp" {
   259  		return nil, fmt.Errorf("gelf: endpoint needs to be TCP or UDP")
   260  	}
   261  
   262  	// get host and port
   263  	if _, _, err = net.SplitHostPort(url.Host); err != nil {
   264  		return nil, fmt.Errorf("gelf: please provide gelf-address as proto://host:port")
   265  	}
   266  
   267  	return url, nil
   268  }