github.com/cilium/cilium@v1.16.2/pkg/hubble/exporter/exporter.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package exporter
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  
    13  	"github.com/cilium/lumberjack/v2"
    14  	"github.com/sirupsen/logrus"
    15  
    16  	flowpb "github.com/cilium/cilium/api/v1/flow"
    17  	observerpb "github.com/cilium/cilium/api/v1/observer"
    18  	v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
    19  	"github.com/cilium/cilium/pkg/hubble/exporter/exporteroption"
    20  	"github.com/cilium/cilium/pkg/hubble/filters"
    21  	nodeTypes "github.com/cilium/cilium/pkg/node/types"
    22  )
    23  
    24  // exporter is an implementation of OnDecodedEvent interface that writes Hubble events to a file.
    25  type exporter struct {
    26  	FlowLogExporter
    27  	ctx     context.Context
    28  	logger  logrus.FieldLogger
    29  	encoder *json.Encoder
    30  	writer  io.WriteCloser
    31  	flow    *flowpb.Flow
    32  
    33  	opts exporteroption.Options
    34  }
    35  
    36  // NewExporter initializes an exporter.
    37  func NewExporter(
    38  	ctx context.Context,
    39  	logger logrus.FieldLogger,
    40  	options ...exporteroption.Option) (*exporter, error) {
    41  	opts := exporteroption.Default // start with defaults
    42  	for _, opt := range options {
    43  		if err := opt(&opts); err != nil {
    44  			return nil, fmt.Errorf("failed to apply option: %w", err)
    45  		}
    46  	}
    47  	logger.WithField("options", opts).Info("Configuring Hubble event exporter")
    48  	var writer io.WriteCloser
    49  	// If hubble-export-file-path is set to "stdout", use os.Stdout as the writer.
    50  	if opts.Path == "stdout" {
    51  		writer = os.Stdout
    52  	} else {
    53  		writer = &lumberjack.Logger{
    54  			Filename:   opts.Path,
    55  			MaxSize:    opts.MaxSizeMB,
    56  			MaxBackups: opts.MaxBackups,
    57  			Compress:   opts.Compress,
    58  		}
    59  	}
    60  	return newExporter(ctx, logger, writer, opts)
    61  }
    62  
    63  // newExporter let's you supply your own WriteCloser for tests.
    64  func newExporter(ctx context.Context, logger logrus.FieldLogger, writer io.WriteCloser, opts exporteroption.Options) (*exporter, error) {
    65  	encoder := json.NewEncoder(writer)
    66  	var flow *flowpb.Flow
    67  	if opts.FieldMask.Active() {
    68  		flow = new(flowpb.Flow)
    69  		opts.FieldMask.Alloc(flow.ProtoReflect())
    70  	}
    71  	return &exporter{
    72  		ctx:     ctx,
    73  		logger:  logger,
    74  		encoder: encoder,
    75  		writer:  writer,
    76  		flow:    flow,
    77  		opts:    opts,
    78  	}, nil
    79  }
    80  
    81  // eventToExportEvent converts Event to ExportEvent.
    82  func (e *exporter) eventToExportEvent(event *v1.Event) *observerpb.ExportEvent {
    83  	switch ev := event.Event.(type) {
    84  	case *flowpb.Flow:
    85  		if e.opts.FieldMask.Active() {
    86  			e.opts.FieldMask.Copy(e.flow.ProtoReflect(), ev.ProtoReflect())
    87  			ev = e.flow
    88  		}
    89  		return &observerpb.ExportEvent{
    90  			Time:     ev.GetTime(),
    91  			NodeName: ev.GetNodeName(),
    92  			ResponseTypes: &observerpb.ExportEvent_Flow{
    93  				Flow: ev,
    94  			},
    95  		}
    96  	case *flowpb.LostEvent:
    97  		return &observerpb.ExportEvent{
    98  			Time:     event.Timestamp,
    99  			NodeName: nodeTypes.GetName(),
   100  			ResponseTypes: &observerpb.ExportEvent_LostEvents{
   101  				LostEvents: ev,
   102  			},
   103  		}
   104  	case *flowpb.AgentEvent:
   105  		return &observerpb.ExportEvent{
   106  			Time:     event.Timestamp,
   107  			NodeName: nodeTypes.GetName(),
   108  			ResponseTypes: &observerpb.ExportEvent_AgentEvent{
   109  				AgentEvent: ev,
   110  			},
   111  		}
   112  	case *flowpb.DebugEvent:
   113  		return &observerpb.ExportEvent{
   114  			Time:     event.Timestamp,
   115  			NodeName: nodeTypes.GetName(),
   116  			ResponseTypes: &observerpb.ExportEvent_DebugEvent{
   117  				DebugEvent: ev,
   118  			},
   119  		}
   120  	default:
   121  		return nil
   122  	}
   123  }
   124  
   125  func (e *exporter) Stop() error {
   126  	if e.writer == nil {
   127  		// Already stoppped
   128  		return nil
   129  	}
   130  	err := e.writer.Close()
   131  	e.writer = nil
   132  	return err
   133  }
   134  
   135  // OnDecodedEvent checks if the event passes the filter.
   136  // If context was cancelled, it calls Stop() and stops processing events.
   137  func (e *exporter) OnDecodedEvent(_ context.Context, ev *v1.Event) (bool, error) {
   138  	select {
   139  	case <-e.ctx.Done():
   140  		return false, e.Stop()
   141  	default:
   142  	}
   143  	if !filters.Apply(e.opts.AllowList, e.opts.DenyList, ev) {
   144  		return false, nil
   145  	}
   146  	res := e.eventToExportEvent(ev)
   147  	if res == nil {
   148  		return false, nil
   149  	}
   150  	return false, e.encoder.Encode(res)
   151  }