github.com/sujit-baniya/log@v1.0.73/gelf/udpwriter.go (about)

     1  // Copyright 2012 SocialCode. All rights reserved.
     2  // Use of this source code is governed by the MIT
     3  // license that can be found in the LICENSE file.
     4  
     5  package gelf
     6  
     7  import (
     8  	"bytes"
     9  	"compress/flate"
    10  	"compress/gzip"
    11  	"compress/zlib"
    12  	"fmt"
    13  	"io"
    14  	"net"
    15  	"os"
    16  	"path"
    17  	"sync"
    18  )
    19  
    20  type UDPWriter struct {
    21  	Writer
    22  	CompressionLevel int // one of the consts from compress/flate
    23  	CompressionType  CompressType
    24  }
    25  
    26  // What compression type the writer should use when sending messages
    27  // to the graylog2 server
    28  type CompressType int
    29  
    30  const (
    31  	CompressGzip CompressType = iota
    32  	CompressZlib
    33  	CompressNone
    34  )
    35  
    36  // Used to control GELF chunking.  Should be less than (MTU - len(UDP
    37  // header)).
    38  //
    39  // TODO: generate dynamically using Path MTU Discovery?
    40  const (
    41  	ChunkSize        = 1420
    42  	chunkedHeaderLen = 12
    43  	chunkedDataLen   = ChunkSize - chunkedHeaderLen
    44  )
    45  
    46  var (
    47  	magicChunked = []byte{0x1e, 0x0f}
    48  	magicZlib    = []byte{0x78}
    49  	magicGzip    = []byte{0x1f, 0x8b}
    50  )
    51  
    52  // numChunks returns the number of GELF chunks necessary to transmit
    53  // the given compressed buffer.
    54  func numChunks(b []byte) int {
    55  	lenB := len(b)
    56  	if lenB <= ChunkSize {
    57  		return 1
    58  	}
    59  	return len(b)/chunkedDataLen + 1
    60  }
    61  
    62  // New returns a new GELF Writer.  This writer can be used to send the
    63  // output of the standard Go log functions to a central GELF server by
    64  // passing it to log.SetOutput()
    65  func NewUDPWriter(addr string) (*UDPWriter, error) {
    66  	var err error
    67  	w := new(UDPWriter)
    68  	w.CompressionLevel = flate.BestSpeed
    69  
    70  	if w.conn, err = net.Dial("udp", addr); err != nil {
    71  		return nil, err
    72  	}
    73  	if w.hostname, err = os.Hostname(); err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	w.Facility = path.Base(os.Args[0])
    78  
    79  	return w, nil
    80  }
    81  
    82  // 1k bytes buffer by default
    83  var bufPool = sync.Pool{
    84  	New: func() interface{} {
    85  		return bytes.NewBuffer(make([]byte, 0, 1024))
    86  	},
    87  }
    88  
    89  func newBuffer() *bytes.Buffer {
    90  	b := bufPool.Get().(*bytes.Buffer)
    91  	if b != nil {
    92  		b.Reset()
    93  		return b
    94  	}
    95  	return bytes.NewBuffer(nil)
    96  }
    97  
    98  // WriteMessage sends the specified message to the GELF server
    99  // specified in the call to New().  It assumes all the fields are
   100  // filled out appropriately.  In general, clients will want to use
   101  // Write, rather than WriteMessage.
   102  func (w *UDPWriter) WriteMessage(m *Message) (err error) {
   103  	mBuf := newBuffer()
   104  	defer bufPool.Put(mBuf)
   105  	if err = m.MarshalJSONBuf(mBuf); err != nil {
   106  		return err
   107  	}
   108  	mBytes := mBuf.Bytes()
   109  
   110  	var (
   111  		zBuf   *bytes.Buffer
   112  		zBytes []byte
   113  	)
   114  
   115  	var zw io.WriteCloser
   116  	switch w.CompressionType {
   117  	case CompressGzip:
   118  		zBuf = newBuffer()
   119  		defer bufPool.Put(zBuf)
   120  		zw, err = gzip.NewWriterLevel(zBuf, w.CompressionLevel)
   121  	case CompressZlib:
   122  		zBuf = newBuffer()
   123  		defer bufPool.Put(zBuf)
   124  		zw, err = zlib.NewWriterLevel(zBuf, w.CompressionLevel)
   125  	case CompressNone:
   126  		zBytes = mBytes
   127  	default:
   128  		panic(fmt.Sprintf("unknown compression type %d",
   129  			w.CompressionType))
   130  	}
   131  	if zw != nil {
   132  		if err != nil {
   133  			return
   134  		}
   135  		if _, err = zw.Write(mBytes); err != nil {
   136  			zw.Close()
   137  			return
   138  		}
   139  		zw.Close()
   140  		zBytes = zBuf.Bytes()
   141  	}
   142  
   143  	if numChunks(zBytes) > 1 {
   144  		return w.writeChunked(zBytes)
   145  	}
   146  	n, err := w.conn.Write(zBytes)
   147  	if err != nil {
   148  		return
   149  	}
   150  	if n != len(zBytes) {
   151  		return fmt.Errorf("bad write (%d/%d)", n, len(zBytes))
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  // Write encodes the given string in a GELF message and sends it to
   158  // the server specified in New().
   159  func (w *UDPWriter) Write(p []byte) (n int, err error) {
   160  	// 1 for the function that called us.
   161  	file, line := getCallerIgnoringLogMulti(1)
   162  
   163  	m := constructMessage(p, w.hostname, w.Facility, file, line)
   164  	if err = w.WriteMessage(m); err != nil {
   165  		return 0, err
   166  	}
   167  
   168  	return len(p), nil
   169  }