Skip to content

Commit

Permalink
Add experimental Docker json-file prospector (#5402)
Browse files Browse the repository at this point in the history
  • Loading branch information
exekias authored and ruflin committed Nov 16, 2017
1 parent 60e5905 commit 4b3f9e3
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ https://github.com/elastic/beats/compare/v6.0.0-beta2...master[Check the HEAD di
- Remove error log from runnerfactory as error is returned by API. {pull}5085[5085]
- Remove error log from runnerfactory as error is returned by API. {pull}5085[5085]
- Add Kubernetes manifests to deploy Filebeat. {pull}5349[5349]
- Add experimental Docker `json-file` prospector . {pull}5402[5402]

*Heartbeat*

Expand Down
6 changes: 6 additions & 0 deletions filebeat/_meta/fields.common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
description: >
The content of the line read from the log file.
- name: stream
type: keyword
required: false
description: >
Log stream when reading container logs, can be 'stdout' or 'stderr'
- name: prospector.type
required: true
description: >
Expand Down
10 changes: 10 additions & 0 deletions filebeat/docs/fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,16 @@ required: True
The content of the line read from the log file.
[float]
=== `stream`
type: keyword
required: False
Log stream when reading container logs, can be 'stdout' or 'stderr'
[float]
=== `prospector.type`
Expand Down
54 changes: 54 additions & 0 deletions filebeat/harvester/reader/docker_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package reader

import (
"bytes"
"encoding/json"
"time"

"github.com/elastic/beats/libbeat/common"

"github.com/pkg/errors"
)

// DockerJSON processor renames a given field
type DockerJSON struct {
reader Reader
}

type dockerLog struct {
Timestamp string `json:"time"`
Log string `json:"log"`
Stream string `json:"stream"`
}

// NewDockerJSON creates a new reader renaming a field
func NewDockerJSON(r Reader) *DockerJSON {
return &DockerJSON{reader: r}
}

// Next returns the next line.
func (p *DockerJSON) Next() (Message, error) {
message, err := p.reader.Next()
if err != nil {
return message, err
}

var line dockerLog
dec := json.NewDecoder(bytes.NewReader(message.Content))
if err = dec.Decode(&line); err != nil {
return message, errors.Wrap(err, "decoding docker JSON")
}

// Parse timestamp
ts, err := time.Parse(time.RFC3339, line.Timestamp)
if err != nil {
return message, errors.Wrap(err, "parsing docker timestamp")
}

message.AddFields(common.MapStr{
"stream": line.Stream,
})
message.Content = []byte(line.Log)
message.Ts = ts
return message, nil
}
60 changes: 60 additions & 0 deletions filebeat/harvester/reader/docker_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package reader

import (
"testing"
"time"

"github.com/elastic/beats/libbeat/common"

"github.com/stretchr/testify/assert"
)

func TestDockerJSON(t *testing.T) {
tests := []struct {
input []byte
expectedError bool
expectedMessage Message
}{
// Common log message
{
input: []byte(`{"log":"1:M 09 Nov 13:27:36.276 # User requested shutdown...\n","stream":"stdout","time":"2017-11-09T13:27:36.277747246Z"}`),
expectedMessage: Message{
Content: []byte("1:M 09 Nov 13:27:36.276 # User requested shutdown...\n"),
Fields: common.MapStr{"stream": "stdout"},
Ts: time.Date(2017, 11, 9, 13, 27, 36, 277747246, time.UTC),
},
},
// Wrong JSON
{
input: []byte(`this is not JSON`),
expectedError: true,
},
// Missing time
{
input: []byte(`{"log":"1:M 09 Nov 13:27:36.276 # User requested shutdown...\n","stream":"stdout"}`),
expectedError: true,
},
}

for _, test := range tests {
r := mockReader{message: test.input}
json := NewDockerJSON(r)
message, err := json.Next()

assert.Equal(t, test.expectedError, err != nil)

if !test.expectedError {
assert.EqualValues(t, test.expectedMessage, message)
}
}
}

type mockReader struct {
message []byte
}

func (m mockReader) Next() (Message, error) {
return Message{
Content: m.message,
}, nil
}
9 changes: 5 additions & 4 deletions filebeat/harvester/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import "github.com/elastic/beats/libbeat/common/match"

// Contains available prospector types
const (
LogType = "log"
StdinType = "stdin"
RedisType = "redis"
UdpType = "udp"
LogType = "log"
StdinType = "stdin"
RedisType = "redis"
UdpType = "udp"
DockerType = "docker"
)

// MatchAny checks if the text matches any of the regular expressions
Expand Down
1 change: 1 addition & 0 deletions filebeat/include/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package include

import (
// This list is automatically generated by `make imports`
_ "github.com/elastic/beats/filebeat/prospector/docker"
_ "github.com/elastic/beats/filebeat/prospector/log"
_ "github.com/elastic/beats/filebeat/prospector/redis"
_ "github.com/elastic/beats/filebeat/prospector/stdin"
Expand Down
17 changes: 17 additions & 0 deletions filebeat/prospector/docker/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package docker

var defaultConfig = config{
Containers: containers{
IDs: []string{},
Path: "/var/lib/docker/containers",
},
}

type config struct {
Containers containers `config:"containers"`
}

type containers struct {
IDs []string `config:"ids"`
Path string `config:"path"`
}
42 changes: 42 additions & 0 deletions filebeat/prospector/docker/prospector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package docker

import (
"path"

"github.com/elastic/beats/filebeat/channel"
"github.com/elastic/beats/filebeat/prospector"
"github.com/elastic/beats/filebeat/prospector/log"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/common/cfgwarn"

"github.com/pkg/errors"
)

func init() {
err := prospector.Register("docker", NewProspector)
if err != nil {
panic(err)
}
}

// NewProspector creates a new docker prospector
func NewProspector(cfg *common.Config, outletFactory channel.Factory, context prospector.Context) (prospector.Prospectorer, error) {
cfgwarn.Experimental("Docker prospector is enabled.")

config := defaultConfig
if err := cfg.Unpack(&config); err != nil {
return nil, errors.Wrap(err, "reading docker prospector config")
}

// Wrap log prospector with custom docker settings
if len(config.Containers.IDs) > 0 {
for idx, containerID := range config.Containers.IDs {
cfg.SetString("paths", idx, path.Join(config.Containers.Path, containerID, "*.log"))
}
}

if err := cfg.SetBool("docker-json", -1, true); err != nil {
return nil, errors.Wrap(err, "update prospector config")
}
return log.NewProspector(cfg, outletFactory, context)
}
3 changes: 3 additions & 0 deletions filebeat/prospector/log/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type config struct {
MaxBytes int `config:"max_bytes" validate:"min=0,nonzero"`
Multiline *reader.MultilineConfig `config:"multiline"`
JSON *reader.JSONConfig `config:"json"`

// Hidden on purpose, used by the docker prospector:
DockerJSON bool `config:"docker-json"`
}

type LogConfig struct {
Expand Down
7 changes: 7 additions & 0 deletions filebeat/prospector/log/harvester.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ func (h *Harvester) open() error {
return h.openStdin()
case harvester.LogType:
return h.openFile()
case harvester.DockerType:
return h.openFile()
default:
return fmt.Errorf("Invalid harvester type: %+v", h.config)
}
Expand Down Expand Up @@ -515,6 +517,11 @@ func (h *Harvester) newLogFileReader() (reader.Reader, error) {
return nil, err
}

if h.config.DockerJSON {
// Docker json-file format, add custom parsing to the pipeline
r = reader.NewDockerJSON(r)
}

if h.config.JSON != nil {
r = reader.NewJSON(r, h.config.JSON)
}
Expand Down

0 comments on commit 4b3f9e3

Please sign in to comment.