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 }