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 }