github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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 // import "github.com/docker/docker/daemon/logger/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  	if len(msg.Line) == 0 {
   170  		return nil
   171  	}
   172  
   173  	level := gelf.LOG_INFO
   174  	if msg.Source == "stderr" {
   175  		level = gelf.LOG_ERR
   176  	}
   177  
   178  	m := gelf.Message{
   179  		Version:  "1.1",
   180  		Host:     s.hostname,
   181  		Short:    string(msg.Line),
   182  		TimeUnix: float64(msg.Timestamp.UnixNano()/int64(time.Millisecond)) / 1000.0,
   183  		Level:    int32(level),
   184  		RawExtra: s.rawExtra,
   185  	}
   186  	logger.PutMessage(msg)
   187  
   188  	if err := s.writer.WriteMessage(&m); err != nil {
   189  		return fmt.Errorf("gelf: cannot send GELF message: %v", err)
   190  	}
   191  	return nil
   192  }
   193  
   194  func (s *gelfLogger) Close() error {
   195  	return s.writer.Close()
   196  }
   197  
   198  func (s *gelfLogger) Name() string {
   199  	return name
   200  }
   201  
   202  // ValidateLogOpt looks for gelf specific log option gelf-address.
   203  func ValidateLogOpt(cfg map[string]string) error {
   204  	address, err := parseAddress(cfg["gelf-address"])
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	for key, val := range cfg {
   210  		switch key {
   211  		case "gelf-address":
   212  		case "tag":
   213  		case "labels":
   214  		case "labels-regex":
   215  		case "env":
   216  		case "env-regex":
   217  		case "gelf-compression-level":
   218  			if address.Scheme != "udp" {
   219  				return fmt.Errorf("compression is only supported on UDP")
   220  			}
   221  			i, err := strconv.Atoi(val)
   222  			if err != nil || i < flate.DefaultCompression || i > flate.BestCompression {
   223  				return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key)
   224  			}
   225  		case "gelf-compression-type":
   226  			if address.Scheme != "udp" {
   227  				return fmt.Errorf("compression is only supported on UDP")
   228  			}
   229  			switch val {
   230  			case "gzip", "zlib", "none":
   231  			default:
   232  				return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key)
   233  			}
   234  		case "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay":
   235  			if address.Scheme != "tcp" {
   236  				return fmt.Errorf("%q is only valid for TCP", key)
   237  			}
   238  			i, err := strconv.Atoi(val)
   239  			if err != nil || i < 0 {
   240  				return fmt.Errorf("%q must be a positive integer", key)
   241  			}
   242  		default:
   243  			return fmt.Errorf("unknown log opt %q for gelf log driver", key)
   244  		}
   245  	}
   246  
   247  	return nil
   248  }
   249  
   250  func parseAddress(address string) (*url.URL, error) {
   251  	if address == "" {
   252  		return nil, fmt.Errorf("gelf-address is a required parameter")
   253  	}
   254  	if !urlutil.IsTransportURL(address) {
   255  		return nil, fmt.Errorf("gelf-address should be in form proto://address, got %v", address)
   256  	}
   257  	url, err := url.Parse(address)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	// we support only udp
   263  	if url.Scheme != "udp" && url.Scheme != "tcp" {
   264  		return nil, fmt.Errorf("gelf: endpoint needs to be TCP or UDP")
   265  	}
   266  
   267  	// get host and port
   268  	if _, _, err = net.SplitHostPort(url.Host); err != nil {
   269  		return nil, fmt.Errorf("gelf: please provide gelf-address as proto://host:port")
   270  	}
   271  
   272  	return url, nil
   273  }