The Beater Interface
editThe Beater Interface
editEach Beat needs to implement the Beater interface defined in libbeat.
// Beater is the interface that must be implemented by every Beat. A Beater // provides the main Run-loop and a Stop method to break the Run-loop. // Instantiation and Configuration is normally provided by a Beat-`Creator`. // // Once the beat is fully configured, the Run() method is invoked. The // Run()-method implements the beat its run-loop. Once the Run()-method returns, // the beat shuts down. // // The Stop() method is invoked the first time (and only the first time) a // shutdown signal is received. The Stop()-method normally will stop the Run()-loop, // such that the beat can gracefully shutdown. type Beater interface { // The main event loop. This method should block until signalled to stop by an // invocation of the Stop() method. Run(b *Beat) error // Stop is invoked to signal that the Run method should finish its execution. // It will be invoked at most once. Stop() }
To implement the Beater interface, you need to define a Beat object that
implements two methods: Run()
and Stop()
.
type Countbeat struct { done chan struct{} config config.Config client publisher.Client ... } func (bt *Countbeat) Run(b *beat.Beat) error { ... } func (bt *Countbeat) Stop() { ... }
By default, the Beat object contains the following:
|
|
|
|
|
The Beat
parameter received by the Run
method contains data about the
Beat, such as the name, version, and common configuration options.
Each Beat also needs to implement the New()
function to create the Beat object. This means your
Beat should implement the following functions:
Creates the Beat object |
|
Contains the main application loop that captures data and sends it to the defined output using the publisher |
|
Contains logic that is called when the Beat is signaled to stop |
When you run the Beat generator, it adds implementations for all these functions to the source code (see
beater/countbeat.go
). You can modify these implementations, as required, for your Beat.
We strongly recommend that you create a main package that contains only the main
method (see main.go
). All your Beat-specific code should go in a separate folder and package.
This will allow other Beats to use the other parts of your Beat as a library, if
needed.
To be consistent with other Beats, you should append beat
to your Beat name.
Let’s go through each of the methods in the Beater
interface and look at a
sample implementation.
New function
editThe New()
function receives the configuration options defined for the Beat and
creates a Beat object based on them. Here’s the New()
function that’s generated in
beater/countbeat.go
when you run the Beat generator:
func New(b *beat.Beat, cfg *common.Config) (beat.Beater, error) { config := config.DefaultConfig if err := cfg.Unpack(&config); err != nil { return nil, fmt.Errorf("Error reading config file: %v", err) } bt := &Countbeat{ done: make(chan struct{}), config: config, } return bt, nil }
Pointers are used to distinguish between when the setting is completely missing from the configuration file and when it has a value that matches the type’s default value.
The recommended way of handling the configuration (as shown in the code example)
is to create a Config
structure with the configuration options and a DefaultConfig
with
the default configuration options.
When you use the Beat generator, the Go structures for a basic config are added to config/config.go
:
package config import "time" type Config struct { Period time.Duration `config:"period"` } var DefaultConfig = Config{ Period: 1 * time.Second, }
This mirrors the config options that are defined in the config file, countbeat.yml
.
countbeat: # Defines how often an event is sent to the output period: 10s
-
period
: Defines how often to send out events
The config file is generated when you run make setup
to set up the beat. The file contains
basic configuration information. To add configuration options to your Beat, you need to update the Go structures in
config/config.go
and add the corresponding config options to _meta/beat.yml
.
For example, if you add a config option called path
to the Go structures:
type Config struct { Period time.Duration `config:"period"` Path string `config:"path"` } var DefaultConfig = Config{ Period: 1 * time.Second, Path: ".", }
You also need to add path
to _meta/beat.yml
:
countbeat: period: 10s path: "."
After modifying beat.yml
, run the following command to apply your updates:
make update
Run Method
editThe Run
method contains your main application loop.
func (bt *Countbeat) Run(b *beat.Beat) error { logp.Info("countbeat is running! Hit CTRL-C to stop it.") bt.client = b.Publisher.Connect() ticker := time.NewTicker(bt.config.Period) counter := 1 for { select { case <-bt.done: return nil case <-ticker.C: } event := common.MapStr{ "@timestamp": common.Time(time.Now()), "type": b.Name, "counter": counter, } bt.client.PublishEvent(event) logp.Info("Event sent") counter++ } }
Create the event object. |
|
Specify a |
|
Use the publisher to send the event out to the defined output |
Inside the loop, the Beat sleeps for a configurable period of time and then
captures the required data and sends it to the publisher. The publisher client is available as part of the Beat object
through the client
variable.
The event := common.MapStr{}
stores the event in a json format, and bt.client.PublishEvent(event)
publishes data to Elasticsearch.
In the generated Beat, there are three fields in the event: @timestamp, type, and counter.
When you add fields to the event object, you also need to add them to the _meta/fields.yml
file:
- key: countbeat title: countbeat description: fields: - name: counter type: long required: true description: > PLEASE UPDATE DOCUMENTATION
Remember to run make update
to apply your updates.
For more detail about naming the fields in an event, see Naming Conventions.
Stop Method
editThe Stop
method is called when the Beat is signaled to stop, for
example through the SIGTERM signal on Unix systems or the service control
interface on Windows. This method simply closes the channel
which breaks the main loop.
func (bt *Countbeat) Stop() { bt.client.Close() close(bt.done) }
The main Function
editIf you follow the Countbeat
model and put your Beat-specific code in its own type
that implements the Beater
interface, the code from your main package is
very simple:
package main import ( "os" "github.com/elastic/beats/libbeat/beat" "github.com/elastic/beats/libbeat/cmd" "github.com/kimjmin/countbeat/beater" ) var RootCmd = cmd.GenRootCmd("countbeat", "", beater.New) func main() { if err := RootCmd.Execute(); err != nil { os.Exit(1) } }