├── .gitmodules ├── .gitignore ├── vendor ├── modules.txt └── gopkg.in │ └── Graylog2 │ └── go-gelf.v2 │ ├── gelf │ ├── writer.go │ ├── utils.go │ ├── tcpwriter.go │ ├── reader.go │ ├── tcpreader.go │ ├── message.go │ └── udpwriter.go │ └── LICENSE ├── go.mod ├── go.sum ├── SystemdJournal2Gelf.service ├── LICENSE ├── README.md ├── SystemdJournal2Gelf.go └── SystemdJournal2Gelf_test.go /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | SystemdJournal2Gelf 2 | .idea/ 3 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0 2 | ## explicit 3 | gopkg.in/Graylog2/go-gelf.v2/gelf 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/parse-nl/SystemdJournal2Gelf 2 | 3 | go 1.14 4 | 5 | require gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0 h1:Xg23ydYYJLmb9AK3XdcEpplHZd1MpN3X2ZeeMoBClmY= 2 | gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0/go.mod h1:CeDeqW4tj9FrgZXF/dQCWZrBdcZWWBenhJtxLH4On2g= 3 | -------------------------------------------------------------------------------- /SystemdJournal2Gelf.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Forward systemd journal to a remote Graylog server 3 | Wants=network-online.target 4 | After=network-online.target 5 | 6 | [Service] 7 | ExecStart=/bin/SystemdJournal2Gelf localhost:12201 --follow 8 | Restart=on-failure 9 | RestartSec=5s 10 | RestartForceExitStatus=3 11 | 12 | DynamicUser=true 13 | Group=systemd-journal 14 | NoNewPrivileges=yes 15 | CapabilityBoundingSet= 16 | SystemCallArchitectures=native 17 | SystemCallFilter=@system-service 18 | 19 | PrivateDevices=yes 20 | PrivateUsers=yes 21 | 22 | ProtectSystem=strict 23 | ProtectClock=yes 24 | ProtectHome=true 25 | ProtectKernelLogs=yes 26 | ProtectKernelModules=yes 27 | 28 | RestrictAddressFamilies=AF_INET AF_INET6 29 | RestrictNamespaces=yes 30 | RestrictRealtime=yes 31 | RestrictSUIDSGID=yes 32 | 33 | CPUWeight=5000 34 | MemoryHigh=512M 35 | MemoryMax=1G 36 | 37 | [Install] 38 | WantedBy=multi-user.target 39 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/gelf/writer.go: -------------------------------------------------------------------------------- 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 | "net" 9 | ) 10 | 11 | type Writer interface { 12 | Close() error 13 | Write([]byte) (int, error) 14 | WriteMessage(*Message) error 15 | } 16 | 17 | // Writer implements io.Writer and is used to send both discrete 18 | // messages to a graylog2 server, or data from a stream-oriented 19 | // interface (like the functions in log). 20 | type GelfWriter struct { 21 | addr string 22 | conn net.Conn 23 | hostname string 24 | Facility string // defaults to current process name 25 | proto string 26 | } 27 | 28 | // Close connection and interrupt blocked Read or Write operations 29 | func (w *GelfWriter) Close() error { 30 | if w.conn == nil { 31 | return nil 32 | } 33 | return w.conn.Close() 34 | } 35 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 SocialCode 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/gelf/utils.go: -------------------------------------------------------------------------------- 1 | package gelf 2 | 3 | import ( 4 | "runtime" 5 | "strings" 6 | ) 7 | 8 | // getCaller returns the filename and the line info of a function 9 | // further down in the call stack. Passing 0 in as callDepth would 10 | // return info on the function calling getCallerIgnoringLog, 1 the 11 | // parent function, and so on. Any suffixes passed to getCaller are 12 | // path fragments like "/pkg/log/log.go", and functions in the call 13 | // stack from that file are ignored. 14 | func getCaller(callDepth int, suffixesToIgnore ...string) (file string, line int) { 15 | // bump by 1 to ignore the getCaller (this) stackframe 16 | callDepth++ 17 | outer: 18 | for { 19 | var ok bool 20 | _, file, line, ok = runtime.Caller(callDepth) 21 | if !ok { 22 | file = "???" 23 | line = 0 24 | break 25 | } 26 | 27 | for _, s := range suffixesToIgnore { 28 | if strings.HasSuffix(file, s) { 29 | callDepth++ 30 | continue outer 31 | } 32 | } 33 | break 34 | } 35 | return 36 | } 37 | 38 | func getCallerIgnoringLogMulti(callDepth int) (string, int) { 39 | // the +1 is to ignore this (getCallerIgnoringLogMulti) frame 40 | return getCaller(callDepth+1, "/pkg/log/log.go", "/pkg/io/multi.go") 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Parse Software Development B.V. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are 5 | permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of 8 | conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | of conditions and the following disclaimer in the documentation and/or other materials 12 | provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 15 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 16 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 21 | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SystemdJournal2Gelf 2 | =================== 3 | 4 | Export entries from systemd's journal and send them to a Graylog server using 5 | gelf. This script is written in Google go to make it easier to compile and 6 | distribute to your machines. 7 | 8 | Dependencies: 9 | ------------- 10 | 11 | - this repo includes https://github.com/DECK36/go-gelf 12 | - Google golang 13 | 14 | 15 | Install / Compile 16 | ----------------- 17 | 18 | Compile this package by checking out the repo and run: 19 | 20 | ``` 21 | go get github.com/parse-nl/SystemdJournal2Gelf 22 | ``` 23 | 24 | The binary will be compiled in $GOPATH/bin/SystemdJournal2Gelf 25 | 26 | Or install the package for: 27 | 28 | * [Archlinux](https://aur.archlinux.org/packages/systemdjournal2gelf/) 29 | 30 | Running as a service 31 | -------------------- 32 | 33 | Copy the included `SystemdJournal2Gelf.service` to `/etc/systemd/system`. 34 | 35 | Usage: 36 | ------ 37 | 38 | SystemdJournal2Gelf will connect to the server you specify as first argument 39 | and passes all other arguments to journalctl. It prepends these arguments with 40 | --output=json 41 | 42 | - Export only the kernel messages 43 | ``` 44 | SystemdJournal2Gelf localhost:11201 _TRANSPORT=kernel 45 | ``` 46 | 47 | - Perform initial import, reading entire journal 48 | ``` 49 | SystemdJournal2Gelf localhost:11201 --merge 50 | ``` 51 | 52 | - Monitor the journal 53 | ``` 54 | SystemdJournal2Gelf localhost:11201 --follow 55 | ``` 56 | 57 | Logging additional properties: 58 | ------------------------------ 59 | 60 | Letting this script decode json encoded properties in Messages has been removed, 61 | please see [this issue](https://github.com/parse-nl/SystemdJournal2Gelf/issues/10) 62 | that explains how to configure graylog to do that instead 63 | 64 | License 65 | ------- 66 | Copyright (c) 2016-2021, Parse Software Development B.V. 67 | 68 | Released under the Simplified BSD license, see LICENSE for details. 69 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/gelf/tcpwriter.go: -------------------------------------------------------------------------------- 1 | package gelf 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | const ( 12 | DefaultMaxReconnect = 3 13 | DefaultReconnectDelay = 1 14 | ) 15 | 16 | type TCPWriter struct { 17 | GelfWriter 18 | mu sync.Mutex 19 | MaxReconnect int 20 | ReconnectDelay time.Duration 21 | } 22 | 23 | func NewTCPWriter(addr string) (*TCPWriter, error) { 24 | var err error 25 | w := new(TCPWriter) 26 | w.MaxReconnect = DefaultMaxReconnect 27 | w.ReconnectDelay = DefaultReconnectDelay 28 | w.proto = "tcp" 29 | w.addr = addr 30 | 31 | if w.conn, err = net.Dial("tcp", addr); err != nil { 32 | return nil, err 33 | } 34 | if w.hostname, err = os.Hostname(); err != nil { 35 | return nil, err 36 | } 37 | 38 | return w, nil 39 | } 40 | 41 | // WriteMessage sends the specified message to the GELF server 42 | // specified in the call to New(). It assumes all the fields are 43 | // filled out appropriately. In general, clients will want to use 44 | // Write, rather than WriteMessage. 45 | func (w *TCPWriter) WriteMessage(m *Message) (err error) { 46 | buf := newBuffer() 47 | defer bufPool.Put(buf) 48 | messageBytes, err := m.toBytes(buf) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | messageBytes = append(messageBytes, 0) 54 | 55 | n, err := w.writeToSocketWithReconnectAttempts(messageBytes) 56 | if err != nil { 57 | return err 58 | } 59 | if n != len(messageBytes) { 60 | return fmt.Errorf("bad write (%d/%d)", n, len(messageBytes)) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func (w *TCPWriter) Write(p []byte) (n int, err error) { 67 | file, line := getCallerIgnoringLogMulti(1) 68 | 69 | m := constructMessage(p, w.hostname, w.Facility, file, line) 70 | 71 | if err = w.WriteMessage(m); err != nil { 72 | return 0, err 73 | } 74 | 75 | return len(p), nil 76 | } 77 | 78 | func (w *TCPWriter) writeToSocketWithReconnectAttempts(zBytes []byte) (n int, err error) { 79 | var errConn error 80 | var i int 81 | 82 | w.mu.Lock() 83 | for i = 0; i <= w.MaxReconnect; i++ { 84 | errConn = nil 85 | 86 | if w.conn != nil { 87 | n, err = w.conn.Write(zBytes) 88 | } else { 89 | err = fmt.Errorf("Connection was nil, will attempt reconnect") 90 | } 91 | if err != nil { 92 | time.Sleep(w.ReconnectDelay * time.Second) 93 | w.conn, errConn = net.Dial("tcp", w.addr) 94 | } else { 95 | break 96 | } 97 | } 98 | w.mu.Unlock() 99 | 100 | if i > w.MaxReconnect { 101 | return 0, fmt.Errorf("Maximum reconnection attempts was reached; giving up") 102 | } 103 | if errConn != nil { 104 | return 0, fmt.Errorf("Write Failed: %s\nReconnection failed: %s", err, errConn) 105 | } 106 | return n, nil 107 | } 108 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/gelf/reader.go: -------------------------------------------------------------------------------- 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/gzip" 10 | "compress/zlib" 11 | "encoding/json" 12 | "fmt" 13 | "io" 14 | "net" 15 | "strings" 16 | "sync" 17 | ) 18 | 19 | type Reader struct { 20 | mu sync.Mutex 21 | conn net.Conn 22 | } 23 | 24 | func NewReader(addr string) (*Reader, error) { 25 | var err error 26 | udpAddr, err := net.ResolveUDPAddr("udp", addr) 27 | if err != nil { 28 | return nil, fmt.Errorf("ResolveUDPAddr('%s'): %s", addr, err) 29 | } 30 | 31 | conn, err := net.ListenUDP("udp", udpAddr) 32 | if err != nil { 33 | return nil, fmt.Errorf("ListenUDP: %s", err) 34 | } 35 | 36 | r := new(Reader) 37 | r.conn = conn 38 | return r, nil 39 | } 40 | 41 | func (r *Reader) Addr() string { 42 | return r.conn.LocalAddr().String() 43 | } 44 | 45 | // FIXME: this will discard data if p isn't big enough to hold the 46 | // full message. 47 | func (r *Reader) Read(p []byte) (int, error) { 48 | msg, err := r.ReadMessage() 49 | if err != nil { 50 | return -1, err 51 | } 52 | 53 | var data string 54 | 55 | if msg.Full == "" { 56 | data = msg.Short 57 | } else { 58 | data = msg.Full 59 | } 60 | 61 | return strings.NewReader(data).Read(p) 62 | } 63 | 64 | func (r *Reader) ReadMessage() (*Message, error) { 65 | cBuf := make([]byte, ChunkSize) 66 | var ( 67 | err error 68 | n, length int 69 | cid, ocid []byte 70 | seq, total uint8 71 | cHead []byte 72 | cReader io.Reader 73 | chunks [][]byte 74 | ) 75 | 76 | for got := 0; got < 128 && (total == 0 || got < int(total)); got++ { 77 | if n, err = r.conn.Read(cBuf); err != nil { 78 | return nil, fmt.Errorf("Read: %s", err) 79 | } 80 | cHead, cBuf = cBuf[:2], cBuf[:n] 81 | 82 | if bytes.Equal(cHead, magicChunked) { 83 | //fmt.Printf("chunked %v\n", cBuf[:14]) 84 | cid, seq, total = cBuf[2:2+8], cBuf[2+8], cBuf[2+8+1] 85 | if ocid != nil && !bytes.Equal(cid, ocid) { 86 | return nil, fmt.Errorf("out-of-band message %v (awaited %v)", cid, ocid) 87 | } else if ocid == nil { 88 | ocid = cid 89 | chunks = make([][]byte, total) 90 | } 91 | n = len(cBuf) - chunkedHeaderLen 92 | //fmt.Printf("setting chunks[%d]: %d\n", seq, n) 93 | chunks[seq] = append(make([]byte, 0, n), cBuf[chunkedHeaderLen:]...) 94 | length += n 95 | } else { //not chunked 96 | if total > 0 { 97 | return nil, fmt.Errorf("out-of-band message (not chunked)") 98 | } 99 | break 100 | } 101 | } 102 | //fmt.Printf("\nchunks: %v\n", chunks) 103 | 104 | if length > 0 { 105 | if cap(cBuf) < length { 106 | cBuf = append(cBuf, make([]byte, 0, length-cap(cBuf))...) 107 | } 108 | cBuf = cBuf[:0] 109 | for i := range chunks { 110 | //fmt.Printf("appending %d %v\n", i, chunks[i]) 111 | cBuf = append(cBuf, chunks[i]...) 112 | } 113 | cHead = cBuf[:2] 114 | } 115 | 116 | // the data we get from the wire is compressed 117 | if bytes.Equal(cHead, magicGzip) { 118 | cReader, err = gzip.NewReader(bytes.NewReader(cBuf)) 119 | } else if cHead[0] == magicZlib[0] && 120 | (int(cHead[0])*256+int(cHead[1]))%31 == 0 { 121 | // zlib is slightly more complicated, but correct 122 | cReader, err = zlib.NewReader(bytes.NewReader(cBuf)) 123 | } else { 124 | // compliance with https://github.com/Graylog2/graylog2-server 125 | // treating all messages as uncompressed if they are not gzip, zlib or 126 | // chunked 127 | cReader = bytes.NewReader(cBuf) 128 | } 129 | 130 | if err != nil { 131 | return nil, fmt.Errorf("NewReader: %s", err) 132 | } 133 | 134 | msg := new(Message) 135 | if err := json.NewDecoder(cReader).Decode(&msg); err != nil { 136 | return nil, fmt.Errorf("json.Unmarshal: %s", err) 137 | } 138 | 139 | return msg, nil 140 | } 141 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/gelf/tcpreader.go: -------------------------------------------------------------------------------- 1 | package gelf 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "time" 9 | ) 10 | 11 | type TCPReader struct { 12 | listener *net.TCPListener 13 | conn net.Conn 14 | messages chan []byte 15 | } 16 | 17 | type connChannels struct { 18 | drop chan string 19 | confirm chan string 20 | } 21 | 22 | func newTCPReader(addr string) (*TCPReader, chan string, chan string, error) { 23 | var err error 24 | tcpAddr, err := net.ResolveTCPAddr("tcp", addr) 25 | if err != nil { 26 | return nil, nil, nil, fmt.Errorf("ResolveTCPAddr('%s'): %s", addr, err) 27 | } 28 | 29 | listener, err := net.ListenTCP("tcp", tcpAddr) 30 | if err != nil { 31 | return nil, nil, nil, fmt.Errorf("ListenTCP: %s", err) 32 | } 33 | 34 | r := &TCPReader{ 35 | listener: listener, 36 | messages: make(chan []byte, 100), // Make a buffered channel with at most 100 messages 37 | } 38 | 39 | closeSignal := make(chan string, 1) 40 | doneSignal := make(chan string, 1) 41 | 42 | go r.listenUntilCloseSignal(closeSignal, doneSignal) 43 | 44 | return r, closeSignal, doneSignal, nil 45 | } 46 | 47 | func (r *TCPReader) accepter(connections chan net.Conn) { 48 | for { 49 | conn, err := r.listener.Accept() 50 | if err != nil { 51 | break 52 | } 53 | connections <- conn 54 | } 55 | } 56 | 57 | func (r *TCPReader) listenUntilCloseSignal(closeSignal chan string, doneSignal chan string) { 58 | defer func() { doneSignal <- "done" }() 59 | defer r.listener.Close() 60 | var conns []connChannels 61 | connectionsChannel := make(chan net.Conn, 1) 62 | go r.accepter(connectionsChannel) 63 | for { 64 | select { 65 | case conn := <-connectionsChannel: 66 | dropSignal := make(chan string, 1) 67 | dropConfirm := make(chan string, 1) 68 | channels := connChannels{drop: dropSignal, confirm: dropConfirm} 69 | go handleConnection(conn, r.messages, dropSignal, dropConfirm) 70 | conns = append(conns, channels) 71 | default: 72 | } 73 | 74 | select { 75 | case sig := <-closeSignal: 76 | if sig == "stop" || sig == "drop" { 77 | if len(conns) >= 1 { 78 | for _, s := range conns { 79 | if s.drop != nil { 80 | s.drop <- "drop" 81 | <-s.confirm 82 | conns = append(conns[:0], conns[1:]...) 83 | } 84 | } 85 | if sig == "stop" { 86 | return 87 | } 88 | } else if sig == "stop" { 89 | closeSignal <- "stop" 90 | } 91 | if sig == "drop" { 92 | doneSignal <- "done" 93 | } 94 | } 95 | default: 96 | } 97 | } 98 | } 99 | 100 | func (r *TCPReader) addr() string { 101 | return r.listener.Addr().String() 102 | } 103 | 104 | func handleConnection(conn net.Conn, messages chan<- []byte, dropSignal chan string, dropConfirm chan string) { 105 | defer func() { dropConfirm <- "done" }() 106 | defer conn.Close() 107 | reader := bufio.NewReader(conn) 108 | 109 | var b []byte 110 | var err error 111 | drop := false 112 | canDrop := false 113 | 114 | for { 115 | conn.SetDeadline(time.Now().Add(2 * time.Second)) 116 | if b, err = reader.ReadBytes(0); err != nil { 117 | if drop { 118 | return 119 | } 120 | } else if len(b) > 0 { 121 | messages <- b 122 | canDrop = true 123 | if drop { 124 | return 125 | } 126 | } else if drop { 127 | return 128 | } 129 | select { 130 | case sig := <-dropSignal: 131 | if sig == "drop" { 132 | drop = true 133 | time.Sleep(1 * time.Second) 134 | if canDrop { 135 | return 136 | } 137 | } 138 | default: 139 | } 140 | } 141 | } 142 | 143 | func (r *TCPReader) readMessage() (*Message, error) { 144 | b := <-r.messages 145 | 146 | var msg Message 147 | if err := json.Unmarshal(b[:len(b)-1], &msg); err != nil { 148 | return nil, fmt.Errorf("json.Unmarshal: %s", err) 149 | } 150 | 151 | return &msg, nil 152 | } 153 | 154 | func (r *TCPReader) Close() { 155 | r.listener.Close() 156 | } 157 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/gelf/message.go: -------------------------------------------------------------------------------- 1 | package gelf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // Message represents the contents of the GELF message. It is gzipped 11 | // before sending. 12 | type Message struct { 13 | Version string `json:"version"` 14 | Host string `json:"host"` 15 | Short string `json:"short_message"` 16 | Full string `json:"full_message,omitempty"` 17 | TimeUnix float64 `json:"timestamp"` 18 | Level int32 `json:"level,omitempty"` 19 | Facility string `json:"facility,omitempty"` 20 | Extra map[string]interface{} `json:"-"` 21 | RawExtra json.RawMessage `json:"-"` 22 | } 23 | 24 | // Syslog severity levels 25 | const ( 26 | LOG_EMERG = 0 27 | LOG_ALERT = 1 28 | LOG_CRIT = 2 29 | LOG_ERR = 3 30 | LOG_WARNING = 4 31 | LOG_NOTICE = 5 32 | LOG_INFO = 6 33 | LOG_DEBUG = 7 34 | ) 35 | 36 | func (m *Message) MarshalJSONBuf(buf *bytes.Buffer) error { 37 | b, err := json.Marshal(m) 38 | if err != nil { 39 | return err 40 | } 41 | // write up until the final } 42 | if _, err = buf.Write(b[:len(b)-1]); err != nil { 43 | return err 44 | } 45 | if len(m.Extra) > 0 { 46 | eb, err := json.Marshal(m.Extra) 47 | if err != nil { 48 | return err 49 | } 50 | // merge serialized message + serialized extra map 51 | if err = buf.WriteByte(','); err != nil { 52 | return err 53 | } 54 | // write serialized extra bytes, without enclosing quotes 55 | if _, err = buf.Write(eb[1 : len(eb)-1]); err != nil { 56 | return err 57 | } 58 | } 59 | 60 | if len(m.RawExtra) > 0 { 61 | if err := buf.WriteByte(','); err != nil { 62 | return err 63 | } 64 | 65 | // write serialized extra bytes, without enclosing quotes 66 | if _, err = buf.Write(m.RawExtra[1 : len(m.RawExtra)-1]); err != nil { 67 | return err 68 | } 69 | } 70 | 71 | // write final closing quotes 72 | return buf.WriteByte('}') 73 | } 74 | 75 | func (m *Message) UnmarshalJSON(data []byte) error { 76 | i := make(map[string]interface{}, 16) 77 | if err := json.Unmarshal(data, &i); err != nil { 78 | return err 79 | } 80 | for k, v := range i { 81 | if k[0] == '_' { 82 | if m.Extra == nil { 83 | m.Extra = make(map[string]interface{}, 1) 84 | } 85 | m.Extra[k] = v 86 | continue 87 | } 88 | 89 | ok := true 90 | switch k { 91 | case "version": 92 | m.Version, ok = v.(string) 93 | case "host": 94 | m.Host, ok = v.(string) 95 | case "short_message": 96 | m.Short, ok = v.(string) 97 | case "full_message": 98 | m.Full, ok = v.(string) 99 | case "timestamp": 100 | m.TimeUnix, ok = v.(float64) 101 | case "level": 102 | var level float64 103 | level, ok = v.(float64) 104 | m.Level = int32(level) 105 | case "facility": 106 | m.Facility, ok = v.(string) 107 | } 108 | 109 | if !ok { 110 | return fmt.Errorf("invalid type for field %s", k) 111 | } 112 | } 113 | return nil 114 | } 115 | 116 | func (m *Message) toBytes(buf *bytes.Buffer) (messageBytes []byte, err error) { 117 | if err = m.MarshalJSONBuf(buf); err != nil { 118 | return nil, err 119 | } 120 | messageBytes = buf.Bytes() 121 | return messageBytes, nil 122 | } 123 | 124 | func constructMessage(p []byte, hostname string, facility string, file string, line int) (m *Message) { 125 | // remove trailing and leading whitespace 126 | p = bytes.TrimSpace(p) 127 | 128 | // If there are newlines in the message, use the first line 129 | // for the short message and set the full message to the 130 | // original input. If the input has no newlines, stick the 131 | // whole thing in Short. 132 | short := p 133 | full := []byte("") 134 | if i := bytes.IndexRune(p, '\n'); i > 0 { 135 | short = p[:i] 136 | full = p 137 | } 138 | 139 | m = &Message{ 140 | Version: "1.1", 141 | Host: hostname, 142 | Short: string(short), 143 | Full: string(full), 144 | TimeUnix: float64(time.Now().UnixNano()) / float64(time.Second), 145 | Level: 6, // info 146 | Facility: facility, 147 | Extra: map[string]interface{}{ 148 | "_file": file, 149 | "_line": line, 150 | }, 151 | } 152 | 153 | return m 154 | } 155 | -------------------------------------------------------------------------------- /SystemdJournal2Gelf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "gopkg.in/Graylog2/go-gelf.v2/gelf" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "regexp" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | /* 17 | http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html 18 | https://github.com/Graylog2/graylog2-docs/wiki/GELF 19 | */ 20 | type SystemdJournalEntry struct { 21 | Realtime_timestamp int64 `json:"__REALTIME_TIMESTAMP,string"` 22 | Boot_id string `json:"_BOOT_ID"` 23 | Priority int32 `json:"PRIORITY,string"` 24 | Syslog_identifier string `json:"SYSLOG_IDENTIFIER"` 25 | Message string `json:"MESSAGE"` 26 | Pid string `json:"_PID"` 27 | Uid string `json:"_UID"` 28 | Systemd_unit string `json:"_SYSTEMD_UNIT"` 29 | Hostname string `json:"_HOSTNAME"` 30 | FullMessage string `json:"-"` 31 | } 32 | 33 | // Strip date from message-content 34 | var startsWithTimestamp = regexp.MustCompile("^20[0-9][0-9][/\\-][01][0-9][/\\-][0123][0-9] [0-2]?[0-9]:[0-5][0-9]:[0-5][0-9][,0-9]{0,3} ") 35 | 36 | func (this *SystemdJournalEntry) toGelf() *gelf.Message { 37 | var extra = map[string]interface{}{ 38 | "Boot_id": this.Boot_id, 39 | "Pid": this.Pid, 40 | "Uid": this.Uid, 41 | "Systemd_unit": this.Systemd_unit, 42 | } 43 | 44 | if strings.Contains(this.Message, "\n") { 45 | this.FullMessage = this.Message 46 | this.Message = strings.Split(this.Message, "\n")[0] 47 | } 48 | 49 | return &gelf.Message{ 50 | Version: "1.1", 51 | Host: this.Hostname, 52 | Short: this.Message, 53 | Full: this.FullMessage, 54 | TimeUnix: float64(this.Realtime_timestamp) / 1000 / 1000, 55 | Level: this.Priority, 56 | Facility: this.Syslog_identifier, 57 | Extra: extra, 58 | } 59 | } 60 | 61 | // Custom wrapper to support unprintable chars in message 62 | func (this *SystemdJournalEntry) UnmarshalJSON(data []byte) error { 63 | // use an alias to prevent recursion 64 | type entryAlias SystemdJournalEntry 65 | aux := (*entryAlias)(this) 66 | 67 | if err := json.Unmarshal(data, &aux); err == nil { 68 | this.Message = startsWithTimestamp.ReplaceAllString(this.Message, "") 69 | 70 | return nil 71 | } else if ute, ok := err.(*json.UnmarshalTypeError); ok && ute.Field == "MESSAGE" && ute.Value == "array" { 72 | // Include brackets, which is why we subtract and add by one 73 | len := int64(strings.Index(string(data[ute.Offset:]), `]`)) + 1 74 | 75 | var message []byte 76 | if err := json.Unmarshal(data[ute.Offset-1:ute.Offset+len], &message); err != nil { 77 | return err 78 | } 79 | 80 | // only the failing field is skipped, so we can still use the rest 81 | this.Message = string(message) 82 | 83 | return nil 84 | } else { 85 | return err 86 | } 87 | } 88 | 89 | func (this *SystemdJournalEntry) send() { 90 | message := this.toGelf() 91 | 92 | for err := writer.WriteMessage(message); err != nil; err = writer.WriteMessage(message) { 93 | // UDP is nonblocking, but the OS stores an error which go will return on the next call. 94 | // This means we've already lost a message, but can keep retrying the current one. Sleep to make this less obtrusive 95 | fmt.Fprintln(os.Stderr, "send - processing paused because of: "+err.Error()) 96 | time.Sleep(SLEEP_AFTER_ERROR) 97 | } 98 | } 99 | 100 | type pendingEntry struct { 101 | sync.RWMutex 102 | entry *SystemdJournalEntry 103 | } 104 | 105 | func (this *pendingEntry) Push(next SystemdJournalEntry) { 106 | this.Lock() 107 | 108 | if this.entry != nil { 109 | this.entry.send() 110 | } 111 | 112 | this.entry = &next 113 | this.Unlock() 114 | } 115 | 116 | func (this *pendingEntry) Clear() { 117 | if this.entry == nil { 118 | return 119 | } 120 | 121 | this.Lock() 122 | entry := this.entry 123 | this.entry = nil 124 | this.Unlock() 125 | 126 | entry.send() 127 | } 128 | 129 | func (this *pendingEntry) ClearEvery(interval time.Duration) { 130 | for { 131 | time.Sleep(interval) 132 | this.Clear() 133 | } 134 | } 135 | 136 | var writer gelf.Writer 137 | 138 | const ( 139 | WRITE_INTERVAL = 50 * time.Millisecond 140 | SAMESOURCE_TIME_DIFFERENCE = 100 * 1000 141 | SLEEP_AFTER_ERROR = 15 * time.Second 142 | ) 143 | 144 | func main() { 145 | if len(os.Args) < 3 { 146 | fmt.Println("usage: SystemdJournal2Gelf SERVER:12201 [JOURNALCTL PARAMETERS]") 147 | os.Exit(1) 148 | } 149 | 150 | if w, err := gelf.NewUDPWriter(os.Args[1]); err != nil { 151 | panic("while connecting to Graylog server: " + err.Error()) 152 | } else { 153 | writer = w 154 | } 155 | 156 | journalArgs := []string{"--all", "--output=json"} 157 | journalArgs = append(journalArgs, os.Args[2:]...) 158 | cmd := exec.Command("journalctl", journalArgs...) 159 | 160 | stderr, _ := cmd.StderrPipe() 161 | stdout, _ := cmd.StdoutPipe() 162 | go io.Copy(os.Stderr, stderr) 163 | d := json.NewDecoder(stdout) 164 | 165 | var pending pendingEntry 166 | go pending.ClearEvery(WRITE_INTERVAL) 167 | cmd.Start() 168 | 169 | for { 170 | var entry SystemdJournalEntry 171 | if err := d.Decode(&entry); err != nil { 172 | if err == io.EOF { 173 | break 174 | } 175 | 176 | cmd.Process.Kill() 177 | panic("could not parse journal output: " + err.Error()) 178 | } 179 | 180 | pending.Push(entry) 181 | 182 | // Prevent saturation and throttling 183 | time.Sleep(1 * time.Millisecond) 184 | } 185 | 186 | cmd.Wait() 187 | 188 | pending.Clear() 189 | } 190 | -------------------------------------------------------------------------------- /SystemdJournal2Gelf_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestUnmarshalEntry(t *testing.T) { 9 | entry := SystemdJournalEntry{} 10 | 11 | err := json.Unmarshal([]byte(`{ 12 | "MESSAGE" : "Linux version 4.20.6-arch1-1-ARCH (builduser@heftig-32156) (gcc version 8.2.1 20181127 (GCC)) #1 SMP PREEMPT Thu Jan 31 08:22:01 UTC 2019", 13 | "PRIORITY" : "5", 14 | "__REALTIME_TIMESTAMP" : "1549067421724300", 15 | "_TRANSPORT" : "kernel", 16 | "SYSLOG_FACILITY" : "0", 17 | "SYSLOG_IDENTIFIER" : "kernel", 18 | "_HOSTNAME" : "machine.nl", 19 | "_BOOT_ID" : "61c0e40c739f4f009c785cef13b46e17", 20 | "_UID" : "99", 21 | "_PID" : "1234" 22 | }`), &entry) 23 | 24 | AssertNotError(t, err) 25 | 26 | gelf := entry.toGelf() 27 | AssertEquals(t, "machine.nl", gelf.Host) 28 | AssertEquals(t, "Linux version 4.20.6-arch1-1-ARCH (builduser@heftig-32156) (gcc version 8.2.1 20181127 (GCC)) #1 SMP PREEMPT Thu Jan 31 08:22:01 UTC 2019", gelf.Short) 29 | AssertEquals(t, "", gelf.Full) 30 | AssertEquals(t, float64(1549067421.7243001), gelf.TimeUnix) 31 | AssertEquals(t, int32(5), gelf.Level) 32 | AssertEquals(t, "kernel", gelf.Facility) 33 | 34 | AssertEquals(t, 4, len(gelf.Extra)) 35 | AssertEquals(t, "61c0e40c739f4f009c785cef13b46e17", gelf.Extra["Boot_id"]) 36 | AssertEquals(t, "99", gelf.Extra["Uid"]) 37 | AssertEquals(t, "1234", gelf.Extra["Pid"]) 38 | 39 | } 40 | 41 | func TestJsonMessageOverridesNormalProperties(t *testing.T) { 42 | entry := SystemdJournalEntry{} 43 | 44 | err := json.Unmarshal([]byte(`{ 45 | "_HOSTNAME" : "machine.nl", 46 | "MESSAGE" : "{\"Message\":\"actually something else\",\"FullMessage\":\"additional data\"}", 47 | "SYSLOG_IDENTIFIER" : "kernel" 48 | }`), &entry) 49 | 50 | AssertNotError(t, err) 51 | 52 | gelf := entry.toGelf() 53 | 54 | AssertEquals(t, "machine.nl", gelf.Host) 55 | AssertEquals(t, "actually something else", gelf.Short) 56 | AssertEquals(t, "additional data", gelf.Full) 57 | AssertEquals(t, "kernel", gelf.Facility) 58 | AssertEquals(t, 4, len(gelf.Extra)) 59 | } 60 | 61 | func TestJsonMessageIncludeDataInExtra(t *testing.T) { 62 | entry := SystemdJournalEntry{} 63 | 64 | err := json.Unmarshal([]byte(`{ 65 | "_HOSTNAME" : "machine.nl", 66 | "MESSAGE" : "{\"Message\":\"actually something else\",\"stuff\":\"things and stuff and more like that\"}", 67 | "SYSLOG_IDENTIFIER" : "kernel" 68 | }`), &entry) 69 | 70 | AssertNotError(t, err) 71 | 72 | gelf := entry.toGelf() 73 | 74 | AssertEquals(t, "machine.nl", gelf.Host) 75 | AssertEquals(t, "actually something else", gelf.Short) 76 | AssertEquals(t, "kernel", gelf.Facility) 77 | AssertEquals(t, 5, len(gelf.Extra)) 78 | AssertEquals(t, "things and stuff and more like that", gelf.Extra["stuff"]) 79 | } 80 | 81 | func TestUnmarshalUnprintableEntry(t *testing.T) { 82 | entry := SystemdJournalEntry{} 83 | 84 | err := json.Unmarshal([]byte(`{ 85 | "_HOSTNAME" : "machine.nl", 86 | "MESSAGE" : [ 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 98, 105, 110, 97, 114, 121, 32, 118, 97, 108, 117, 101, 32, 7 ], 87 | "SYSLOG_IDENTIFIER" : "kernel" 88 | }`), &entry) 89 | 90 | AssertNotError(t, err) 91 | 92 | gelf := entry.toGelf() 93 | AssertEquals(t, "this is a binary value \a", gelf.Short) 94 | 95 | AssertEquals(t, "kernel", gelf.Facility) 96 | AssertEquals(t, "machine.nl", gelf.Host) 97 | 98 | } 99 | 100 | func TestDateStrippedFromMessage(t *testing.T) { 101 | entry := SystemdJournalEntry{} 102 | 103 | err := json.Unmarshal([]byte(`{ 104 | "_HOSTNAME" : "machine.nl", 105 | "MESSAGE" : "2019-04-04 13:20:27 15024 [Warning] Aborted connection", 106 | "SYSLOG_IDENTIFIER" : "mysqld" 107 | }`), &entry) 108 | 109 | AssertNotError(t, err) 110 | 111 | gelf := entry.toGelf() 112 | 113 | AssertEquals(t, "machine.nl", gelf.Host) 114 | AssertEquals(t, "15024 [Warning] Aborted connection", gelf.Short) 115 | AssertEquals(t, "", gelf.Full) 116 | AssertEquals(t, "mysqld", gelf.Facility) 117 | AssertEquals(t, 4, len(gelf.Extra)) 118 | } 119 | 120 | func TestShortLinesDontTriggerPanic(t *testing.T) { 121 | entry := SystemdJournalEntry{} 122 | 123 | err := json.Unmarshal([]byte(`{ 124 | "MESSAGE" : "" 125 | }`), &entry) 126 | 127 | AssertNotError(t, err) 128 | 129 | gelf := entry.toGelf() 130 | 131 | AssertEquals(t, "", gelf.Short) 132 | } 133 | 134 | // asserts 135 | 136 | func AssertEquals(t *testing.T, expected, actual interface{}) { 137 | if expected != actual { 138 | t.Errorf("AssertEquals: %[1]T(%#[1]v) does not match %[2]T(%#[2]v))", actual, expected) 139 | } 140 | } 141 | 142 | func AssertNotEquals(t *testing.T, expected, actual interface{}) { 143 | if expected == actual { 144 | t.Errorf("AssertNotEquals: %[1]T(%#[1]v) unexpectedly matches %[2]T(%#[2]v)", actual, expected) 145 | } 146 | } 147 | 148 | func AssertError(t *testing.T, err interface{}) { 149 | _, ok := err.(error) 150 | 151 | if !ok { 152 | t.Errorf("AssertError: %[1]T(%#[1]v) is not an error", err) 153 | } 154 | } 155 | 156 | func AssertSpecificError(t *testing.T, err interface{}, specific error) { 157 | _, ok := err.(error) 158 | 159 | if !ok { 160 | t.Errorf("AssertError: %[1]T(%#[1]v) is not an error", err) 161 | } else if specific != nil && err != specific { 162 | t.Errorf("AssertError: %[1]T(%#[1]v) is not an %[1]T(%#[1]v)", err, specific) 163 | } 164 | } 165 | 166 | func AssertNotError(t *testing.T, err interface{}) { 167 | _, ok := err.(error) 168 | 169 | if ok { 170 | t.Errorf("AssertNotError: %#[1]v is unexpectedly an error", err) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /vendor/gopkg.in/Graylog2/go-gelf.v2/gelf/udpwriter.go: -------------------------------------------------------------------------------- 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 | "crypto/rand" 13 | "fmt" 14 | "io" 15 | "net" 16 | "os" 17 | "path" 18 | "sync" 19 | ) 20 | 21 | type UDPWriter struct { 22 | GelfWriter 23 | CompressionLevel int // one of the consts from compress/flate 24 | CompressionType CompressType 25 | } 26 | 27 | // What compression type the writer should use when sending messages 28 | // to the graylog2 server 29 | type CompressType int 30 | 31 | const ( 32 | CompressGzip CompressType = iota 33 | CompressZlib 34 | CompressNone 35 | ) 36 | 37 | // Used to control GELF chunking. Should be less than (MTU - len(UDP 38 | // header)). 39 | // 40 | // TODO: generate dynamically using Path MTU Discovery? 41 | const ( 42 | ChunkSize = 1420 43 | chunkedHeaderLen = 12 44 | chunkedDataLen = ChunkSize - chunkedHeaderLen 45 | ) 46 | 47 | var ( 48 | magicChunked = []byte{0x1e, 0x0f} 49 | magicZlib = []byte{0x78} 50 | magicGzip = []byte{0x1f, 0x8b} 51 | ) 52 | 53 | // numChunks returns the number of GELF chunks necessary to transmit 54 | // the given compressed buffer. 55 | func numChunks(b []byte) int { 56 | lenB := len(b) 57 | if lenB <= ChunkSize { 58 | return 1 59 | } 60 | return len(b)/chunkedDataLen + 1 61 | } 62 | 63 | // New returns a new GELF Writer. This writer can be used to send the 64 | // output of the standard Go log functions to a central GELF server by 65 | // passing it to log.SetOutput() 66 | func NewUDPWriter(addr string) (*UDPWriter, error) { 67 | var err error 68 | w := new(UDPWriter) 69 | w.CompressionLevel = flate.BestSpeed 70 | 71 | if w.conn, err = net.Dial("udp", addr); err != nil { 72 | return nil, err 73 | } 74 | if w.hostname, err = os.Hostname(); err != nil { 75 | return nil, err 76 | } 77 | 78 | w.Facility = path.Base(os.Args[0]) 79 | 80 | return w, nil 81 | } 82 | 83 | // writes the gzip compressed byte array to the connection as a series 84 | // of GELF chunked messages. The format is documented at 85 | // http://docs.graylog.org/en/2.1/pages/gelf.html as: 86 | // 87 | // 2-byte magic (0x1e 0x0f), 8 byte id, 1 byte sequence id, 1 byte 88 | // total, chunk-data 89 | func (w *GelfWriter) writeChunked(zBytes []byte) (err error) { 90 | b := make([]byte, 0, ChunkSize) 91 | buf := bytes.NewBuffer(b) 92 | nChunksI := numChunks(zBytes) 93 | if nChunksI > 128 { 94 | return fmt.Errorf("msg too large, would need %d chunks", nChunksI) 95 | } 96 | nChunks := uint8(nChunksI) 97 | // use urandom to get a unique message id 98 | msgId := make([]byte, 8) 99 | n, err := io.ReadFull(rand.Reader, msgId) 100 | if err != nil || n != 8 { 101 | return fmt.Errorf("rand.Reader: %d/%s", n, err) 102 | } 103 | 104 | bytesLeft := len(zBytes) 105 | for i := uint8(0); i < nChunks; i++ { 106 | buf.Reset() 107 | // manually write header. Don't care about 108 | // host/network byte order, because the spec only 109 | // deals in individual bytes. 110 | buf.Write(magicChunked) //magic 111 | buf.Write(msgId) 112 | buf.WriteByte(i) 113 | buf.WriteByte(nChunks) 114 | // slice out our chunk from zBytes 115 | chunkLen := chunkedDataLen 116 | if chunkLen > bytesLeft { 117 | chunkLen = bytesLeft 118 | } 119 | off := int(i) * chunkedDataLen 120 | chunk := zBytes[off : off+chunkLen] 121 | buf.Write(chunk) 122 | 123 | // write this chunk, and make sure the write was good 124 | n, err := w.conn.Write(buf.Bytes()) 125 | if err != nil { 126 | return fmt.Errorf("Write (chunk %d/%d): %s", i, 127 | nChunks, err) 128 | } 129 | if n != len(buf.Bytes()) { 130 | return fmt.Errorf("Write len: (chunk %d/%d) (%d/%d)", 131 | i, nChunks, n, len(buf.Bytes())) 132 | } 133 | 134 | bytesLeft -= chunkLen 135 | } 136 | 137 | if bytesLeft != 0 { 138 | return fmt.Errorf("error: %d bytes left after sending", bytesLeft) 139 | } 140 | return nil 141 | } 142 | 143 | // 1k bytes buffer by default 144 | var bufPool = sync.Pool{ 145 | New: func() interface{} { 146 | return bytes.NewBuffer(make([]byte, 0, 1024)) 147 | }, 148 | } 149 | 150 | func newBuffer() *bytes.Buffer { 151 | b := bufPool.Get().(*bytes.Buffer) 152 | if b != nil { 153 | b.Reset() 154 | return b 155 | } 156 | return bytes.NewBuffer(nil) 157 | } 158 | 159 | // WriteMessage sends the specified message to the GELF server 160 | // specified in the call to New(). It assumes all the fields are 161 | // filled out appropriately. In general, clients will want to use 162 | // Write, rather than WriteMessage. 163 | func (w *UDPWriter) WriteMessage(m *Message) (err error) { 164 | mBuf := newBuffer() 165 | defer bufPool.Put(mBuf) 166 | if err = m.MarshalJSONBuf(mBuf); err != nil { 167 | return err 168 | } 169 | mBytes := mBuf.Bytes() 170 | 171 | var ( 172 | zBuf *bytes.Buffer 173 | zBytes []byte 174 | ) 175 | 176 | var zw io.WriteCloser 177 | switch w.CompressionType { 178 | case CompressGzip: 179 | zBuf = newBuffer() 180 | defer bufPool.Put(zBuf) 181 | zw, err = gzip.NewWriterLevel(zBuf, w.CompressionLevel) 182 | case CompressZlib: 183 | zBuf = newBuffer() 184 | defer bufPool.Put(zBuf) 185 | zw, err = zlib.NewWriterLevel(zBuf, w.CompressionLevel) 186 | case CompressNone: 187 | zBytes = mBytes 188 | default: 189 | panic(fmt.Sprintf("unknown compression type %d", 190 | w.CompressionType)) 191 | } 192 | if zw != nil { 193 | if err != nil { 194 | return 195 | } 196 | if _, err = zw.Write(mBytes); err != nil { 197 | zw.Close() 198 | return 199 | } 200 | zw.Close() 201 | zBytes = zBuf.Bytes() 202 | } 203 | 204 | if numChunks(zBytes) > 1 { 205 | return w.writeChunked(zBytes) 206 | } 207 | n, err := w.conn.Write(zBytes) 208 | if err != nil { 209 | return 210 | } 211 | if n != len(zBytes) { 212 | return fmt.Errorf("bad write (%d/%d)", n, len(zBytes)) 213 | } 214 | 215 | return nil 216 | } 217 | 218 | // Write encodes the given string in a GELF message and sends it to 219 | // the server specified in New(). 220 | func (w *UDPWriter) Write(p []byte) (n int, err error) { 221 | // 1 for the function that called us. 222 | file, line := getCallerIgnoringLogMulti(1) 223 | 224 | m := constructMessage(p, w.hostname, w.Facility, file, line) 225 | 226 | if err = w.WriteMessage(m); err != nil { 227 | return 0, err 228 | } 229 | 230 | return len(p), nil 231 | } 232 | --------------------------------------------------------------------------------