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 }