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

     1  // Copyright 2021 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 helper
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/alibaba/ilogtail/pkg/config"
    26  	"github.com/alibaba/ilogtail/pkg/protocol"
    27  	"github.com/alibaba/ilogtail/pkg/selfmonitor"
    28  	"github.com/alibaba/ilogtail/pkg/util"
    29  )
    30  
    31  const (
    32  	// StaleNaN is a signaling NaN, due to the MSB of the mantissa being 0.
    33  	// This value is chosen with many leading 0s, so we have scope to store more
    34  	// complicated values in the future. It is 2 rather than 1 to make
    35  	// it easier to distinguish from the NormalNaN by a human when debugging.
    36  	StaleNaN                              uint64 = 0x7ff0000000000002
    37  	StaleNan                                     = "__STALE_NAN__"
    38  	SlsMetricstoreInvalidReplaceCharacter        = '_'
    39  )
    40  
    41  func CreateLog(t time.Time, enableTimestampNano bool, configTag map[string]string, logTags map[string]string, fields map[string]string) (*protocol.Log, error) {
    42  	var slsLog protocol.Log
    43  	slsLog.Contents = make([]*protocol.Log_Content, 0, len(configTag)+len(logTags)+len(fields))
    44  	for key, val := range configTag {
    45  		cont := &protocol.Log_Content{
    46  			Key:   key,
    47  			Value: val,
    48  		}
    49  		slsLog.Contents = append(slsLog.Contents, cont)
    50  	}
    51  
    52  	for key, val := range logTags {
    53  		cont := &protocol.Log_Content{
    54  			Key:   key,
    55  			Value: val,
    56  		}
    57  		slsLog.Contents = append(slsLog.Contents, cont)
    58  	}
    59  
    60  	for key, val := range fields {
    61  		cont := &protocol.Log_Content{
    62  			Key:   key,
    63  			Value: val,
    64  		}
    65  		slsLog.Contents = append(slsLog.Contents, cont)
    66  	}
    67  	if enableTimestampNano {
    68  		protocol.SetLogTimeWithNano(&slsLog, uint32(t.Unix()), uint32(t.Nanosecond()))
    69  	} else {
    70  		protocol.SetLogTime(&slsLog, uint32(t.Unix()))
    71  	}
    72  	return &slsLog, nil
    73  }
    74  
    75  func CreateLogByArray(t time.Time, enableTimestampNano bool, configTag map[string]string, logTags map[string]string, columns []string, values []string) (*protocol.Log, error) {
    76  	var slsLog protocol.Log
    77  	slsLog.Contents = make([]*protocol.Log_Content, 0, len(configTag)+len(logTags)+len(columns))
    78  
    79  	for key, val := range configTag {
    80  		cont := &protocol.Log_Content{
    81  			Key:   key,
    82  			Value: val,
    83  		}
    84  		slsLog.Contents = append(slsLog.Contents, cont)
    85  	}
    86  
    87  	for key, val := range logTags {
    88  		cont := &protocol.Log_Content{
    89  			Key:   key,
    90  			Value: val,
    91  		}
    92  		slsLog.Contents = append(slsLog.Contents, cont)
    93  	}
    94  
    95  	if len(columns) != len(values) {
    96  		return nil, fmt.Errorf("columns and values not equal")
    97  	}
    98  
    99  	for index := range columns {
   100  		cont := &protocol.Log_Content{
   101  			Key:   columns[index],
   102  			Value: values[index],
   103  		}
   104  		slsLog.Contents = append(slsLog.Contents, cont)
   105  	}
   106  	if enableTimestampNano {
   107  		protocol.SetLogTimeWithNano(&slsLog, uint32(t.Unix()), uint32(t.Nanosecond()))
   108  	} else {
   109  		protocol.SetLogTime(&slsLog, uint32(t.Unix()))
   110  	}
   111  	return &slsLog, nil
   112  }
   113  
   114  // Label for metric label
   115  type MetricLabel struct {
   116  	Name  string
   117  	Value string
   118  }
   119  
   120  // Labels for metric labels
   121  type MetricLabels struct {
   122  	keyValues []*MetricLabel
   123  	sorted    bool
   124  	formatStr string
   125  }
   126  
   127  func (kv *MetricLabels) clearCache() {
   128  	kv.sorted = false
   129  	kv.formatStr = ""
   130  }
   131  
   132  func (kv *MetricLabels) Len() int {
   133  	return len(kv.keyValues)
   134  }
   135  
   136  func (kv *MetricLabels) Swap(i int, j int) {
   137  	kv.keyValues[i], kv.keyValues[j] = kv.keyValues[j], kv.keyValues[i]
   138  }
   139  
   140  func (kv *MetricLabels) Less(i int, j int) bool {
   141  	return kv.keyValues[i].Name < kv.keyValues[j].Name
   142  }
   143  
   144  func (kv *MetricLabels) Replace(key, value string) {
   145  	sort.Sort(kv)
   146  	findIndex := sort.Search(len(kv.keyValues), func(index int) bool {
   147  		return kv.keyValues[index].Name >= key
   148  	})
   149  	if findIndex < len(kv.keyValues) && kv.keyValues[findIndex].Name == key {
   150  		kv.keyValues[findIndex].Value = value
   151  	} else {
   152  		kv.Append(key, value)
   153  	}
   154  	kv.clearCache()
   155  }
   156  
   157  func (kv *MetricLabels) Clone() *MetricLabels {
   158  	if kv == nil {
   159  		return &MetricLabels{}
   160  	}
   161  	var newKeyValues MetricLabels
   162  	kv.CloneInto(&newKeyValues)
   163  	return &newKeyValues
   164  }
   165  
   166  func (kv *MetricLabels) CloneInto(dst *MetricLabels) *MetricLabels {
   167  	if kv == nil {
   168  		return &MetricLabels{}
   169  	}
   170  	if dst == nil {
   171  		return kv.Clone()
   172  	}
   173  	if len(kv.keyValues) < cap(dst.keyValues) {
   174  		dst.keyValues = dst.keyValues[:len(kv.keyValues)]
   175  	} else {
   176  		dst.keyValues = make([]*MetricLabel, len(kv.keyValues))
   177  	}
   178  	dst.sorted = kv.sorted
   179  	dst.formatStr = kv.formatStr
   180  	for i, value := range kv.keyValues {
   181  		cp := *value
   182  		dst.keyValues[i] = &cp
   183  	}
   184  	return dst
   185  }
   186  
   187  // AppendMap ...
   188  func (kv *MetricLabels) AppendMap(mapVal map[string]string) {
   189  	for key, value := range mapVal {
   190  		kv.Append(key, value)
   191  	}
   192  	kv.clearCache()
   193  }
   194  
   195  // Append ...
   196  func (kv *MetricLabels) Append(key, value string) {
   197  	kv.keyValues = append(kv.keyValues, &MetricLabel{
   198  		formatLabelKey(key),
   199  		formatLabelValue(value),
   200  	})
   201  	kv.clearCache()
   202  }
   203  
   204  func (kv *MetricLabels) SubSlice(begin, end int) {
   205  	kv.keyValues = kv.keyValues[begin:end]
   206  	kv.clearCache()
   207  }
   208  
   209  func (kv *MetricLabels) String() string {
   210  	if kv == nil {
   211  		return ""
   212  	}
   213  	if !kv.sorted || kv.formatStr == "" {
   214  		sort.Sort(kv)
   215  		var builder strings.Builder
   216  		for index, label := range kv.keyValues {
   217  			builder.WriteString(label.Name)
   218  			builder.WriteString("#$#")
   219  			builder.WriteString(label.Value)
   220  			if index != len(kv.keyValues)-1 {
   221  				builder.WriteByte('|')
   222  			}
   223  		}
   224  		kv.formatStr = builder.String()
   225  		kv.sorted = true
   226  	}
   227  	return kv.formatStr
   228  }
   229  
   230  // DefBucket ...
   231  type DefBucket struct {
   232  	Le    float64
   233  	Count int64
   234  }
   235  
   236  // HistogramData ...
   237  type HistogramData struct {
   238  	Buckets []DefBucket
   239  	Count   int64
   240  	Sum     float64
   241  }
   242  
   243  // ToMetricLogs ..
   244  func (hd *HistogramData) ToMetricLogs(name string, timeMs int64, labels *MetricLabels) []*protocol.Log {
   245  	logs := make([]*protocol.Log, 0, len(hd.Buckets)+2)
   246  	logs = append(logs, NewMetricLog(name+"_count", timeMs, float64(hd.Count), labels))
   247  	logs = append(logs, NewMetricLog(name+"_sum", timeMs, hd.Sum, labels))
   248  	for _, v := range hd.Buckets {
   249  		labels.Replace("le", strconv.FormatFloat(v.Le, 'g', -1, 64))
   250  		logs = append(logs, NewMetricLog(name+"_bucket", timeMs, float64(v.Count), labels))
   251  	}
   252  
   253  	return logs
   254  }
   255  
   256  // NewMetricLog create a metric log, time support unix milliseconds and unix nanoseconds.
   257  // Note: must pass safe string
   258  func NewMetricLog(name string, t int64, value float64, labels *MetricLabels) *protocol.Log {
   259  	var valStr string
   260  	if math.Float64bits(value) == StaleNaN {
   261  		valStr = StaleNan
   262  	} else {
   263  		valStr = strconv.FormatFloat(value, 'g', -1, 64)
   264  	}
   265  	return NewMetricLogStringVal(name, t, valStr, labels)
   266  }
   267  
   268  // NewMetricLogStringVal create a metric log with val string, time support unix milliseconds and unix nanoseconds.
   269  // Note: must pass safe string
   270  func NewMetricLogStringVal(name string, t int64, value string, labels *MetricLabels) *protocol.Log {
   271  	strTime := strconv.FormatInt(t, 10)
   272  	metric := &protocol.Log{}
   273  	switch len(strTime) {
   274  	case 13:
   275  		protocol.SetLogTimeWithNano(metric, uint32(t/1000), uint32((t*1e6)%1e9))
   276  		strTime += "000000"
   277  	case 19:
   278  		protocol.SetLogTimeWithNano(metric, uint32(t/1e9), uint32(t%1e9))
   279  	default:
   280  		t = int64(float64(t) * math.Pow10(19-len(strTime)))
   281  		strTime = strconv.FormatInt(t, 10)
   282  		protocol.SetLogTimeWithNano(metric, uint32(t/1e9), uint32(t%1e9))
   283  	}
   284  	metric.Contents = make([]*protocol.Log_Content, 0, 4)
   285  	metric.Contents = append(metric.Contents, &protocol.Log_Content{Key: "__name__", Value: formatNewMetricName(name)})
   286  	metric.Contents = append(metric.Contents, &protocol.Log_Content{Key: "__time_nano__", Value: strTime})
   287  	metric.Contents = append(metric.Contents, &protocol.Log_Content{Key: "__labels__", Value: labels.String()})
   288  	metric.Contents = append(metric.Contents, &protocol.Log_Content{Key: "__value__", Value: value})
   289  	return metric
   290  }
   291  
   292  func formatLabelKey(key string) string {
   293  	if !config.LoongcollectorGlobalConfig.EnableSlsMetricsFormat {
   294  		return key
   295  	}
   296  	var newKey []byte
   297  	for i := 0; i < len(key); i++ {
   298  		b := key[i]
   299  		if (b >= 'a' && b <= 'z') ||
   300  			(b >= 'A' && b <= 'Z') ||
   301  			(b >= '0' && b <= '9') ||
   302  			b == '_' {
   303  			continue
   304  		} else {
   305  			if newKey == nil {
   306  				newKey = []byte(key)
   307  			}
   308  			newKey[i] = SlsMetricstoreInvalidReplaceCharacter
   309  		}
   310  	}
   311  	if newKey == nil {
   312  		return key
   313  	}
   314  	return util.ZeroCopyBytesToString(newKey)
   315  }
   316  
   317  func formatLabelValue(value string) string {
   318  	if !config.LoongcollectorGlobalConfig.EnableSlsMetricsFormat {
   319  		return value
   320  	}
   321  	var newValue []byte
   322  	for i := 0; i < len(value); i++ {
   323  		b := value[i]
   324  		if b != '|' {
   325  			continue
   326  		} else {
   327  			if newValue == nil {
   328  				newValue = []byte(value)
   329  			}
   330  			newValue[i] = SlsMetricstoreInvalidReplaceCharacter
   331  		}
   332  	}
   333  	if newValue == nil {
   334  		return value
   335  	}
   336  	return util.ZeroCopyBytesToString(newValue)
   337  }
   338  
   339  func formatNewMetricName(name string) string {
   340  	if !config.LoongcollectorGlobalConfig.EnableSlsMetricsFormat {
   341  		return name
   342  	}
   343  	var newName []byte
   344  	for i := 0; i < len(name); i++ {
   345  		b := name[i]
   346  		if (b >= 'a' && b <= 'z') ||
   347  			(b >= 'A' && b <= 'Z') ||
   348  			(b >= '0' && b <= '9') ||
   349  			b == '_' ||
   350  			b == ':' {
   351  			continue
   352  		} else {
   353  			if newName == nil {
   354  				newName = []byte(name)
   355  			}
   356  			newName[i] = SlsMetricstoreInvalidReplaceCharacter
   357  		}
   358  	}
   359  	if newName == nil {
   360  		return name
   361  	}
   362  	return util.ZeroCopyBytesToString(newName)
   363  }
   364  
   365  // ReplaceInvalidChars analog of invalidChars = regexp.MustCompile("[^a-zA-Z0-9_]")
   366  func ReplaceInvalidChars(in *string) {
   367  	for charIndex, char := range *in {
   368  		charInt := int(char)
   369  		if !((charInt >= 97 && charInt <= 122) || // a-z
   370  			(charInt >= 65 && charInt <= 90) || // A-Z
   371  			(charInt >= 48 && charInt <= 57) || // 0-9
   372  			charInt == 95 || charInt == ':') { // _
   373  
   374  			*in = (*in)[:charIndex] + "_" + (*in)[charIndex+1:]
   375  		}
   376  	}
   377  }
   378  
   379  func GetMetricName(log *protocol.Log) string {
   380  	for _, cnt := range log.Contents {
   381  		if cnt.GetKey() == selfmonitor.SelfMetricNameKey {
   382  			return cnt.GetValue()
   383  		}
   384  	}
   385  	return ""
   386  }
   387  
   388  func LogContentsToMap(contents []*protocol.Log_Content) map[string]string {
   389  	result := make(map[string]string)
   390  	for _, content := range contents {
   391  		result[content.GetKey()] = content.GetValue()
   392  	}
   393  	return result
   394  }