github.com/cilium/cilium@v1.16.2/pkg/proxy/logger/logger.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package logger
     5  
     6  import (
     7  	"context"
     8  	"net/netip"
     9  
    10  	"github.com/sirupsen/logrus"
    11  
    12  	"github.com/cilium/cilium/pkg/flowdebug"
    13  	"github.com/cilium/cilium/pkg/identity"
    14  	"github.com/cilium/cilium/pkg/lock"
    15  	"github.com/cilium/cilium/pkg/logging"
    16  	"github.com/cilium/cilium/pkg/logging/logfields"
    17  	"github.com/cilium/cilium/pkg/node"
    18  	"github.com/cilium/cilium/pkg/proxy/accesslog"
    19  	"github.com/cilium/cilium/pkg/time"
    20  )
    21  
    22  var (
    23  	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "proxy-logger")
    24  
    25  	logMutex lock.Mutex
    26  	notifier LogRecordNotifier
    27  	metadata []string
    28  )
    29  
    30  // fields used for structured logging
    31  const (
    32  	FieldType     = "type"
    33  	FieldVerdict  = "verdict"
    34  	FieldCode     = "code"
    35  	FieldMethod   = "method"
    36  	FieldURL      = "url"
    37  	FieldProtocol = "protocol"
    38  	FieldHeader   = "header"
    39  	FieldFilePath = logfields.Path
    40  	FieldMessage  = "message"
    41  )
    42  
    43  // fields used for structured logging of Kafka messages
    44  const (
    45  	FieldKafkaAPIKey        = "kafkaApiKey"
    46  	FieldKafkaAPIVersion    = "kafkaApiVersion"
    47  	FieldKafkaCorrelationID = "kafkaCorrelationID"
    48  )
    49  
    50  // LogRecord is a proxy log record based off accesslog.LogRecord.
    51  type LogRecord struct {
    52  	accesslog.LogRecord
    53  }
    54  
    55  // endpointInfoRegistry provides access to any endpoint's information given
    56  // its IP address.
    57  var endpointInfoRegistry EndpointInfoRegistry
    58  
    59  func SetEndpointInfoRegistry(epInfoRegistry EndpointInfoRegistry) {
    60  	endpointInfoRegistry = epInfoRegistry
    61  }
    62  
    63  // NewLogRecord creates a new log record and applies optional tags
    64  //
    65  // Example:
    66  // record := logger.NewLogRecord(flowType, observationPoint, logger.LogTags.Timestamp(time.Now()))
    67  func NewLogRecord(t accesslog.FlowType, ingress bool, tags ...LogTag) *LogRecord {
    68  	var observationPoint accesslog.ObservationPoint
    69  	if ingress {
    70  		observationPoint = accesslog.Ingress
    71  	} else {
    72  		observationPoint = accesslog.Egress
    73  	}
    74  
    75  	lr := LogRecord{
    76  		LogRecord: accesslog.LogRecord{
    77  			Type:              t,
    78  			ObservationPoint:  observationPoint,
    79  			IPVersion:         accesslog.VersionIPv4,
    80  			TransportProtocol: 6,
    81  			Timestamp:         time.Now().UTC().Format(time.RFC3339Nano),
    82  			NodeAddressInfo:   accesslog.NodeAddressInfo{},
    83  		},
    84  	}
    85  
    86  	if ip := node.GetIPv4(); ip != nil {
    87  		lr.LogRecord.NodeAddressInfo.IPv4 = ip.String()
    88  	}
    89  
    90  	if ip := node.GetIPv6(); ip != nil {
    91  		lr.LogRecord.NodeAddressInfo.IPv6 = ip.String()
    92  	}
    93  
    94  	for _, tagFn := range tags {
    95  		tagFn(&lr)
    96  	}
    97  
    98  	return &lr
    99  }
   100  
   101  // LogTag attaches a tag to a log record
   102  type LogTag func(lr *LogRecord)
   103  
   104  // LogTags are optional structured tags that can be attached to log records.
   105  // See NewLogRecord() and ApplyTags() for example usage.
   106  var LogTags logTags
   107  
   108  type logTags struct{}
   109  
   110  // Verdict attachs verdict information to the log record
   111  func (logTags) Verdict(v accesslog.FlowVerdict, info string) LogTag {
   112  	return func(lr *LogRecord) {
   113  		lr.Verdict = v
   114  		lr.Info = info
   115  	}
   116  }
   117  
   118  // Timestamp overwrites the starting timestamp of the log record
   119  func (logTags) Timestamp(ts time.Time) LogTag {
   120  	return func(lr *LogRecord) {
   121  		lr.Timestamp = ts.UTC().Format(time.RFC3339Nano)
   122  	}
   123  }
   124  
   125  // AddressingInfo is the information passed in via the Addressing() tag
   126  type AddressingInfo struct {
   127  	SrcIPPort string
   128  	DstIPPort string
   129  
   130  	SrcIdentity    identity.NumericIdentity
   131  	SrcSecIdentity *identity.Identity
   132  	SrcEPID        uint64
   133  
   134  	DstIdentity    identity.NumericIdentity
   135  	DstSecIdentity *identity.Identity
   136  	DstEPID        uint64
   137  }
   138  
   139  // Addressing attaches addressing information about the source and destination
   140  // to the logrecord
   141  func (logTags) Addressing(ctx context.Context, i AddressingInfo) LogTag {
   142  	return func(lr *LogRecord) {
   143  		lr.SourceEndpoint.ID = i.SrcEPID
   144  		if i.SrcSecIdentity != nil {
   145  			lr.SourceEndpoint.Identity = uint64(i.SrcSecIdentity.ID)
   146  			lr.SourceEndpoint.Labels = i.SrcSecIdentity.LabelArray
   147  		} else {
   148  			lr.SourceEndpoint.Identity = uint64(i.SrcIdentity)
   149  		}
   150  
   151  		addrPort, err := netip.ParseAddrPort(i.SrcIPPort)
   152  		if err == nil {
   153  			if addrPort.Addr().Is6() {
   154  				lr.IPVersion = accesslog.VersionIPV6
   155  			}
   156  
   157  			lr.SourceEndpoint.Port = addrPort.Port()
   158  			endpointInfoRegistry.FillEndpointInfo(ctx, &lr.SourceEndpoint, addrPort.Addr())
   159  		}
   160  
   161  		lr.DestinationEndpoint.ID = i.DstEPID
   162  		if i.DstSecIdentity != nil {
   163  			lr.DestinationEndpoint.Identity = uint64(i.DstSecIdentity.ID)
   164  			lr.DestinationEndpoint.Labels = i.DstSecIdentity.LabelArray
   165  		} else {
   166  			lr.DestinationEndpoint.Identity = uint64(i.DstIdentity)
   167  		}
   168  
   169  		addrPort, err = netip.ParseAddrPort(i.DstIPPort)
   170  		if err == nil {
   171  			lr.DestinationEndpoint.Port = addrPort.Port()
   172  			endpointInfoRegistry.FillEndpointInfo(ctx, &lr.DestinationEndpoint, addrPort.Addr())
   173  		}
   174  	}
   175  }
   176  
   177  // HTTP attaches HTTP information to the log record
   178  func (logTags) HTTP(h *accesslog.LogRecordHTTP) LogTag {
   179  	return func(lr *LogRecord) {
   180  		lr.HTTP = h
   181  	}
   182  }
   183  
   184  // Kafka attaches Kafka information to the log record
   185  func (logTags) Kafka(k *accesslog.LogRecordKafka) LogTag {
   186  	return func(lr *LogRecord) {
   187  		lr.Kafka = k
   188  	}
   189  }
   190  
   191  // DNS attaches DNS information to the log record
   192  func (logTags) DNS(d *accesslog.LogRecordDNS) LogTag {
   193  	return func(lr *LogRecord) {
   194  		lr.DNS = d
   195  	}
   196  }
   197  
   198  // L7 attaches generic L7 information to the log record
   199  func (logTags) L7(h *accesslog.LogRecordL7) LogTag {
   200  	return func(lr *LogRecord) {
   201  		lr.L7 = h
   202  	}
   203  }
   204  
   205  // ApplyTags applies tags to an existing log record
   206  //
   207  // Example:
   208  // lr.ApplyTags(logger.LogTags.Verdict(verdict, info))
   209  func (lr *LogRecord) ApplyTags(tags ...LogTag) {
   210  	for _, tagFn := range tags {
   211  		tagFn(lr)
   212  	}
   213  }
   214  
   215  func (lr *LogRecord) getLogFields() *logrus.Entry {
   216  	fields := make(logrus.Fields, 8) // at most 8 entries, avoid map grow
   217  
   218  	fields[FieldType] = lr.Type
   219  	fields[FieldVerdict] = lr.Verdict
   220  	fields[FieldMessage] = lr.Info
   221  
   222  	if lr.HTTP != nil {
   223  		fields[FieldCode] = lr.HTTP.Code
   224  		fields[FieldMethod] = lr.HTTP.Method
   225  		fields[FieldURL] = lr.HTTP.URL
   226  		fields[FieldProtocol] = lr.HTTP.Protocol
   227  		fields[FieldHeader] = lr.HTTP.Headers
   228  	}
   229  
   230  	if lr.Kafka != nil {
   231  		fields[FieldCode] = lr.Kafka.ErrorCode
   232  		fields[FieldKafkaAPIKey] = lr.Kafka.APIKey
   233  		fields[FieldKafkaAPIVersion] = lr.Kafka.APIVersion
   234  		fields[FieldKafkaCorrelationID] = lr.Kafka.CorrelationID
   235  	}
   236  
   237  	return log.WithFields(fields)
   238  }
   239  
   240  // Log logs a record to the logfile and flushes the buffer
   241  func (lr *LogRecord) Log() {
   242  	flowdebug.Log(func() (*logrus.Entry, string) {
   243  		return lr.getLogFields(), "Logging flow record"
   244  	})
   245  
   246  	logMutex.Lock()
   247  	lr.Metadata = metadata
   248  	n := notifier
   249  	logMutex.Unlock()
   250  
   251  	if n != nil {
   252  		n.NewProxyLogRecord(lr)
   253  	}
   254  }
   255  
   256  // LogRecordNotifier is the interface to implement LogRecord notifications.
   257  // Each type that wants to implement this interface must support concurrent calls
   258  // to the interface methods.
   259  // Besides, the number of concurrent calls may be very high, so long critical sections
   260  // should be avoided (i.e.: avoid using a single lock for slow logging operations).
   261  type LogRecordNotifier interface {
   262  	// NewProxyLogRecord is called for each new log record
   263  	NewProxyLogRecord(l *LogRecord) error
   264  }
   265  
   266  // SetNotifier sets the notifier to call for all L7 records
   267  func SetNotifier(n LogRecordNotifier) {
   268  	logMutex.Lock()
   269  	notifier = n
   270  	logMutex.Unlock()
   271  }
   272  
   273  // SetMetadata sets the metadata to include in each record
   274  func SetMetadata(md []string) {
   275  	logMutex.Lock()
   276  	defer logMutex.Unlock()
   277  
   278  	metadata = md
   279  }
   280  
   281  // EndpointInfoRegistry provides endpoint information lookup by endpoint IP address.
   282  type EndpointInfoRegistry interface {
   283  	// FillEndpointInfo resolves the labels of the specified identity if known locally.
   284  	// ID and Labels should be provieded in 'info' if known.
   285  	// If 'id' is passed as zero, will locate the EP by 'addr', and also fill info.ID, if found.
   286  	// Fills in the following info member fields:
   287  	//  - info.IPv4           (if 'ip' is IPv4)
   288  	//  - info.IPv6           (if 'ip' is not IPv4)
   289  	//  - info.Identity       (defaults to WORLD if not known)
   290  	//  - info.Labels         (only if identity is found)
   291  	FillEndpointInfo(ctx context.Context, info *accesslog.EndpointInfo, addr netip.Addr)
   292  }