github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/protocol/converter/converter.go (about)

     1  // Copyright 2022 iLogtail Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package protocol
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"sync"
    21  
    22  	"github.com/alibaba/ilogtail/pkg/config"
    23  	"github.com/alibaba/ilogtail/pkg/flags"
    24  	"github.com/alibaba/ilogtail/pkg/models"
    25  	"github.com/alibaba/ilogtail/pkg/protocol"
    26  )
    27  
    28  const (
    29  	ProtocolCustomSingle        = "custom_single"
    30  	ProtocolCustomSingleFlatten = "custom_single_flatten"
    31  	ProtocolOtlpV1              = "otlp_v1"
    32  	ProtocolInfluxdb            = "influxdb"
    33  	ProtocolJsonline            = "jsonline"
    34  	ProtocolRaw                 = "raw"
    35  )
    36  
    37  const (
    38  	EncodingNone     = "none"
    39  	EncodingJSON     = "json"
    40  	EncodingProtobuf = "protobuf"
    41  	EncodingCustom   = "custom"
    42  )
    43  
    44  const (
    45  	tagPrefix           = "__tag__:"
    46  	targetContentPrefix = "content."
    47  	targetTagPrefix     = "tag."
    48  
    49  	targetGroupMetadataPrefix = "metadata."
    50  )
    51  
    52  const (
    53  	tagHostIP                = "host.ip"
    54  	tagLogTopic              = "log.topic"
    55  	tagLogFilePath           = "log.file.path"
    56  	tagHostname              = "host.name"
    57  	tagK8sNodeIP             = "k8s.node.ip"
    58  	tagK8sNodeName           = "k8s.node.name"
    59  	tagK8sNamespace          = "k8s.namespace.name"
    60  	tagK8sPodName            = "k8s.pod.name"
    61  	tagK8sPodIP              = "k8s.pod.ip"
    62  	tagK8sPodUID             = "k8s.pod.uid"
    63  	tagContainerName         = "container.name"
    64  	tagContainerIP           = "container.ip"
    65  	tagContainerImageName    = "container.image.name"
    66  	tagK8sContainerName      = "k8s.container.name"
    67  	tagK8sContainerIP        = "k8s.container.ip"
    68  	tagK8sContainerImageName = "k8s.container.image.name"
    69  )
    70  
    71  // todo: make multiple pools for different size levels
    72  var byteBufPool = sync.Pool{
    73  	New: func() interface{} {
    74  		buf := make([]byte, 0, 1024)
    75  		return &buf
    76  	},
    77  }
    78  
    79  var tagConversionMap = map[string]string{
    80  	"__path__":         tagLogFilePath,
    81  	"__hostname__":     tagHostname,
    82  	"_node_ip_":        tagK8sNodeIP,
    83  	"_node_name_":      tagK8sNodeName,
    84  	"_namespace_":      tagK8sNamespace,
    85  	"_pod_name_":       tagK8sPodName,
    86  	"_pod_ip_":         tagK8sPodIP,
    87  	"_pod_uid_":        tagK8sPodUID,
    88  	"_container_name_": tagContainerName,
    89  	"_container_ip_":   tagContainerIP,
    90  	"_image_name_":     tagContainerImageName,
    91  }
    92  
    93  // When in k8s, the following tags should be renamed to k8s-specific names.
    94  var specialTagConversionMap = map[string]string{
    95  	"_container_name_": tagK8sContainerName,
    96  	"_container_ip_":   tagK8sContainerIP,
    97  	"_image_name_":     tagK8sContainerImageName,
    98  }
    99  
   100  var supportedEncodingMap = map[string]map[string]bool{
   101  	ProtocolCustomSingle: {
   102  		EncodingJSON:     true,
   103  		EncodingProtobuf: false,
   104  	},
   105  	ProtocolCustomSingleFlatten: {
   106  		EncodingJSON:     true,
   107  		EncodingProtobuf: false,
   108  	},
   109  	ProtocolOtlpV1: {
   110  		EncodingNone: true,
   111  	},
   112  	ProtocolInfluxdb: {
   113  		EncodingCustom: true,
   114  	},
   115  	ProtocolJsonline: {
   116  		EncodingJSON: true,
   117  	},
   118  	ProtocolRaw: {
   119  		EncodingCustom: true,
   120  	},
   121  }
   122  
   123  type Converter struct {
   124  	Protocol             string
   125  	Encoding             string
   126  	Separator            string
   127  	IgnoreUnExpectedData bool
   128  	OnlyContents         bool
   129  	TagKeyRenameMap      map[string]string
   130  	ProtocolKeyRenameMap map[string]string
   131  	GlobalConfig         *config.GlobalConfig
   132  }
   133  
   134  func NewConverterWithSep(protocol, encoding, sep string, ignoreUnExpectedData bool, tagKeyRenameMap, protocolKeyRenameMap map[string]string, globalConfig *config.GlobalConfig) (*Converter, error) {
   135  	converter, err := NewConverter(protocol, encoding, tagKeyRenameMap, protocolKeyRenameMap, globalConfig)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	converter.Separator = sep
   140  	converter.IgnoreUnExpectedData = ignoreUnExpectedData
   141  	return converter, nil
   142  }
   143  
   144  func NewConverter(protocol, encoding string, tagKeyRenameMap, protocolKeyRenameMap map[string]string, globalConfig *config.GlobalConfig) (*Converter, error) {
   145  	enc, ok := supportedEncodingMap[protocol]
   146  	if !ok {
   147  		return nil, fmt.Errorf("unsupported protocol: %s", protocol)
   148  	}
   149  	if supported, existed := enc[encoding]; !existed || !supported {
   150  		return nil, fmt.Errorf("unsupported encoding: %s for protocol %s", encoding, protocol)
   151  	}
   152  	return &Converter{
   153  		Protocol:             protocol,
   154  		Encoding:             encoding,
   155  		TagKeyRenameMap:      tagKeyRenameMap,
   156  		ProtocolKeyRenameMap: protocolKeyRenameMap,
   157  		GlobalConfig:         globalConfig,
   158  	}, nil
   159  }
   160  
   161  func (c *Converter) Do(logGroup *protocol.LogGroup) (logs interface{}, err error) {
   162  	logs, _, err = c.DoWithSelectedFields(logGroup, nil)
   163  	return
   164  }
   165  
   166  func (c *Converter) DoWithSelectedFields(logGroup *protocol.LogGroup, targetFields []string) (logs interface{}, values []map[string]string, err error) {
   167  	switch c.Protocol {
   168  	case ProtocolCustomSingle:
   169  		return c.ConvertToSingleProtocolLogs(logGroup, targetFields)
   170  	case ProtocolCustomSingleFlatten:
   171  		return c.ConvertToSingleProtocolLogsFlatten(logGroup, targetFields)
   172  	case ProtocolOtlpV1:
   173  		return c.ConvertToOtlpResourseLogs(logGroup, targetFields)
   174  	default:
   175  		return nil, nil, fmt.Errorf("unsupported protocol: %s", c.Protocol)
   176  	}
   177  }
   178  
   179  func (c *Converter) ToByteStream(logGroup *protocol.LogGroup) (stream interface{}, err error) {
   180  	stream, _, err = c.ToByteStreamWithSelectedFields(logGroup, nil)
   181  	return
   182  }
   183  
   184  func (c *Converter) ToByteStreamWithSelectedFields(logGroup *protocol.LogGroup, targetFields []string) (stream interface{}, values []map[string]string, err error) {
   185  	switch c.Protocol {
   186  	case ProtocolCustomSingle:
   187  		return c.ConvertToSingleProtocolStream(logGroup, targetFields)
   188  	case ProtocolCustomSingleFlatten:
   189  		return c.ConvertToSingleProtocolStreamFlatten(logGroup, targetFields)
   190  	case ProtocolInfluxdb:
   191  		return c.ConvertToInfluxdbProtocolStream(logGroup, targetFields)
   192  	case ProtocolJsonline:
   193  		return c.ConvertToJsonlineProtocolStreamFlatten(logGroup)
   194  	default:
   195  		return nil, nil, fmt.Errorf("unsupported protocol: %s", c.Protocol)
   196  	}
   197  }
   198  
   199  func (c *Converter) ToByteStreamWithSelectedFieldsV2(groupEvents *models.PipelineGroupEvents, targetFields []string) (stream interface{}, values []map[string]string, err error) {
   200  	switch c.Protocol {
   201  	case ProtocolRaw:
   202  		return c.ConvertToRawStream(groupEvents, targetFields)
   203  	case ProtocolInfluxdb:
   204  		return c.ConvertToInfluxdbProtocolStreamV2(groupEvents, targetFields)
   205  	default:
   206  		return nil, nil, fmt.Errorf("unsupported protocol: %s", c.Protocol)
   207  	}
   208  }
   209  
   210  func GetPooledByteBuf() *[]byte {
   211  	return byteBufPool.Get().(*[]byte)
   212  }
   213  
   214  func PutPooledByteBuf(buf *[]byte) {
   215  	*buf = (*buf)[:0]
   216  	byteBufPool.Put(buf)
   217  }
   218  
   219  func TrimPrefix(str string) string {
   220  	switch {
   221  	case strings.HasPrefix(str, targetContentPrefix):
   222  		return strings.TrimPrefix(str, targetContentPrefix)
   223  	case strings.HasPrefix(str, targetTagPrefix):
   224  		return strings.TrimPrefix(str, targetTagPrefix)
   225  	default:
   226  		return str
   227  	}
   228  }
   229  
   230  func convertLogToMap(log *protocol.Log, logTags []*protocol.LogTag, src, topic string, tagKeyRenameMap map[string]string) (map[string]string, map[string]string) {
   231  	contents, tags := make(map[string]string), make(map[string]string)
   232  	for _, logContent := range log.Contents {
   233  		switch logContent.Key {
   234  		case "__log_topic__":
   235  			addTagIfRequired(tags, tagKeyRenameMap, tagLogTopic, logContent.Value)
   236  		case tagPrefix + "__user_defined_id__":
   237  			continue
   238  		default:
   239  			var tagName string
   240  			if strings.HasPrefix(logContent.Key, tagPrefix) {
   241  				tagName = logContent.Key[len(tagPrefix):]
   242  				if _, ok := specialTagConversionMap[tagName]; *flags.K8sFlag && ok {
   243  					tagName = specialTagConversionMap[tagName]
   244  				} else if _, ok := tagConversionMap[tagName]; ok {
   245  					tagName = tagConversionMap[tagName]
   246  				}
   247  			} else {
   248  				if _, ok := specialTagConversionMap[logContent.Key]; *flags.K8sFlag && ok {
   249  					tagName = specialTagConversionMap[logContent.Key]
   250  				} else if _, ok := tagConversionMap[logContent.Key]; ok {
   251  					tagName = tagConversionMap[logContent.Key]
   252  				}
   253  			}
   254  			if len(tagName) != 0 {
   255  				addTagIfRequired(tags, tagKeyRenameMap, tagName, logContent.Value)
   256  			} else {
   257  				contents[logContent.Key] = logContent.Value
   258  			}
   259  		}
   260  	}
   261  
   262  	for _, logTag := range logTags {
   263  		if logTag.Key == "__user_defined_id__" || logTag.Key == "__pack_id__" {
   264  			continue
   265  		}
   266  
   267  		tagName := logTag.Key
   268  		if _, ok := specialTagConversionMap[logTag.Key]; *flags.K8sFlag && ok {
   269  			tagName = specialTagConversionMap[logTag.Key]
   270  		} else if _, ok := tagConversionMap[logTag.Key]; ok {
   271  			tagName = tagConversionMap[logTag.Key]
   272  		}
   273  		addTagIfRequired(tags, tagKeyRenameMap, tagName, logTag.Value)
   274  	}
   275  
   276  	addTagIfRequired(tags, tagKeyRenameMap, tagHostIP, src)
   277  	if topic != "" {
   278  		addTagIfRequired(tags, tagKeyRenameMap, tagLogTopic, topic)
   279  	}
   280  
   281  	return contents, tags
   282  }
   283  
   284  func findTargetValues(targetFields []string, contents, tags, tagKeyRenameMap map[string]string) (map[string]string, error) {
   285  	if len(targetFields) == 0 {
   286  		return nil, nil
   287  	}
   288  
   289  	desiredValue := make(map[string]string, len(targetFields))
   290  	for _, field := range targetFields {
   291  		switch {
   292  		case strings.HasPrefix(field, targetContentPrefix):
   293  			if value, ok := contents[field[len(targetContentPrefix):]]; ok {
   294  				desiredValue[field] = value
   295  			}
   296  		case strings.HasPrefix(field, targetTagPrefix):
   297  			if value, ok := tags[field[len(targetTagPrefix):]]; ok {
   298  				desiredValue[field] = value
   299  			} else if value, ok := tagKeyRenameMap[field[len(targetTagPrefix):]]; ok {
   300  				desiredValue[field] = tags[value]
   301  			}
   302  		default:
   303  			return nil, fmt.Errorf("unsupported field: %s", field)
   304  		}
   305  	}
   306  	return desiredValue, nil
   307  }
   308  
   309  func addTagIfRequired(tags, tagKeyRenameMap map[string]string, key, value string) {
   310  	if newKey, ok := tagKeyRenameMap[key]; ok && len(newKey) != 0 {
   311  		tags[newKey] = value
   312  	} else if !ok {
   313  		tags[key] = value
   314  	}
   315  }