github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/builtin/input/windows/operator.go (about)

     1  // +build windows
     2  
     3  package windows
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/observiq/carbon/operator"
    12  	"github.com/observiq/carbon/operator/helper"
    13  )
    14  
    15  func init() {
    16  	operator.Register("windows_eventlog_input", NewDefaultConfig)
    17  }
    18  
    19  // EventLogConfig is the configuration of a windows event log operator.
    20  type EventLogConfig struct {
    21  	helper.InputConfig `yaml:",inline"`
    22  	Channel            string            `json:"channel" yaml:"channel"`
    23  	MaxReads           int               `json:"max_reads,omitempty" yaml:"max_reads,omitempty"`
    24  	StartAt            string            `json:"start_at,omitempty" yaml:"start_at,omitempty"`
    25  	PollInterval       operator.Duration `json:"poll_interval,omitempty" yaml:"poll_interval,omitempty"`
    26  }
    27  
    28  // Build will build a windows event log operator.
    29  func (c *EventLogConfig) Build(context operator.BuildContext) (operator.Operator, error) {
    30  	inputOperator, err := c.InputConfig.Build(context)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	if c.Channel == "" {
    36  		return nil, fmt.Errorf("missing required `channel` field")
    37  	}
    38  
    39  	if c.MaxReads < 1 {
    40  		return nil, fmt.Errorf("the `max_reads` field must be greater than zero")
    41  	}
    42  
    43  	if c.StartAt != "end" && c.StartAt != "beginning" {
    44  		return nil, fmt.Errorf("the `start_at` field must be set to `beginning` or `end`")
    45  	}
    46  
    47  	offsets := helper.NewScopedDBPersister(context.Database, c.ID())
    48  
    49  	eventLogInput := &EventLogInput{
    50  		InputOperator: inputOperator,
    51  		buffer:        NewBuffer(),
    52  		channel:       c.Channel,
    53  		maxReads:      c.MaxReads,
    54  		startAt:       c.StartAt,
    55  		pollInterval:  c.PollInterval,
    56  		offsets:       offsets,
    57  	}
    58  	return eventLogInput, nil
    59  }
    60  
    61  // NewDefaultConfig will return an event log config with default values.
    62  func NewDefaultConfig() operator.Builder {
    63  	return &EventLogConfig{
    64  		InputConfig: helper.NewInputConfig("", "windows_eventlog_input"),
    65  		MaxReads:    100,
    66  		StartAt:     "end",
    67  		PollInterval: operator.Duration{
    68  			Duration: 1 * time.Second,
    69  		},
    70  	}
    71  }
    72  
    73  // EventLogInput is an operator that creates entries using the windows event log api.
    74  type EventLogInput struct {
    75  	helper.InputOperator
    76  	bookmark     Bookmark
    77  	subscription Subscription
    78  	buffer       Buffer
    79  	channel      string
    80  	maxReads     int
    81  	startAt      string
    82  	pollInterval operator.Duration
    83  	offsets      helper.Persister
    84  	cancel       context.CancelFunc
    85  	wg           *sync.WaitGroup
    86  }
    87  
    88  // Start will start reading events from a subscription.
    89  func (e *EventLogInput) Start() error {
    90  	ctx, cancel := context.WithCancel(context.Background())
    91  	e.cancel = cancel
    92  	e.wg = &sync.WaitGroup{}
    93  
    94  	e.bookmark = NewBookmark()
    95  	offsetXML, err := e.getBookmarkOffset()
    96  	if err != nil {
    97  		return fmt.Errorf("failed to retrieve bookmark offset: %s", err)
    98  	}
    99  
   100  	if offsetXML != "" {
   101  		if err := e.bookmark.Open(offsetXML); err != nil {
   102  			return fmt.Errorf("failed to open bookmark: %s", err)
   103  		}
   104  	}
   105  
   106  	e.subscription = NewSubscription()
   107  	if err := e.subscription.Open(e.channel, e.startAt, e.bookmark); err != nil {
   108  		return fmt.Errorf("failed to open subscription: %s", err)
   109  	}
   110  
   111  	e.wg.Add(1)
   112  	go e.readOnInterval(ctx)
   113  	return nil
   114  }
   115  
   116  // Stop will stop reading events from a subscription.
   117  func (e *EventLogInput) Stop() error {
   118  	e.cancel()
   119  	e.wg.Wait()
   120  
   121  	if err := e.subscription.Close(); err != nil {
   122  		return fmt.Errorf("failed to close subscription: %s", err)
   123  	}
   124  
   125  	if err := e.bookmark.Close(); err != nil {
   126  		return fmt.Errorf("failed to close bookmark: %s", err)
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  // readOnInterval will read events with respect to the polling interval.
   133  func (e *EventLogInput) readOnInterval(ctx context.Context) {
   134  	defer e.wg.Done()
   135  
   136  	ticker := time.NewTicker(e.pollInterval.Raw())
   137  	defer ticker.Stop()
   138  
   139  	for {
   140  		select {
   141  		case <-ctx.Done():
   142  			return
   143  		case <-ticker.C:
   144  			e.readToEnd(ctx)
   145  		}
   146  	}
   147  }
   148  
   149  // readToEnd will read events from the subscription until it reaches the end of the channel.
   150  func (e *EventLogInput) readToEnd(ctx context.Context) {
   151  	for {
   152  		select {
   153  		case <-ctx.Done():
   154  			return
   155  		default:
   156  			if count := e.read(ctx); count == 0 {
   157  				return
   158  			}
   159  		}
   160  	}
   161  }
   162  
   163  // read will read events from the subscription.
   164  func (e *EventLogInput) read(ctx context.Context) int {
   165  	events, err := e.subscription.Read(e.maxReads)
   166  	if err != nil {
   167  		e.Errorf("Failed to read events from subscription: %s", err)
   168  		return 0
   169  	}
   170  
   171  	for i, event := range events {
   172  		e.processEvent(ctx, event)
   173  		if len(events) == i+1 {
   174  			e.updateBookmarkOffset(event)
   175  		}
   176  		event.Close()
   177  	}
   178  
   179  	return len(events)
   180  }
   181  
   182  // processEvent will process and send an event retrieved from windows event log.
   183  func (e *EventLogInput) processEvent(ctx context.Context, event Event) {
   184  	simpleEvent, err := event.RenderSimple(e.buffer)
   185  	if err != nil {
   186  		e.Errorf("Failed to render simple event: %s", err)
   187  		return
   188  	}
   189  
   190  	publisher := NewPublisher()
   191  	if err := publisher.Open(simpleEvent.Provider.Name); err != nil {
   192  		e.Errorf("Failed to open publisher: %s")
   193  		e.sendEvent(ctx, simpleEvent)
   194  		return
   195  	}
   196  	defer publisher.Close()
   197  
   198  	formattedEvent, err := event.RenderFormatted(e.buffer, publisher)
   199  	if err != nil {
   200  		e.Errorf("Failed to render formatted event: %s", err)
   201  		e.sendEvent(ctx, simpleEvent)
   202  		return
   203  	}
   204  
   205  	e.sendEvent(ctx, formattedEvent)
   206  }
   207  
   208  // sendEvent will send EventXML as an entry to the operator's output.
   209  func (e *EventLogInput) sendEvent(ctx context.Context, eventXML EventXML) {
   210  	record := eventXML.parseRecord()
   211  	entry, err := e.NewEntry(record)
   212  	if err != nil {
   213  		e.Errorf("Failed to create entry: %s", err)
   214  		return
   215  	}
   216  
   217  	entry.Timestamp = eventXML.parseTimestamp()
   218  	entry.Severity = eventXML.parseSeverity()
   219  	e.Write(ctx, entry)
   220  }
   221  
   222  // getBookmarkXML will get the bookmark xml from the offsets database.
   223  func (e *EventLogInput) getBookmarkOffset() (string, error) {
   224  	if err := e.offsets.Load(); err != nil {
   225  		return "", fmt.Errorf("failed to load offsets database: %s", err)
   226  	}
   227  
   228  	bytes := e.offsets.Get(e.channel)
   229  	return string(bytes), nil
   230  }
   231  
   232  // updateBookmark will update the bookmark xml and save it in the offsets database.
   233  func (e *EventLogInput) updateBookmarkOffset(event Event) {
   234  	if err := e.bookmark.Update(event); err != nil {
   235  		e.Errorf("Failed to update bookmark from event: %s", err)
   236  		return
   237  	}
   238  
   239  	bookmarkXML, err := e.bookmark.Render(e.buffer)
   240  	if err != nil {
   241  		e.Errorf("Failed to render bookmark xml: %s", err)
   242  		return
   243  	}
   244  
   245  	e.offsets.Set(e.channel, []byte(bookmarkXML))
   246  	if err := e.offsets.Sync(); err != nil {
   247  		e.Errorf("failed to sync offsets database: %s", err)
   248  		return
   249  	}
   250  }