├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gopkg.toml ├── LICENSE ├── README.md ├── gelf.go └── gelf_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | Gopkg.lock 2 | vendor/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.9.x 4 | - 1.10.x 5 | - tip 6 | env: 7 | - GOMAXPROCS=4 GORACE=halt_on_error=1 8 | install: 9 | - go get -u github.com/golang/dep/cmd/dep 10 | - go install github.com/golang/dep/cmd/dep 11 | - dep ensure 12 | script: 13 | - go test -race -v ./... 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - Caller line and file in log messages. 10 | 11 | ## 1.0.0 - 2018-04-05 12 | ### Added 13 | - Initial revision. 14 | 15 | 16 | [Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.0...HEAD 17 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | [[constraint]] 2 | name = "github.com/sirupsen/logrus" 3 | version = "~1.0" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fabien Meurillon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-logrus-formatters [![Build Status](https://travis-ci.org/FabienM/go-logrus-formatters.svg?branch=master)](https://travis-ci.org/FabienM/go-logrus-formatters) 2 | 3 | This repository contains a set of [logrus] formatters. 4 | 5 | ## Installation 6 | 7 | Since this project supports [semver], preferred way of installation is through [dep] or [gomodules]: 8 | 9 | ``` 10 | dep ensure -add github.com/fabienm/go-logrus-formatters 11 | ``` 12 | 13 | or 14 | 15 | ``` 16 | go get github.com/fabienm/go-logrus-formatters 17 | ``` 18 | 19 | ## GELF formatter 20 | 21 | The [GELF] formatter supports [1.1 payload specification](http://docs.graylog.org/en/2.4/pages/gelf.html#gelf-payload-specification). 22 | 23 | Notable features: 24 | 25 | * Logrus levels are converted to syslog levels 26 | * Logrus entries times are converted to UNIX timestamps. 27 | * Logrus entry fields are prefixed with `_`, excepted `version`, `host`, `short_message`, `full_message`, `timestamp` and `level`, allowing override. 28 | 29 | ### Syslog level mapping 30 | 31 | | Logrus | Syslog | 32 | |--------|-------------| 33 | | Panic | EMERG (0) | 34 | | Fatal | CRIT (2) | 35 | | Error | ERR (3) | 36 | | Warn | WARNING (4) | 37 | | Info | INFO (6) | 38 | | Debug | DEBUG (7) | 39 | 40 | ### Usage 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "os" 47 | 48 | "github.com/fabienm/go-logrus-formatters" 49 | log "github.com/sirupsen/logrus" 50 | ) 51 | 52 | func init() { 53 | hostname, _ := os.Hostname() 54 | // Log as GELF instead of the default ASCII formatter. 55 | log.SetFormatter(formatters.NewGelf(hostname)) 56 | } 57 | 58 | func main() { 59 | log.WithFields(log.Fields{ 60 | "animal": "walrus", 61 | "size": 10, 62 | }).Info("A group of walrus emerges from the ocean") 63 | log.WithFields(log.Fields{ 64 | "full_message": "Backtrace here\n\nmore stuff", 65 | "user_id": 9001, 66 | "some_info": "foo", 67 | "some_env_var": "bar", 68 | }).Fatal("A short message that helps you identify what is going on") 69 | } 70 | ``` 71 | 72 | Output: 73 | 74 | ```json 75 | {"_animal":"walrus","_level_name":"INFORMATIONAL","_size":10,"host":"mylaptop","level":6,"short_message":"A group of walrus emerges from the ocean","timestamp":1522937330.7570872,"version":"1.1"} 76 | {"_some_env_var":"bar","_some_info":"foo","_user_id":9001,"_level_name":"CRITICAL","full_message":"Backtrace here\n\nmore stuff","host":"mylaptop","level":2,"short_message":"A short message that helps you identify what is going on","timestamp":1522937330.7573297,"version":"1.1"} 77 | ``` 78 | 79 | ## See also 80 | 81 | * [GELF] 82 | * [logrus] 83 | * https://github.com/seatgeek/logrus-gelf-formatter 84 | * https://github.com/xild/go-gelf-formatter 85 | 86 | [dep]: https://golang.github.io/dep/ 87 | [gomodules]: https://github.com/golang/go/wiki/Modules 88 | [semver]: https://semver.org/ 89 | [logrus]: https://github.com/sirupsen/logrus 90 | [GELF]: http://docs.graylog.org/en/2.4/pages/gelf.html 91 | -------------------------------------------------------------------------------- /gelf.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "encoding/json" 5 | "log/syslog" 6 | "runtime" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | const ( 13 | // GelfVersion is the supported gelf version 14 | GelfVersion = "1.1" 15 | ) 16 | 17 | var ( 18 | levelMap map[logrus.Level]syslog.Priority 19 | syslogNameMap map[syslog.Priority]string 20 | 21 | protectedFields map[string]bool 22 | 23 | // DefaultLevel is the default syslog level to use if the logrus level does not map to a syslog level 24 | DefaultLevel = syslog.LOG_INFO 25 | ) 26 | 27 | func init() { 28 | levelMap = map[logrus.Level]syslog.Priority{ 29 | logrus.PanicLevel: syslog.LOG_EMERG, 30 | logrus.FatalLevel: syslog.LOG_CRIT, 31 | logrus.ErrorLevel: syslog.LOG_ERR, 32 | logrus.WarnLevel: syslog.LOG_WARNING, 33 | logrus.InfoLevel: syslog.LOG_INFO, 34 | logrus.DebugLevel: syslog.LOG_DEBUG, 35 | } 36 | syslogNameMap = map[syslog.Priority]string{ 37 | syslog.LOG_EMERG: "EMERGENCY", 38 | syslog.LOG_ALERT: "ALERT", 39 | syslog.LOG_CRIT: "CRITICAL", 40 | syslog.LOG_ERR: "ERROR", 41 | syslog.LOG_WARNING: "WARNING", 42 | syslog.LOG_NOTICE: "NOTICE", 43 | syslog.LOG_INFO: "INFORMATIONAL", 44 | syslog.LOG_DEBUG: "DEBUGGING", 45 | } 46 | protectedFields = map[string]bool{ 47 | "version": true, 48 | "host": true, 49 | "short_message": true, 50 | "full_message": true, 51 | "timestamp": true, 52 | "level": true, 53 | } 54 | } 55 | 56 | type gelfFormatter struct { 57 | hostname string 58 | } 59 | 60 | // NewGelf returns a new logrus / gelf-compliant formatter 61 | func NewGelf(hostname string) gelfFormatter { 62 | return gelfFormatter{hostname: hostname} 63 | } 64 | 65 | // Format implements logrus formatter 66 | func (f gelfFormatter) Format(entry *logrus.Entry) ([]byte, error) { 67 | level := toSyslogLevel(entry.Level) 68 | gelfEntry := map[string]interface{}{ 69 | "version": GelfVersion, 70 | "short_message": entry.Message, 71 | "level": level, 72 | "timestamp": toTimestamp(entry.Time), 73 | "host": f.hostname, 74 | "_level_name": syslogNameMap[level], 75 | } 76 | if _, file, line, ok := runtime.Caller(5); ok { 77 | gelfEntry["_file"] = file 78 | gelfEntry["_line"] = line 79 | } 80 | for key, value := range entry.Data { 81 | if !protectedFields[key] { 82 | key = "_" + key 83 | } 84 | gelfEntry[key] = value 85 | } 86 | message, err := json.Marshal(gelfEntry) 87 | return append(message, '\n'), err 88 | } 89 | 90 | func toTimestamp(t time.Time) float64 { 91 | nanosecond := float64(t.Nanosecond()) / 1e9 92 | seconds := float64(t.Unix()) 93 | return seconds + nanosecond 94 | } 95 | 96 | func toSyslogLevel(level logrus.Level) syslog.Priority { 97 | syslog, ok := levelMap[level] 98 | if ok { 99 | return syslog 100 | } 101 | return DefaultLevel 102 | } 103 | -------------------------------------------------------------------------------- /gelf_test.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log/syslog" 7 | "testing" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func TestGelfFormatter_Format(t *testing.T) { 13 | log := logrus.New() 14 | log.Formatter = NewGelf("testhost") 15 | buffer := new(bytes.Buffer) 16 | log.Out = buffer 17 | log.WithField("foo", "bar").Info("great test message") 18 | 19 | var message map[string]interface{} 20 | err := json.Unmarshal(buffer.Bytes(), &message) 21 | if err != nil { 22 | t.Error(err) 23 | } 24 | 25 | expectations := map[string]interface{}{ 26 | "host": "testhost", 27 | "level": float64(syslog.LOG_INFO), 28 | "short_message": "great test message", 29 | "version": GelfVersion, 30 | "_level_name": "INFORMATIONAL", 31 | "_foo": "bar", 32 | "_line": 17., 33 | } 34 | for key, expected := range expectations { 35 | if message[key] != expected { 36 | t.Errorf("invalid log object: expected value for key '%s' was '%s', found '%s'", key, expected, message[key]) 37 | } 38 | } 39 | } 40 | --------------------------------------------------------------------------------