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 }