github.com/GuanceCloud/cliutils@v1.1.21/lineproto/lineproto.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the MIT License. 3 // This product includes software developed at Guance Cloud (https://www.guance.com/). 4 // Copyright 2021-present Guance, Inc. 5 6 // Package lineproto wraps influxdb lineprotocol. 7 // Deprecated: use point package. 8 package lineproto 9 10 import ( 11 "bytes" 12 "fmt" 13 "math" 14 "reflect" 15 "sort" 16 "strings" 17 "time" 18 19 "github.com/influxdata/influxdb1-client/models" 20 influxdb "github.com/influxdata/influxdb1-client/v2" 21 ) 22 23 type ( 24 Callback func(models.Point) (models.Point, error) 25 CallbackV2 func(point *Point) (*Point, error) 26 ) 27 28 type Option struct { 29 Time time.Time 30 31 DisabledTagKeys []string 32 DisabledFieldKeys []string 33 34 Precision string 35 ExtraTags map[string]string 36 Callback Callback 37 CallbackV2 CallbackV2 38 PrecisionV2 Precision 39 40 Strict bool 41 EnablePointInKey bool 42 DisableStringField bool // disable string field value 43 44 MaxTags, 45 MaxFields, 46 MaxTagKeyLen, 47 MaxFieldKeyLen, 48 MaxTagValueLen, 49 MaxFieldValueLen int 50 } 51 52 type OptionSetter func(opt *Option) 53 54 func WithTime(time time.Time) OptionSetter { 55 return func(opt *Option) { 56 opt.Time = time 57 } 58 } 59 60 func WithPrecision(precision string) OptionSetter { 61 return func(opt *Option) { 62 opt.Precision = precision 63 } 64 } 65 66 func WithPrecisionV2(precision Precision) OptionSetter { 67 return func(opt *Option) { 68 opt.PrecisionV2 = precision 69 } 70 } 71 72 func WithExtraTags(extraTags map[string]string) OptionSetter { 73 return func(opt *Option) { 74 opt.ExtraTags = extraTags 75 } 76 } 77 78 func WithDisabledTagKeys(disabledTagKeys []string) OptionSetter { 79 return func(opt *Option) { 80 opt.DisabledTagKeys = disabledTagKeys 81 } 82 } 83 84 func WithDisabledFieldKeys(disabledFieldKeys []string) OptionSetter { 85 return func(opt *Option) { 86 opt.DisabledFieldKeys = disabledFieldKeys 87 } 88 } 89 90 func WithStrict(b bool) OptionSetter { 91 return func(opt *Option) { 92 opt.Strict = b 93 } 94 } 95 96 func WithEnablePointInKey(b bool) OptionSetter { 97 return func(opt *Option) { 98 opt.EnablePointInKey = b 99 } 100 } 101 102 func WithDisableStringField(disableStringField bool) OptionSetter { 103 return func(opt *Option) { 104 opt.DisableStringField = disableStringField 105 } 106 } 107 108 func WithCallback(callback Callback) OptionSetter { 109 return func(opt *Option) { 110 opt.Callback = callback 111 } 112 } 113 114 func WithCallbackV2(callback CallbackV2) OptionSetter { 115 return func(opt *Option) { 116 opt.CallbackV2 = callback 117 } 118 } 119 120 func WithMaxTags(maxTags int) OptionSetter { 121 return func(opt *Option) { 122 opt.MaxTags = maxTags 123 } 124 } 125 126 func WithMaxFields(maxFields int) OptionSetter { 127 return func(opt *Option) { 128 opt.MaxFields = maxFields 129 } 130 } 131 132 func WithMaxTagKeyLen(maxTagKeyLen int) OptionSetter { 133 return func(opt *Option) { 134 opt.MaxTagKeyLen = maxTagKeyLen 135 } 136 } 137 138 func WithMaxFieldKeyLen(maxFieldKeyLen int) OptionSetter { 139 return func(opt *Option) { 140 opt.MaxFieldKeyLen = maxFieldKeyLen 141 } 142 } 143 144 func WithMaxTagValueLen(maxTagValueLen int) OptionSetter { 145 return func(opt *Option) { 146 opt.MaxTagValueLen = maxTagValueLen 147 } 148 } 149 150 func WithMaxFieldValueLen(maxFieldValueLen int) OptionSetter { 151 return func(opt *Option) { 152 opt.MaxFieldValueLen = maxFieldValueLen 153 } 154 } 155 156 type PointWarning struct { 157 WarningType string 158 Message string 159 } 160 161 const ( 162 WarnMaxTags = "warn_exceed_max_tags" 163 WarnMaxFields = "warn_exceed_max_fields" 164 WarnMaxTagKeyLen = "warn_exceed_max_tag_key_len" 165 WarnMaxFieldKeyLen = "warn_exceed_max_field_key_len" 166 WarnMaxTagValueLen = "warn_exceed_max_tag_value_len" 167 WarnMaxFieldValueLen = "warn_exceed_max_field_value_len" 168 WarnMaxFieldValueInt = "warn_exceed_max_field_value_int" 169 WarnSameTagFieldKey = "warn_same_tag_field_key" 170 WarnInvalidFieldValueType = "warn_invalid_field_value_type" 171 ) 172 173 var DefaultOption = NewDefaultOption() 174 175 func NewDefaultOption() *Option { 176 return &Option{ 177 Strict: true, 178 Precision: "n", 179 PrecisionV2: Nanosecond, 180 181 MaxTags: 256, 182 MaxFields: 1024, 183 184 MaxTagKeyLen: 256, 185 MaxFieldKeyLen: 256, 186 187 MaxTagValueLen: 1024, 188 MaxFieldValueLen: 32 * 1024, // 32K 189 } 190 } 191 192 func (opt *Option) checkDisabledField(f string) error { 193 for _, x := range opt.DisabledFieldKeys { 194 if f == x { 195 return fmt.Errorf("field key `%s' disabled", f) 196 } 197 } 198 return nil 199 } 200 201 func (opt *Option) checkDisabledTag(t string) error { 202 for _, x := range opt.DisabledTagKeys { 203 if t == x { 204 return fmt.Errorf("tag key `%s' disabled", t) 205 } 206 } 207 return nil 208 } 209 210 func ParsePoints(data []byte, opt *Option) ([]*influxdb.Point, error) { 211 if len(data) == 0 { 212 return nil, fmt.Errorf("empty data") 213 } 214 215 if opt == nil { 216 opt = DefaultOption 217 } 218 219 if opt.MaxFields <= 0 { 220 opt.MaxFields = 1024 221 } 222 223 if opt.MaxTags <= 0 { 224 opt.MaxTags = 256 225 } 226 227 ptTime := opt.Time 228 if opt.Time.IsZero() { 229 ptTime = time.Now() 230 } 231 232 points, err := models.ParsePointsWithPrecision(data, ptTime, opt.Precision) 233 if err != nil { 234 return nil, err 235 } 236 237 res := []*influxdb.Point{} 238 for _, point := range points { 239 if opt.ExtraTags != nil { 240 for k, v := range opt.ExtraTags { 241 if !point.HasTag([]byte(k)) { 242 point.AddTag(k, v) 243 } 244 } 245 } 246 247 if opt.Callback != nil { 248 newPoint, err := opt.Callback(point) 249 if err != nil { 250 return nil, err 251 } 252 point = newPoint 253 } 254 255 if point == nil { 256 return nil, fmt.Errorf("line point is empty") 257 } 258 259 if err := checkPoint(point, opt); err != nil { 260 return nil, err 261 } 262 263 res = append(res, influxdb.NewPointFrom(point)) 264 } 265 266 return res, nil 267 } 268 269 func MakeLineProtoPoint(name string, 270 tags map[string]string, 271 fields map[string]interface{}, 272 opt *Option, 273 ) (*influxdb.Point, error) { 274 pt, _, err := MakeLineProtoPointWithWarnings(name, tags, fields, opt) 275 return pt, err 276 } 277 278 func MakeLineProtoPointWithWarnings(name string, 279 tags map[string]string, 280 fields map[string]interface{}, 281 opt *Option, 282 ) (pt *influxdb.Point, warnings []*PointWarning, err error) { 283 warnings = []*PointWarning{} 284 285 if name == "" { 286 err = fmt.Errorf("empty measurement name") 287 return 288 } 289 290 if opt == nil { 291 opt = DefaultOption 292 } 293 294 // add extra tags 295 if opt.ExtraTags != nil { 296 if tags == nil { 297 tags = opt.ExtraTags 298 } else { 299 for k, v := range opt.ExtraTags { 300 if _, ok := tags[k]; !ok { // NOTE: do-not-override exist tag 301 tags[k] = v 302 } 303 } 304 } 305 } 306 307 if opt.MaxTags <= 0 { 308 opt.MaxTags = 256 309 } 310 if opt.MaxFields <= 0 { 311 opt.MaxFields = 1024 312 } 313 314 if err = checkTags(tags, opt, &warnings); err != nil { 315 return 316 } 317 318 if err = checkFields(fields, opt, &warnings); err != nil { 319 return 320 } 321 322 if err = checkTagFieldSameKey(tags, fields, &warnings); err != nil { 323 return 324 } 325 326 if opt.Time.IsZero() { 327 pt, err = influxdb.NewPoint(name, tags, fields, time.Now().UTC()) 328 return 329 } else { 330 pt, err = influxdb.NewPoint(name, tags, fields, opt.Time) 331 return 332 } 333 } 334 335 func MakeLineProtoPointV2(name string, 336 tags map[string]string, 337 fields map[string]interface{}, 338 opt *Option, 339 ) (*Point, error) { 340 pt, _, err := MakeLineProtoPointWithWarningsV2(name, tags, fields, opt) 341 return pt, err 342 } 343 344 func MakeLineProtoPointWithWarningsV2(name string, 345 tags map[string]string, 346 fields map[string]interface{}, 347 opt *Option, 348 ) (pt *Point, warnings []*PointWarning, err error) { 349 warnings = []*PointWarning{} 350 351 if name == "" { 352 err = fmt.Errorf("empty measurement name") 353 return 354 } 355 356 if opt == nil { 357 opt = DefaultOption 358 } 359 360 // add extra tags 361 if opt.ExtraTags != nil { 362 if tags == nil { 363 tags = opt.ExtraTags 364 } else { 365 for k, v := range opt.ExtraTags { 366 if _, ok := tags[k]; !ok { // NOTE: do-not-override exist tag 367 tags[k] = v 368 } 369 } 370 } 371 } 372 373 if opt.MaxTags <= 0 { 374 opt.MaxTags = 256 375 } 376 if opt.MaxFields <= 0 { 377 opt.MaxFields = 1024 378 } 379 380 if err = checkTags(tags, opt, &warnings); err != nil { 381 return 382 } 383 384 if err = checkFields(fields, opt, &warnings); err != nil { 385 return 386 } 387 388 if err = checkTagFieldSameKey(tags, fields, &warnings); err != nil { 389 return 390 } 391 392 if opt.Time.IsZero() { 393 pt, err = NewPoint(name, tags, fields, time.Now().UTC()) 394 return 395 } else { 396 pt, err = NewPoint(name, tags, fields, opt.Time) 397 return 398 } 399 } 400 401 func checkPoint(p models.Point, opt *Option) error { 402 // check if same key in tags and fields 403 fs, err := p.Fields() 404 if err != nil { 405 return err 406 } 407 408 if len(fs) > opt.MaxFields { 409 return fmt.Errorf("exceed max field count(%d), got %d tags", opt.MaxFields, len(fs)) 410 } 411 412 for k := range fs { 413 if p.HasTag([]byte(k)) { 414 return fmt.Errorf("same key `%s' in tag and field", k) 415 } 416 417 // enable `.' in time serial metric 418 if strings.Contains(k, ".") && !opt.EnablePointInKey { 419 return fmt.Errorf("invalid field key `%s': found `.'", k) 420 } 421 422 if err := opt.checkDisabledField(k); err != nil { 423 return err 424 } 425 } 426 427 // check if dup keys in fields 428 fi := p.FieldIterator() 429 fcnt := 0 430 for fi.Next() { 431 fcnt++ 432 } 433 434 if fcnt != len(fs) { 435 return fmt.Errorf("unmached field count, expect %d, got %d", fcnt, len(fs)) 436 } 437 438 // add more point checking here... 439 tags := p.Tags() 440 if len(tags) > opt.MaxTags { 441 return fmt.Errorf("exceed max tag count(%d), got %d tags", opt.MaxTags, len(tags)) 442 } 443 444 for _, t := range tags { 445 if bytes.IndexByte(t.Key, byte('.')) != -1 && !opt.EnablePointInKey { 446 return fmt.Errorf("invalid tag key `%s': found `.'", string(t.Key)) 447 } 448 449 if err := opt.checkDisabledTag(string(t.Key)); err != nil { 450 return err 451 } 452 } 453 454 return nil 455 } 456 457 func checkPointV2(p *Point, opt *Option) error { 458 // check if same key in tags and fields 459 fs := p.Fields 460 461 if len(fs) > opt.MaxFields { 462 return fmt.Errorf("exceed max field count(%d), got %d tags", opt.MaxFields, len(fs)) 463 } 464 465 for k := range fs { 466 if _, ok := p.Tags[k]; ok { 467 return fmt.Errorf("same key `%s' in tag and field", k) 468 } 469 470 // enable `.' in time serial metric 471 if strings.Contains(k, ".") && !opt.EnablePointInKey { 472 return fmt.Errorf("invalid field key `%s': found `.'", k) 473 } 474 475 if err := opt.checkDisabledField(k); err != nil { 476 return err 477 } 478 } 479 480 // add more point checking here... 481 tags := p.Tags 482 if len(tags) > opt.MaxTags { 483 return fmt.Errorf("exceed max tag count(%d), got %d tags", opt.MaxTags, len(tags)) 484 } 485 486 for key := range tags { 487 if strings.IndexByte(key, '.') != -1 && !opt.EnablePointInKey { 488 return fmt.Errorf("invalid tag key `%s': found `.'", key) 489 } 490 491 if err := opt.checkDisabledTag(key); err != nil { 492 return err 493 } 494 } 495 496 return nil 497 } 498 499 func checkTagFieldSameKey(tags map[string]string, fields map[string]interface{}, warnings *[]*PointWarning) error { 500 if tags == nil || fields == nil { 501 return nil 502 } 503 504 for k := range tags { 505 // delete same key from fields 506 if _, ok := fields[k]; ok { 507 *warnings = append(*warnings, &PointWarning{ 508 WarningType: WarnSameTagFieldKey, 509 Message: fmt.Sprintf("same key `%s' in tag and field, ", k), 510 }) 511 512 delete(fields, k) 513 } 514 } 515 516 return nil 517 } 518 519 func trimSuffixAll(s, sfx string) string { 520 var x string 521 for { 522 x = strings.TrimSuffix(s, sfx) 523 if x == s { 524 break 525 } 526 s = x 527 } 528 return x 529 } 530 531 func checkField(k string, v interface{}, opt *Option, pointWarnings *[]*PointWarning) (interface{}, error) { 532 if strings.Contains(k, ".") && !opt.EnablePointInKey { 533 return nil, fmt.Errorf("invalid field key `%s': found `.'", k) 534 } 535 536 if err := opt.checkDisabledField(k); err != nil { 537 return nil, err 538 } 539 540 switch x := v.(type) { 541 case uint64: 542 if x > uint64(math.MaxInt64) { 543 if opt.Strict { 544 return nil, fmt.Errorf("too large int field: key=%s, value=%d(> %d)", 545 k, x, uint64(math.MaxInt64)) 546 } 547 548 *pointWarnings = append(*pointWarnings, &PointWarning{ 549 WarningType: WarnMaxFieldValueInt, 550 Message: fmt.Sprintf("too large int field: key=%s, field dropped", k), 551 }) 552 553 return nil, nil // drop the field 554 } else { 555 // Force convert uint64 to int64: to disable line proto like 556 // `abc,tag=1 f1=32u` 557 // expected is: 558 // `abc,tag=1 f1=32i` 559 return int64(x), nil 560 } 561 562 case int, int8, int16, int32, int64, 563 uint, uint8, uint16, uint32, 564 bool, float32, float64: 565 return v, nil 566 567 case string: 568 if opt.DisableStringField { 569 *pointWarnings = append(*pointWarnings, &PointWarning{ 570 WarningType: WarnInvalidFieldValueType, 571 Message: fmt.Sprintf("field(%s) dropped with string value, when [DisableStringField] enabled", k), 572 }) 573 return nil, nil // drop the field 574 } 575 576 if len(x) > opt.MaxFieldValueLen && opt.MaxFieldValueLen > 0 { 577 *pointWarnings = append(*pointWarnings, &PointWarning{ 578 WarningType: WarnMaxFieldValueLen, 579 Message: fmt.Sprintf("field (%s) exceed max field value length(%d), got %d, value truncated", k, opt.MaxFieldValueLen, len(x)), 580 }) 581 582 return x[:opt.MaxFieldValueLen], nil 583 } 584 return v, nil 585 586 default: 587 if opt.Strict { 588 if v == nil { 589 *pointWarnings = append(*pointWarnings, &PointWarning{ 590 WarningType: WarnInvalidFieldValueType, 591 Message: fmt.Sprintf("invalid field (%s) value type: nil value, field dropped", k), 592 }) 593 594 return nil, fmt.Errorf("invalid field value type %s, value is nil", k) 595 } else { 596 *pointWarnings = append(*pointWarnings, &PointWarning{ 597 WarningType: WarnInvalidFieldValueType, 598 Message: fmt.Sprintf("invalid field (%s) type: %s, field dropped", k, reflect.TypeOf(v).String()), 599 }) 600 601 return nil, fmt.Errorf("invalid field(%s) type: %s", k, reflect.TypeOf(v).String()) 602 } 603 } 604 605 return nil, nil 606 } 607 } 608 609 func checkFields(fields map[string]interface{}, opt *Option, pointWarnings *[]*PointWarning) error { 610 // warnings: WarnMaxFields 611 warnings := []*PointWarning{} 612 613 // delete extra key 614 if opt.MaxFields > 0 && len(fields) > opt.MaxFields { 615 var keys []string 616 for k := range fields { 617 keys = append(keys, k) 618 } 619 620 sort.Strings(keys) 621 622 deleteKeys := keys[opt.MaxFields:] 623 624 for _, k := range deleteKeys { 625 delete(fields, k) 626 } 627 628 warnings = append(warnings, &PointWarning{ 629 WarningType: WarnMaxFields, 630 Message: fmt.Sprintf("exceed max field count(%d), got %d fields, extra fields deleted", opt.MaxFields, len(fields)), 631 }) 632 } 633 634 for k, v := range fields { 635 // trim key 636 if opt.MaxFieldKeyLen > 0 && len(k) > opt.MaxFieldKeyLen { 637 warnings = append(warnings, &PointWarning{ 638 WarningType: WarnMaxFieldKeyLen, 639 Message: fmt.Sprintf("exceed max field key length(%d), got %d, key truncated", opt.MaxFieldKeyLen, len(k)), 640 }) 641 642 delete(fields, k) 643 k = k[:opt.MaxFieldKeyLen] 644 fields[k] = v 645 } 646 647 if x, err := checkField(k, v, opt, &warnings); err != nil { 648 return err 649 } else { 650 if x == nil { 651 delete(fields, k) 652 } else { 653 fields[k] = x 654 } 655 } 656 } 657 658 if pointWarnings != nil { 659 *pointWarnings = append(*pointWarnings, warnings...) 660 } 661 662 return nil 663 } 664 665 func checkTags(tags map[string]string, opt *Option, pointWarnings *[]*PointWarning) error { 666 // warnings: WarnMaxTags, WarnMaxTagKeyLen, WarnMaxTagKeyLen 667 warnings := []*PointWarning{} 668 // delete extra key 669 if len(tags) > opt.MaxTags { 670 var keys []string 671 for k := range tags { 672 keys = append(keys, k) 673 } 674 675 sort.Strings(keys) 676 677 deleteKeys := keys[opt.MaxTags:] 678 679 for _, k := range deleteKeys { 680 delete(tags, k) 681 } 682 683 warnings = append(warnings, &PointWarning{ 684 WarningType: WarnMaxTags, 685 Message: fmt.Sprintf("exceed max tag count(%d), got %d tags, extra tags deleted", opt.MaxTags, len(tags)), 686 }) 687 } 688 689 for k, v := range tags { 690 if opt.MaxTagKeyLen > 0 && len(k) > opt.MaxTagKeyLen { 691 warnings = append(warnings, &PointWarning{ 692 WarningType: WarnMaxTagKeyLen, 693 Message: fmt.Sprintf("exceed max tag key length(%d), got %d, key truncated", opt.MaxTagKeyLen, len(k)), 694 }) 695 696 delete(tags, k) 697 k = k[:opt.MaxTagKeyLen] 698 tags[k] = v 699 } 700 701 if opt.MaxTagValueLen > 0 && len(v) > opt.MaxTagValueLen { 702 tags[k] = v[:opt.MaxTagValueLen] 703 warnings = append(warnings, &PointWarning{ 704 WarningType: WarnMaxTagValueLen, 705 Message: fmt.Sprintf("exceed max tag value length(%d), got %d, value truncated", opt.MaxTagValueLen, len(v)), 706 }) 707 } 708 709 // check tag key '\', '\n' 710 if strings.HasSuffix(k, `\`) || strings.Contains(k, "\n") { 711 if !opt.Strict { 712 delete(tags, k) 713 k = adjustKV(k) 714 tags[k] = v 715 } else { 716 return fmt.Errorf("invalid tag key `%s'", k) 717 } 718 } 719 720 // check tag value: '\', '\n' 721 if strings.HasSuffix(v, `\`) || strings.Contains(v, "\n") { 722 if !opt.Strict { 723 tags[k] = adjustKV(v) 724 } else { 725 return fmt.Errorf("invalid tag value `%s'", v) 726 } 727 } 728 729 // not recoverable if `.' exists! 730 if strings.Contains(k, ".") && !opt.EnablePointInKey { 731 return fmt.Errorf("invalid tag key `%s': found `.'", k) 732 } 733 734 if err := opt.checkDisabledTag(k); err != nil { 735 return err 736 } 737 } 738 739 if pointWarnings != nil { 740 *pointWarnings = append(*pointWarnings, warnings...) 741 } 742 743 return nil 744 } 745 746 // Remove all `\` suffix on key/val 747 // Replace all `\n` with ` `. 748 func adjustKV(x string) string { 749 if strings.HasSuffix(x, `\`) { 750 x = trimSuffixAll(x, `\`) 751 } 752 753 if strings.Contains(x, "\n") { 754 x = strings.ReplaceAll(x, "\n", " ") 755 } 756 757 return x 758 }