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