github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/cmd/fluent-bit/loki.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"sort"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/go-kit/log"
    14  	"github.com/go-kit/log/level"
    15  	"github.com/go-logfmt/logfmt"
    16  	jsoniter "github.com/json-iterator/go"
    17  	"github.com/prometheus/common/model"
    18  	"github.com/weaveworks/common/logging"
    19  
    20  	"github.com/grafana/loki/clients/pkg/promtail/api"
    21  	"github.com/grafana/loki/clients/pkg/promtail/client"
    22  
    23  	"github.com/grafana/loki/pkg/util"
    24  
    25  	"github.com/grafana/loki/pkg/logproto"
    26  )
    27  
    28  var (
    29  	lineReplacer = strings.NewReplacer(`\n`, "\n", `\t`, "\t")
    30  	keyReplacer  = strings.NewReplacer("/", "_", ".", "_", "-", "_")
    31  )
    32  
    33  type loki struct {
    34  	cfg    *config
    35  	client client.Client
    36  	logger log.Logger
    37  }
    38  
    39  func newPlugin(cfg *config, logger log.Logger, metrics *client.Metrics) (*loki, error) {
    40  	client, err := NewClient(cfg, logger, metrics, nil)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	return &loki{
    45  		cfg:    cfg,
    46  		client: client,
    47  		logger: logger,
    48  	}, nil
    49  }
    50  
    51  // sendRecord send fluentbit records to loki as an entry.
    52  func (l *loki) sendRecord(r map[interface{}]interface{}, ts time.Time) error {
    53  	records := toStringMap(r)
    54  	level.Debug(l.logger).Log("msg", "processing records", "records", fmt.Sprintf("%+v", records))
    55  	lbs := model.LabelSet{}
    56  	if l.cfg.autoKubernetesLabels {
    57  		err := autoLabels(records, lbs)
    58  		if err != nil {
    59  			level.Error(l.logger).Log("msg", err.Error(), "records", fmt.Sprintf("%+v", records))
    60  		}
    61  	} else if l.cfg.labelMap != nil {
    62  		mapLabels(records, l.cfg.labelMap, lbs)
    63  	} else {
    64  		lbs = extractLabels(records, l.cfg.labelKeys)
    65  	}
    66  	removeKeys(records, append(l.cfg.labelKeys, l.cfg.removeKeys...))
    67  	if len(records) == 0 {
    68  		return nil
    69  	}
    70  	if l.cfg.dropSingleKey && len(records) == 1 {
    71  		for _, v := range records {
    72  			l.client.Chan() <- api.Entry{
    73  				Labels: lbs,
    74  				Entry: logproto.Entry{
    75  					Timestamp: ts,
    76  					Line:      fmt.Sprintf("%v", v),
    77  				},
    78  			}
    79  			return nil
    80  		}
    81  	}
    82  	line, err := l.createLine(records, l.cfg.lineFormat)
    83  	if err != nil {
    84  		return fmt.Errorf("error creating line: %v", err)
    85  	}
    86  	l.client.Chan() <- api.Entry{
    87  		Labels: lbs,
    88  		Entry: logproto.Entry{
    89  			Timestamp: ts,
    90  			Line:      line,
    91  		},
    92  	}
    93  	return nil
    94  }
    95  
    96  // prevent base64-encoding []byte values (default json.Encoder rule) by
    97  // converting them to strings
    98  func toStringSlice(slice []interface{}) []interface{} {
    99  	var s []interface{}
   100  	for _, v := range slice {
   101  		switch t := v.(type) {
   102  		case []byte:
   103  			s = append(s, string(t))
   104  		case map[interface{}]interface{}:
   105  			s = append(s, toStringMap(t))
   106  		case []interface{}:
   107  			s = append(s, toStringSlice(t))
   108  		default:
   109  			s = append(s, t)
   110  		}
   111  	}
   112  	return s
   113  }
   114  
   115  func toStringMap(record map[interface{}]interface{}) map[string]interface{} {
   116  	m := make(map[string]interface{})
   117  	for k, v := range record {
   118  		key, ok := k.(string)
   119  		if !ok {
   120  			continue
   121  		}
   122  		switch t := v.(type) {
   123  		case []byte:
   124  			m[key] = string(t)
   125  		case map[interface{}]interface{}:
   126  			m[key] = toStringMap(t)
   127  		case []interface{}:
   128  			m[key] = toStringSlice(t)
   129  		default:
   130  			m[key] = v
   131  		}
   132  	}
   133  
   134  	return m
   135  }
   136  
   137  func autoLabels(records map[string]interface{}, kuberneteslbs model.LabelSet) error {
   138  	kube, ok := records["kubernetes"]
   139  	if !ok {
   140  		return errors.New("kubernetes labels not found, no labels will be added")
   141  	}
   142  
   143  	for k, v := range kube.(map[string]interface{}) {
   144  		switch k {
   145  		case "labels":
   146  			for m, n := range v.(map[string]interface{}) {
   147  				kuberneteslbs[model.LabelName(keyReplacer.Replace(m))] = model.LabelValue(fmt.Sprintf("%v", n))
   148  			}
   149  		case "docker_id", "pod_id", "annotations":
   150  			// do nothing
   151  			continue
   152  		default:
   153  			kuberneteslbs[model.LabelName(k)] = model.LabelValue(fmt.Sprintf("%v", v))
   154  		}
   155  	}
   156  
   157  	return nil
   158  }
   159  
   160  func extractLabels(records map[string]interface{}, keys []string) model.LabelSet {
   161  	res := model.LabelSet{}
   162  	for _, k := range keys {
   163  		v, ok := records[k]
   164  		if !ok {
   165  			continue
   166  		}
   167  		ln := model.LabelName(k)
   168  		// skips invalid name and values
   169  		if !ln.IsValid() {
   170  			continue
   171  		}
   172  		lv := model.LabelValue(fmt.Sprintf("%v", v))
   173  		if !lv.IsValid() {
   174  			continue
   175  		}
   176  		res[ln] = lv
   177  	}
   178  	return res
   179  }
   180  
   181  // mapLabels convert records into labels using a json map[string]interface{} mapping
   182  func mapLabels(records map[string]interface{}, mapping map[string]interface{}, res model.LabelSet) {
   183  	for k, v := range mapping {
   184  		switch nextKey := v.(type) {
   185  		// if the next level is a map we are expecting we need to move deeper in the tree
   186  		case map[string]interface{}:
   187  			if nextValue, ok := records[k].(map[string]interface{}); ok {
   188  				// recursively search through the next level map.
   189  				mapLabels(nextValue, nextKey, res)
   190  			}
   191  		// we found a value in the mapping meaning we need to save the corresponding record value for the given key.
   192  		case string:
   193  			if value, ok := getRecordValue(k, records); ok {
   194  				lName := model.LabelName(nextKey)
   195  				lValue := model.LabelValue(value)
   196  				if lValue.IsValid() && lName.IsValid() {
   197  					res[lName] = lValue
   198  				}
   199  			}
   200  		}
   201  	}
   202  }
   203  
   204  func getRecordValue(key string, records map[string]interface{}) (string, bool) {
   205  	if value, ok := records[key]; ok {
   206  		switch typedVal := value.(type) {
   207  		case string:
   208  			return typedVal, true
   209  		case []byte:
   210  			return string(typedVal), true
   211  		default:
   212  			return fmt.Sprintf("%v", typedVal), true
   213  		}
   214  	}
   215  	return "", false
   216  }
   217  
   218  func removeKeys(records map[string]interface{}, keys []string) {
   219  	for _, k := range keys {
   220  		delete(records, k)
   221  	}
   222  }
   223  
   224  func (l *loki) createLine(records map[string]interface{}, f format) (string, error) {
   225  	switch f {
   226  	case jsonFormat:
   227  		for k, v := range records {
   228  			if s, ok := v.(string); ok && (strings.Contains(s, "{") || strings.Contains(s, "[")) {
   229  				var data interface{}
   230  				err := json.Unmarshal([]byte(s), &data)
   231  				if err != nil {
   232  					// keep this debug as it can be very verbose
   233  					level.Debug(l.logger).Log("msg", "error unmarshalling json", "err", err)
   234  					continue
   235  				}
   236  				records[k] = data
   237  			}
   238  		}
   239  		js, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(records)
   240  		if err != nil {
   241  			return "", err
   242  		}
   243  		return string(js), nil
   244  	case kvPairFormat:
   245  		buf := &bytes.Buffer{}
   246  		enc := logfmt.NewEncoder(buf)
   247  		keys := make([]string, 0, len(records))
   248  		for k := range records {
   249  			keys = append(keys, k)
   250  		}
   251  		sort.Strings(keys)
   252  		for _, k := range keys {
   253  			err := enc.EncodeKeyval(k, records[k])
   254  			if err == logfmt.ErrUnsupportedValueType {
   255  				err := enc.EncodeKeyval(k, fmt.Sprintf("%+v", records[k]))
   256  				if err != nil {
   257  					return "", nil
   258  				}
   259  				continue
   260  			}
   261  			if err != nil {
   262  				return "", nil
   263  			}
   264  		}
   265  		return lineReplacer.Replace(buf.String()), nil
   266  	default:
   267  		return "", fmt.Errorf("invalid line format: %v", f)
   268  	}
   269  }
   270  
   271  func newLogger(logLevel logging.Level) log.Logger {
   272  	logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
   273  	logger = level.NewFilter(logger, util.LogFilter(logLevel.String()))
   274  	logger = log.With(logger, "caller", log.Caller(3))
   275  	return logger
   276  }