blob: 4e5a05ec325e6b7141a4b1721716af9b651ea3fc [file] [log] [blame]
package gelf
import (
"bytes"
"encoding/json"
"time"
)
// Message represents the contents of the GELF message. It is gzipped
// before sending.
type Message struct {
Version string `json:"version"`
Host string `json:"host"`
Short string `json:"short_message"`
Full string `json:"full_message,omitempty"`
TimeUnix float64 `json:"timestamp"`
Level int32 `json:"level,omitempty"`
Facility string `json:"facility,omitempty"`
Extra map[string]interface{} `json:"-"`
RawExtra json.RawMessage `json:"-"`
}
// Syslog severity levels
const (
LOG_EMERG = 0
LOG_ALERT = 1
LOG_CRIT = 2
LOG_ERR = 3
LOG_WARNING = 4
LOG_NOTICE = 5
LOG_INFO = 6
LOG_DEBUG = 7
)
func (m *Message) MarshalJSONBuf(buf *bytes.Buffer) error {
b, err := json.Marshal(m)
if err != nil {
return err
}
// write up until the final }
if _, err = buf.Write(b[:len(b)-1]); err != nil {
return err
}
if len(m.Extra) > 0 {
eb, err := json.Marshal(m.Extra)
if err != nil {
return err
}
// merge serialized message + serialized extra map
if err = buf.WriteByte(','); err != nil {
return err
}
// write serialized extra bytes, without enclosing quotes
if _, err = buf.Write(eb[1 : len(eb)-1]); err != nil {
return err
}
}
if len(m.RawExtra) > 0 {
if err := buf.WriteByte(','); err != nil {
return err
}
// write serialized extra bytes, without enclosing quotes
if _, err = buf.Write(m.RawExtra[1 : len(m.RawExtra)-1]); err != nil {
return err
}
}
// write final closing quotes
return buf.WriteByte('}')
}
func (m *Message) UnmarshalJSON(data []byte) error {
i := make(map[string]interface{}, 16)
if err := json.Unmarshal(data, &i); err != nil {
return err
}
for k, v := range i {
if k[0] == '_' {
if m.Extra == nil {
m.Extra = make(map[string]interface{}, 1)
}
m.Extra[k] = v
continue
}
switch k {
case "version":
m.Version = v.(string)
case "host":
m.Host = v.(string)
case "short_message":
m.Short = v.(string)
case "full_message":
m.Full = v.(string)
case "timestamp":
m.TimeUnix = v.(float64)
case "level":
m.Level = int32(v.(float64))
case "facility":
m.Facility = v.(string)
}
}
return nil
}
func (m *Message) toBytes() (messageBytes []byte, err error) {
buf := newBuffer()
defer bufPool.Put(buf)
if err = m.MarshalJSONBuf(buf); err != nil {
return nil, err
}
messageBytes = buf.Bytes()
return messageBytes, nil
}
func constructMessage(p []byte, hostname string, facility string, file string, line int) (m *Message) {
// remove trailing and leading whitespace
p = bytes.TrimSpace(p)
// If there are newlines in the message, use the first line
// for the short message and set the full message to the
// original input. If the input has no newlines, stick the
// whole thing in Short.
short := p
full := []byte("")
if i := bytes.IndexRune(p, '\n'); i > 0 {
short = p[:i]
full = p
}
m = &Message{
Version: "1.1",
Host: hostname,
Short: string(short),
Full: string(full),
TimeUnix: float64(time.Now().Unix()),
Level: 6, // info
Facility: facility,
Extra: map[string]interface{}{
"_file": file,
"_line": line,
},
}
return m
}