264 lines
5.6 KiB
Go
264 lines
5.6 KiB
Go
|
// 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
|
||
|
}
|