youtubebeat/vendor/github.com/elastic/beats/filebeat/input/syslog/input.go

264 lines
5.6 KiB
Go
Raw Normal View History

2018-11-18 11:08:38 +01:00
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package syslog
import (
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/elastic/beats/filebeat/channel"
"github.com/elastic/beats/filebeat/harvester"
"github.com/elastic/beats/filebeat/input"
"github.com/elastic/beats/filebeat/inputsource"
"github.com/elastic/beats/filebeat/util"
"github.com/elastic/beats/libbeat/beat"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/common/cfgwarn"
"github.com/elastic/beats/libbeat/logp"
)
// Parser is generated from a ragel state machine using the following command:
//go:generate ragel -Z -G2 parser.rl -o parser.go
// Severity and Facility are derived from the priority, theses are the human readable terms
// defined in https://tools.ietf.org/html/rfc3164#section-4.1.1.
//
// Example:
// 2 => "Critical"
type mapper []string
var (
severityLabels = mapper{
"Emergency",
"Alert",
"Critical",
"Error",
"Warning",
"Notice",
"Informational",
"Debug",
}
facilityLabels = mapper{
"kernel",
"user-level",
"mail",
"system",
"security/authorization",
"syslogd",
"line printer",
"network news",
"UUCP",
"clock",
"security/authorization",
"FTP",
"NTP",
"log audit",
"log alert",
"clock",
"local0",
"local1",
"local2",
"local3",
"local4",
"local5",
"local6",
"local7",
}
)
func init() {
err := input.Register("syslog", NewInput)
if err != nil {
panic(err)
}
}
// Input define a syslog input
type Input struct {
sync.Mutex
started bool
outlet channel.Outleter
server inputsource.Network
config *config
log *logp.Logger
}
// NewInput creates a new syslog input
func NewInput(
cfg *common.Config,
outlet channel.Connector,
context input.Context,
) (input.Input, error) {
cfgwarn.Experimental("Syslog input type is used")
log := logp.NewLogger("syslog")
out, err := outlet(cfg, context.DynamicFields)
if err != nil {
return nil, err
}
config := defaultConfig
if err = cfg.Unpack(&config); err != nil {
return nil, err
}
forwarder := harvester.NewForwarder(out)
cb := func(data []byte, metadata inputsource.NetworkMetadata) {
ev := newEvent()
Parse(data, ev)
var d *util.Data
if !ev.IsValid() {
log.Errorw("can't not parse event as syslog rfc3164", "message", string(data))
// On error revert to the raw bytes content, we need a better way to communicate this kind of
// error upstream this should be a global effort.
d = &util.Data{
Event: beat.Event{
Timestamp: time.Now(),
Meta: common.MapStr{
"truncated": metadata.Truncated,
},
Fields: common.MapStr{
"message": string(data),
},
},
}
} else {
event := createEvent(ev, metadata, time.Local, log)
d = &util.Data{Event: *event}
}
forwarder.Send(d)
}
server, err := factory(cb, config.Protocol)
if err != nil {
return nil, err
}
return &Input{
outlet: out,
started: false,
server: server,
config: &config,
log: log,
}, nil
}
// Run starts listening for Syslog events over the network.
func (p *Input) Run() {
p.Lock()
defer p.Unlock()
if !p.started {
p.log.Infow("Starting Syslog input", "protocol", p.config.Protocol.Name())
err := p.server.Start()
if err != nil {
p.log.Error("Error starting the server", "error", err)
return
}
p.started = true
}
}
// Stop stops the syslog input.
func (p *Input) Stop() {
defer p.outlet.Close()
p.Lock()
defer p.Unlock()
if !p.started {
return
}
p.log.Info("Stopping Syslog input")
p.server.Stop()
p.started = false
}
// Wait stops the syslog input.
func (p *Input) Wait() {
p.Stop()
}
func createEvent(ev *event, metadata inputsource.NetworkMetadata, timezone *time.Location, log *logp.Logger) *beat.Event {
f := common.MapStr{
"message": strings.TrimRight(ev.Message(), "\n"),
"source": metadata.RemoteAddr.String(),
}
syslog := common.MapStr{}
event := common.MapStr{}
process := common.MapStr{}
if ev.Hostname() != "" {
f["hostname"] = ev.Hostname()
}
if ev.HasPid() {
process["pid"] = ev.Pid()
}
if ev.Program() != "" {
process["program"] = ev.Program()
}
if ev.HasPriority() {
syslog["priority"] = ev.Priority()
event["severity"] = ev.Severity()
v, err := mapValueToName(ev.Severity(), severityLabels)
if err != nil {
log.Debugw("could not find severity label", "error", err)
} else {
syslog["severity_label"] = v
}
syslog["facility"] = ev.Facility()
v, err = mapValueToName(ev.Facility(), facilityLabels)
if err != nil {
log.Debugw("could not find facility label", "error", err)
} else {
syslog["facility_label"] = v
}
}
f["syslog"] = syslog
f["event"] = event
f["process"] = process
return &beat.Event{
Timestamp: ev.Timestamp(timezone),
Meta: common.MapStr{
"truncated": metadata.Truncated,
},
Fields: f,
}
}
func mapValueToName(v int, m mapper) (string, error) {
if v < 0 || v >= len(m) {
return "", errors.Errorf("value out of bound: %d", v)
}
return m[v], nil
}