github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/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/Prakhar-Agarwal-byte/moby/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/Prakhar-Agarwal-byte/moby/daemon/logger"
    16  	"github.com/Prakhar-Agarwal-byte/moby/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  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	for k, v := range extraAttrs {
    79  		extra[k] = v
    80  	}
    81  
    82  	rawExtra, err := json.Marshal(extra)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	var gelfWriter gelf.Writer
    88  	if address.Scheme == "udp" {
    89  		gelfWriter, err = newGELFUDPWriter(address.Host, info)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  	} else if address.Scheme == "tcp" {
    94  		gelfWriter, err = newGELFTCPWriter(address.Host, info)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  	}
    99  
   100  	return &gelfLogger{
   101  		writer:   gelfWriter,
   102  		info:     info,
   103  		hostname: hostname,
   104  		rawExtra: rawExtra,
   105  	}, nil
   106  }
   107  
   108  // create new TCP gelfWriter
   109  func newGELFTCPWriter(address string, info logger.Info) (gelf.Writer, error) {
   110  	gelfWriter, err := gelf.NewTCPWriter(address)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err)
   113  	}
   114  
   115  	if v, ok := info.Config["gelf-tcp-max-reconnect"]; ok {
   116  		i, err := strconv.Atoi(v)
   117  		if err != nil || i < 0 {
   118  			return nil, fmt.Errorf("gelf-tcp-max-reconnect must be a positive integer")
   119  		}
   120  		gelfWriter.MaxReconnect = i
   121  	}
   122  
   123  	if v, ok := info.Config["gelf-tcp-reconnect-delay"]; ok {
   124  		i, err := strconv.Atoi(v)
   125  		if err != nil || i < 0 {
   126  			return nil, fmt.Errorf("gelf-tcp-reconnect-delay must be a positive integer")
   127  		}
   128  		gelfWriter.ReconnectDelay = time.Duration(i)
   129  	}
   130  
   131  	return gelfWriter, nil
   132  }
   133  
   134  // create new UDP gelfWriter
   135  func newGELFUDPWriter(address string, info logger.Info) (gelf.Writer, error) {
   136  	gelfWriter, err := gelf.NewUDPWriter(address)
   137  	if err != nil {
   138  		return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err)
   139  	}
   140  
   141  	if v, ok := info.Config["gelf-compression-type"]; ok {
   142  		switch v {
   143  		case "gzip":
   144  			gelfWriter.CompressionType = gelf.CompressGzip
   145  		case "zlib":
   146  			gelfWriter.CompressionType = gelf.CompressZlib
   147  		case "none":
   148  			gelfWriter.CompressionType = gelf.CompressNone
   149  		default:
   150  			return nil, fmt.Errorf("gelf: invalid compression type %q", v)
   151  		}
   152  	}
   153  
   154  	if v, ok := info.Config["gelf-compression-level"]; ok {
   155  		val, err := strconv.Atoi(v)
   156  		if err != nil {
   157  			return nil, fmt.Errorf("gelf: invalid compression level %s, err %v", v, err)
   158  		}
   159  		gelfWriter.CompressionLevel = val
   160  	}
   161  
   162  	return gelfWriter, nil
   163  }
   164  
   165  func (s *gelfLogger) Log(msg *logger.Message) error {
   166  	if len(msg.Line) == 0 {
   167  		return nil
   168  	}
   169  
   170  	level := gelf.LOG_INFO
   171  	if msg.Source == "stderr" {
   172  		level = gelf.LOG_ERR
   173  	}
   174  
   175  	m := gelf.Message{
   176  		Version:  "1.1",
   177  		Host:     s.hostname,
   178  		Short:    string(msg.Line),
   179  		TimeUnix: float64(msg.Timestamp.UnixNano()/int64(time.Millisecond)) / 1000.0,
   180  		Level:    int32(level),
   181  		RawExtra: s.rawExtra,
   182  	}
   183  	logger.PutMessage(msg)
   184  
   185  	if err := s.writer.WriteMessage(&m); err != nil {
   186  		return fmt.Errorf("gelf: cannot send GELF message: %v", err)
   187  	}
   188  	return nil
   189  }
   190  
   191  func (s *gelfLogger) Close() error {
   192  	return s.writer.Close()
   193  }
   194  
   195  func (s *gelfLogger) Name() string {
   196  	return name
   197  }
   198  
   199  // ValidateLogOpt looks for gelf specific log option gelf-address.
   200  func ValidateLogOpt(cfg map[string]string) error {
   201  	address, err := parseAddress(cfg["gelf-address"])
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	for key, val := range cfg {
   207  		switch key {
   208  		case "gelf-address":
   209  		case "tag":
   210  		case "labels":
   211  		case "labels-regex":
   212  		case "env":
   213  		case "env-regex":
   214  		case "gelf-compression-level":
   215  			if address.Scheme != "udp" {
   216  				return fmt.Errorf("compression is only supported on UDP")
   217  			}
   218  			i, err := strconv.Atoi(val)
   219  			if err != nil || i < flate.DefaultCompression || i > flate.BestCompression {
   220  				return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key)
   221  			}
   222  		case "gelf-compression-type":
   223  			if address.Scheme != "udp" {
   224  				return fmt.Errorf("compression is only supported on UDP")
   225  			}
   226  			switch val {
   227  			case "gzip", "zlib", "none":
   228  			default:
   229  				return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key)
   230  			}
   231  		case "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay":
   232  			if address.Scheme != "tcp" {
   233  				return fmt.Errorf("%q is only valid for TCP", key)
   234  			}
   235  			i, err := strconv.Atoi(val)
   236  			if err != nil || i < 0 {
   237  				return fmt.Errorf("%q must be a positive integer", key)
   238  			}
   239  		default:
   240  			return fmt.Errorf("unknown log opt %q for gelf log driver", key)
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func parseAddress(address string) (*url.URL, error) {
   248  	if address == "" {
   249  		return nil, fmt.Errorf("gelf-address is a required parameter")
   250  	}
   251  	addr, err := url.Parse(address)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	if addr.Scheme != "udp" && addr.Scheme != "tcp" {
   257  		return nil, fmt.Errorf("gelf: endpoint needs to be TCP or UDP")
   258  	}
   259  
   260  	if _, _, err = net.SplitHostPort(addr.Host); err != nil {
   261  		return nil, fmt.Errorf("gelf: please provide gelf-address as proto://host:port")
   262  	}
   263  
   264  	return addr, nil
   265  }