├── .gitignore ├── LICENSE ├── README.md └── gelf.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | *.swp 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Micah Hausler 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graylog GELF Module for Logspout 2 | This module allows Logspout to send Docker logs in the GELF format to Graylog via UDP. 3 | 4 | ## Build 5 | To build, you'll need to fork [Logspout](https://github.com/gliderlabs/logspout), add the following code to `modules.go` 6 | 7 | ``` 8 | _ "github.com/micahhausler/logspout-gelf" 9 | ``` 10 | and run `docker build -t $(whoami)/logspout:gelf` 11 | 12 | ## Run 13 | 14 | ``` 15 | docker run \ 16 | -v /var/run/docker.sock:/var/run/docker.sock \ 17 | -p 8000:80 \ 18 | micahhausler/logspout:gelf \ 19 | gelf://:12201 20 | 21 | ``` 22 | 23 | ## A note about GELF parameters 24 | The following docker container attributes are mapped to the corresponding GELF extra attributes. 25 | 26 | ``` 27 | { 28 | "_container_id": , 29 | "_container_name": , 30 | "_image_id": , 31 | "_image_name": , 32 | "_command": , 33 | "_created": , 34 | "_swarm_node": 35 | } 36 | ``` 37 | 38 | You can also add extra custom fields by adding labels to the containers. 39 | 40 | for example 41 | a container with label ```gelf_service=servicename``` will have the extra field service 42 | 43 | 44 | 45 | ## License 46 | MIT. See [License](LICENSE) 47 | -------------------------------------------------------------------------------- /gelf.go: -------------------------------------------------------------------------------- 1 | package gelf 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/Graylog2/go-gelf/gelf" 12 | "github.com/gliderlabs/logspout/router" 13 | ) 14 | 15 | var hostname string 16 | 17 | func init() { 18 | hostname, _ = os.Hostname() 19 | router.AdapterFactories.Register(NewGelfAdapter, "gelf") 20 | } 21 | 22 | // GelfAdapter is an adapter that streams UDP JSON to Graylog 23 | type GelfAdapter struct { 24 | writer *gelf.Writer 25 | route *router.Route 26 | } 27 | 28 | // NewGelfAdapter creates a GelfAdapter with UDP as the default transport. 29 | func NewGelfAdapter(route *router.Route) (router.LogAdapter, error) { 30 | _, found := router.AdapterTransports.Lookup(route.AdapterTransport("udp")) 31 | if !found { 32 | return nil, errors.New("unable to find adapter: " + route.Adapter) 33 | } 34 | 35 | gelfWriter, err := gelf.NewWriter(route.Address) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return &GelfAdapter{ 41 | route: route, 42 | writer: gelfWriter, 43 | }, nil 44 | } 45 | 46 | // Stream implements the router.LogAdapter interface. 47 | func (a *GelfAdapter) Stream(logstream chan *router.Message) { 48 | for message := range logstream { 49 | m := &GelfMessage{message} 50 | level := gelf.LOG_INFO 51 | if m.Source == "stderr" { 52 | level = gelf.LOG_ERR 53 | } 54 | extra, err := m.getExtraFields() 55 | if err != nil { 56 | log.Println("Graylog:", err) 57 | continue 58 | } 59 | 60 | msg := gelf.Message{ 61 | Version: "1.1", 62 | Host: hostname, 63 | Short: m.Message.Data, 64 | TimeUnix: float64(m.Message.Time.UnixNano()/int64(time.Millisecond)) / 1000.0, 65 | Level: level, 66 | RawExtra: extra, 67 | } 68 | // ContainerId: m.Container.ID, 69 | // ContainerImage: m.Container.Config.Image, 70 | // ContainerName: m.Container.Name, 71 | // } 72 | 73 | // here be message write. 74 | if err := a.writer.WriteMessage(&msg); err != nil { 75 | log.Println("Graylog:", err) 76 | continue 77 | } 78 | } 79 | } 80 | 81 | type GelfMessage struct { 82 | *router.Message 83 | } 84 | 85 | func (m GelfMessage) getExtraFields() (json.RawMessage, error) { 86 | 87 | extra := map[string]interface{}{ 88 | "_container_id": m.Container.ID, 89 | "_container_name": m.Container.Name[1:], // might be better to use strings.TrimLeft() to remove the first / 90 | "_image_id": m.Container.Image, 91 | "_image_name": m.Container.Config.Image, 92 | "_command": strings.Join(m.Container.Config.Cmd[:], " "), 93 | "_created": m.Container.Created, 94 | } 95 | for name, label := range m.Container.Config.Labels { 96 | if len(name) > 5 && strings.ToLower(name[0:5]) == "gelf_" { 97 | extra[name[4:]] = label 98 | } 99 | } 100 | swarmnode := m.Container.Node 101 | if swarmnode != nil { 102 | extra["_swarm_node"] = swarmnode.Name 103 | } 104 | 105 | rawExtra, err := json.Marshal(extra) 106 | if err != nil { 107 | return nil, err 108 | } 109 | return rawExtra, nil 110 | } 111 | --------------------------------------------------------------------------------