diff --git a/internal/config/conf.go b/internal/config/conf.go index 93b7c0db15..126159aeee 100644 --- a/internal/config/conf.go +++ b/internal/config/conf.go @@ -466,6 +466,8 @@ func (c *Config) ApplyMainConfig() error { c.Operator = operator.NewOperatorClientFromEnv() // 初始化 ENC 密码加密功能。 initCrypto(c) + // + fillPipelineConfig(c) return nil } diff --git a/internal/config/env.go b/internal/config/env.go index 3ae977c738..9bc844fa29 100644 --- a/internal/config/env.go +++ b/internal/config/env.go @@ -128,6 +128,11 @@ func (c *Config) loadConfdEnvs() { } func (c *Config) loadPprofEnvs() { + if v := datakit.GetEnv("ENV_ENABLE_DEBUG_FIELDS"); v != "" { + b, _ := strconv.ParseBool(v) + c.EnableDebugFields = b + } + if v := datakit.GetEnv("ENV_ENABLE_PPROF"); v != "" { c.EnablePProf = true } @@ -212,10 +217,6 @@ func (c *Config) loadPipelineEnvs() { c.Pipeline.SQLiteMemMode = true } - if v := datakit.GetEnv("ENV_PIPELINE_DISABLE_APPEND_RUN_INFO"); v != "" { - c.Pipeline.DisableAppendRunInfo = true - } - if v := datakit.GetEnv("ENV_PIPELINE_OFFLOAD_RECEIVER"); v != "" { if c.Pipeline.Offload == nil { c.Pipeline.Offload = &offload.OffloadConfig{ diff --git a/internal/config/env_test.go b/internal/config/env_test.go index 02cf046812..61838e3fa4 100644 --- a/internal/config/env_test.go +++ b/internal/config/env_test.go @@ -121,10 +121,9 @@ func TestLoadEnv(t *testing.T) { "ENV_HTTP_TLS_CRT": "/path/to/datakit/tls.crt", "ENV_HTTP_TLS_KEY": "/path/to/datakit/tls.key", - "ENV_ENABLE_ELECTION_NAMESPACE_TAG": "ok", - "ENV_PIPELINE_OFFLOAD_RECEIVER": offload.DKRcv, - "ENV_PIPELINE_DISABLE_APPEND_RUN_INFO": "true", - "ENV_PIPELINE_OFFLOAD_ADDRESSES": "http://aaa:123,http://1.2.3.4:1234", + "ENV_ENABLE_ELECTION_NAMESPACE_TAG": "ok", + "ENV_PIPELINE_OFFLOAD_RECEIVER": offload.DKRcv, + "ENV_PIPELINE_OFFLOAD_ADDRESSES": "http://aaa:123,http://1.2.3.4:1234", }, expect: func() *Config { cfg := DefaultConfig() @@ -159,7 +158,6 @@ func TestLoadEnv(t *testing.T) { cfg.Logging.RotateBackups = 10 cfg.Logging.Rotate = 128 - cfg.Pipeline.DisableAppendRunInfo = true cfg.Pipeline.Offload = &offload.OffloadConfig{} cfg.Pipeline.Offload.Receiver = offload.DKRcv cfg.Pipeline.Offload.Addresses = []string{"http://aaa:123", "http://1.2.3.4:1234"} diff --git a/internal/config/load.go b/internal/config/load.go index 5ef538e101..5706cd8d5a 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -306,3 +306,9 @@ func GetNamespacePipelineFiles(namespace string) ([]string, error) { } return nil, fmt.Errorf("invalid namespace") } + +func fillPipelineConfig(c *Config) { + if c.Pipeline != nil { + c.Pipeline.EnableDebugFields = c.EnableDebugFields + } +} diff --git a/internal/config/mainconf.go b/internal/config/mainconf.go index c8a310d6bb..bb1b00808c 100644 --- a/internal/config/mainconf.go +++ b/internal/config/mainconf.go @@ -50,6 +50,8 @@ type Config struct { PointPool *pointPool `toml:"point_pool"` + // debug + EnableDebugFields bool `toml:"enable_debug_fields,omitempty"` // pprof EnablePProf bool `toml:"enable_pprof"` PProfListen string `toml:"pprof_listen"` @@ -129,9 +131,10 @@ func DefaultConfig() *Config { GlobalHostTags: map[string]string{}, GlobalTagsDeprecated: map[string]string{}, - EnablePProf: true, - PProfListen: "localhost:6060", - DatakitUser: "root", + EnableDebugFields: false, + EnablePProf: true, + PProfListen: "localhost:6060", + DatakitUser: "root", Election: &election.ElectionCfg{ Enable: false, diff --git a/internal/pipeline/pl.go b/internal/pipeline/pl.go index 76c29df5f9..e03490d511 100644 --- a/internal/pipeline/pl.go +++ b/internal/pipeline/pl.go @@ -22,8 +22,7 @@ const ( plTagService = "_pl_service" plTagNS = "_pl_ns" plStatus = "_pl_status" - - plFieldCost = "_pl_cost" // data type: float64, unit: second + plFieldCost = "_pl_cost" // data type: float64, unit: second svcName = "datakit" diff --git a/internal/pipeline/plval/cfg.go b/internal/pipeline/plval/cfg.go index fde267eef9..48b929b2a3 100644 --- a/internal/pipeline/plval/cfg.go +++ b/internal/pipeline/plval/cfg.go @@ -39,7 +39,9 @@ type PipelineCfg struct { UseSQLite bool `toml:"use_sqlite"` SQLiteMemMode bool `toml:"sqlite_mem_mode"` Offload *offload.OffloadConfig `toml:"offload"` - DisableAppendRunInfo bool `toml:"disable_append_run_info"` + EnableDebugFields bool `toml:"_"` + + DeprecatedDisableAppendRunInfo bool `toml:"disable_append_run_info"` } // InitIPdb init ipdb instance. diff --git a/internal/pipeline/plval/plval.go b/internal/pipeline/plval/plval.go index 34e43d28e4..ddfc85a275 100644 --- a/internal/pipeline/plval/plval.go +++ b/internal/pipeline/plval/plval.go @@ -43,7 +43,7 @@ var ( // offload. _offloadWkr *offload.OffloadWorker - _enableAppendRunInfo bool = true + _enableAppendRunInfo bool = false ) func EnableAppendRunInfo() bool { @@ -120,8 +120,8 @@ func InitPlVal(cfg *PipelineCfg, upFn plmap.UploadFunc, gTags map[string]string, SetIPDB(ipdb) } - if cfg != nil && cfg.DisableAppendRunInfo { - _enableAppendRunInfo = false + if cfg != nil && cfg.EnableDebugFields { + _enableAppendRunInfo = true } // init refer-table diff --git a/internal/plugins/inputs/container/container.go b/internal/plugins/inputs/container/container.go index c39cccc1bc..4a5837ad3b 100644 --- a/internal/plugins/inputs/container/container.go +++ b/internal/plugins/inputs/container/container.go @@ -422,11 +422,7 @@ func (c *container) transformPoint(info *runtime.Container, setPodLabelAsTags fu } } - imageName, shortName, imageTag := runtime.ParseImage(image) p.SetTag("image", image) - p.SetTag("image_name", imageName) - p.SetTag("image_short_name", shortName) - p.SetTag("image_tag", imageTag) // only ecs fargate p.SetTagIfNotEmpty("aws_ecs_cluster_name", getAWSClusterNameForLabels(info.Labels)) diff --git a/internal/plugins/inputs/container/container_log.go b/internal/plugins/inputs/container/container_log.go index f768f90ad9..1a02a3606a 100644 --- a/internal/plugins/inputs/container/container_log.go +++ b/internal/plugins/inputs/container/container_log.go @@ -10,6 +10,7 @@ import ( "fmt" "time" + "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/config" "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/container/runtime" "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/goroutine" "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/logtail/fileprovider" @@ -50,6 +51,7 @@ func (c *container) tailingLogs(ins *logInstance) { tailer.WithSource(cfg.Source), tailer.WithService(cfg.Service), tailer.WithPipeline(cfg.Pipeline), + tailer.WithEnableDebugFields(config.Cfg.EnableDebugFields), tailer.WithCharacterEncoding(cfg.CharacterEncoding), tailer.WithMultilinePatterns(cfg.MultilinePatterns), tailer.WithGlobalTags(mergedTags), @@ -98,7 +100,6 @@ func (c *container) tailingLogs(ins *logInstance) { pathAtInside := trimLogsFromRootfs(file) if insidePath := joinInsideFilepath(cfg.hostDir, cfg.insideDir, pathAtInside); insidePath != pathAtInside { newOpts = append(newOpts, tailer.WithTag("inside_filepath", insidePath)) - newOpts = append(newOpts, tailer.WithTag("host_filepath", file)) } tail, err := tailer.NewTailerSingle(file, newOpts...) diff --git a/internal/plugins/inputs/container/container_measurement.go b/internal/plugins/inputs/container/container_measurement.go index 79451951b2..2061778523 100644 --- a/internal/plugins/inputs/container/container_measurement.go +++ b/internal/plugins/inputs/container/container_measurement.go @@ -131,12 +131,9 @@ func (*containerLog) Info() *inputs.MeasurementInfo { "daemonset": inputs.NewTagInfo("The name of the DaemonSet which the object belongs to."), }, Fields: map[string]interface{}{ - "status": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The status of the logging, only supported `info/emerg/alert/critical/error/warning/debug/OK/unknown`."}, - "log_read_lines": &inputs.FieldInfo{DataType: inputs.Int, Unit: inputs.NCount, Desc: "The lines of the read file ([:octicons-tag-24: Version-1.4.6](../datakit/changelog.md#cl-1.4.6))."}, - "log_read_offset": &inputs.FieldInfo{DataType: inputs.Int, Unit: inputs.UnknownUnit, Desc: "The offset of the read file ([:octicons-tag-24: Version-1.4.8](../datakit/changelog.md#cl-1.4.8) · [:octicons-beaker-24: Experimental](../datakit/index.md#experimental))."}, - "log_read_time": &inputs.FieldInfo{DataType: inputs.DurationSecond, Unit: inputs.UnknownUnit, Desc: "The timestamp of the read file."}, - "message_length": &inputs.FieldInfo{DataType: inputs.SizeByte, Unit: inputs.NCount, Desc: "The length of the message content."}, - "message": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The text of the logging."}, + "status": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The status of the logging, only supported `info/emerg/alert/critical/error/warning/debug/OK/unknown`."}, + "log_read_lines": &inputs.FieldInfo{DataType: inputs.Int, Unit: inputs.NCount, Desc: "The lines of the read file ([:octicons-tag-24: Version-1.4.6](../datakit/changelog.md#cl-1.4.6))."}, + "message": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The text of the logging."}, }, } } diff --git a/internal/plugins/inputs/container/log.go b/internal/plugins/inputs/container/log.go index e9aac956db..f294179822 100644 --- a/internal/plugins/inputs/container/log.go +++ b/internal/plugins/inputs/container/log.go @@ -214,12 +214,9 @@ func replaceLabelKey(s string) string { func (lc *logInstance) tags() map[string]string { m := map[string]string{ - "container_id": lc.id, - "container_name": lc.containerName, - "image": lc.image, - "image_name": lc.imageName, - "image_short_name": lc.imageShortName, - "image_tag": lc.imageTag, + "container_id": lc.id, + "container_name": lc.containerName, + "image": lc.image, } if lc.podName != "" { diff --git a/internal/plugins/inputs/logging/input.go b/internal/plugins/inputs/logging/input.go index 9a448bda08..1b4abf63dc 100644 --- a/internal/plugins/inputs/logging/input.go +++ b/internal/plugins/inputs/logging/input.go @@ -13,6 +13,7 @@ import ( "github.com/GuanceCloud/cliutils" "github.com/GuanceCloud/cliutils/logger" + "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/config" "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/datakit" "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/goroutine" "gitlab.jiagouyun.com/cloudcare-tools/datakit/internal/logtail/multiline" @@ -146,6 +147,7 @@ func (ipt *Input) Run() { tailer.WithSource(ipt.Source), tailer.WithService(ipt.Service), tailer.WithPipeline(ipt.Pipeline), + tailer.WithEnableDebugFields(config.Cfg.EnableDebugFields), tailer.WithSockets(ipt.Sockets), tailer.WithIgnoreStatus(ipt.IgnoreStatus), tailer.WithFromBeginning(ipt.FromBeginning), @@ -276,12 +278,9 @@ func (*loggingMeasurement) Info() *inputs.MeasurementInfo { "service": inputs.NewTagInfo("Use the `service` of the config."), }, Fields: map[string]interface{}{ - "message": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The text of the logging."}, - "status": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The status of the logging, default is `unknown`[^1]."}, - "log_read_lines": &inputs.FieldInfo{DataType: inputs.Int, Unit: inputs.NCount, Desc: "The lines of the read file."}, - "log_read_offset": &inputs.FieldInfo{DataType: inputs.Int, Unit: inputs.UnknownUnit, Desc: "The offset of the read file."}, - "log_read_time": &inputs.FieldInfo{DataType: inputs.DurationSecond, Unit: inputs.UnknownUnit, Desc: "The timestamp of the read file."}, - "message_length": &inputs.FieldInfo{DataType: inputs.SizeByte, Unit: inputs.NCount, Desc: "The length of the message content."}, + "message": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The text of the logging."}, + "status": &inputs.FieldInfo{DataType: inputs.String, Unit: inputs.UnknownUnit, Desc: "The status of the logging, default is `unknown`[^1]."}, + "log_read_lines": &inputs.FieldInfo{DataType: inputs.Int, Unit: inputs.NCount, Desc: "The lines of the read file."}, "`__docid`": &inputs.FieldInfo{ DataType: inputs.String, Unit: inputs.UnknownUnit, diff --git a/internal/tailer/option.go b/internal/tailer/option.go index 12745a5c8e..8cb444bc41 100644 --- a/internal/tailer/option.go +++ b/internal/tailer/option.go @@ -28,6 +28,8 @@ type option struct { // 解释文件内容时所使用的的字符编码,如果设置为空,将不进行转码处理 // e.g. "utf-8","utf-16le","utf-16be","gbk","gb18030" characterEncoding string + // 添加 debug 字段 + enableDebugFields bool // 匹配正则表达式 // 符合此正则匹配的数据,将被认定为有效数据。否则会累积追加到上一条有效数据的末尾 @@ -48,7 +50,7 @@ type option struct { // 判定不活跃文件 ignoreDeadLog time.Duration // 添加额外tag - globalTags map[string]string + extraTags map[string]string // 连续 N 次采集为空,就强制 flush 已有数据 maxForceFlushLimit int @@ -70,6 +72,7 @@ func WithIgnoreStatus(arr []string) Option { return func(opt *option) { opt.ig func WithPipeline(s string) Option { return func(opt *option) { opt.pipeline = s } } func WithCharacterEncoding(s string) Option { return func(opt *option) { opt.characterEncoding = s } } func WithFromBeginning(b bool) Option { return func(opt *option) { opt.fromBeginning = b } } +func WithEnableDebugFields(b bool) Option { return func(opt *option) { opt.enableDebugFields = b } } func WithTextParserMode(mode Mode) Option { return func(opt *option) { opt.mode = mode } } func WithSource(s string) Option { @@ -90,10 +93,10 @@ func WithService(s string) Option { s = opt.source } opt.service = s - if opt.globalTags == nil { - opt.globalTags = make(map[string]string) + if opt.extraTags == nil { + opt.extraTags = make(map[string]string) } - opt.globalTags["service"] = opt.service + opt.extraTags["service"] = opt.service } } @@ -144,17 +147,17 @@ func WithMaxForceFlushLimit(n int) Option { func WithGlobalTags(m map[string]string) Option { return func(opt *option) { for k, v := range m { - opt.globalTags[k] = v + opt.extraTags[k] = v } } } func WithTag(key, value string) Option { return func(opt *option) { - if opt.globalTags == nil { - opt.globalTags = make(map[string]string) + if opt.extraTags == nil { + opt.extraTags = make(map[string]string) } - opt.globalTags[key] = value + opt.extraTags[key] = value } } @@ -167,7 +170,7 @@ func WithFeeder(feeder dkio.Feeder) Option { return func(opt *option) { opt.fee func defaultOption() *option { return &option{ source: "default", - globalTags: map[string]string{"service": "default"}, + extraTags: map[string]string{"service": "default"}, maxForceFlushLimit: 10, fileFromBeginningThresholdSize: 1000 * 1000 * 1, // 1 MB done: make(<-chan interface{}), diff --git a/internal/tailer/option_test.go b/internal/tailer/option_test.go index 8093f75b83..00fb98e015 100644 --- a/internal/tailer/option_test.go +++ b/internal/tailer/option_test.go @@ -18,7 +18,7 @@ func TestWithOptions(t *testing.T) { WithService("testing-service")(opt) res := map[string]string{"service": "testing-service"} - assert.Equal(t, opt.globalTags, res) + assert.Equal(t, opt.extraTags, res) }) t.Run("with-default-service", func(t *testing.T) { @@ -27,7 +27,7 @@ func TestWithOptions(t *testing.T) { WithService("")(opt) res := map[string]string{"service": "testing-source"} - assert.Equal(t, opt.globalTags, res) + assert.Equal(t, opt.extraTags, res) }) t.Run("with-default-service", func(t *testing.T) { @@ -36,7 +36,7 @@ func TestWithOptions(t *testing.T) { WithSource("testing-source")(opt) res := map[string]string{"service": "default"} - assert.Equal(t, opt.globalTags, res) + assert.Equal(t, opt.extraTags, res) }) t.Run("with-non-service", func(t *testing.T) { @@ -44,6 +44,6 @@ func TestWithOptions(t *testing.T) { WithSource("testing-source")(opt) res := map[string]string{"service": "default"} - assert.Equal(t, opt.globalTags, res) + assert.Equal(t, opt.extraTags, res) }) } diff --git a/internal/tailer/socket.go b/internal/tailer/socket.go index 9d61f19ef1..53993d9ee4 100644 --- a/internal/tailer/socket.go +++ b/internal/tailer/socket.go @@ -44,7 +44,7 @@ func NewSocketLogWithOptions(opts ...Option) (*SocketLogger, error) { sk := &SocketLogger{ opt: c, } - sk.tags = buildTags(sk.opt.globalTags) + sk.tags = buildTags(sk.opt.extraTags) sk.log = logger.SLogger("socketLog/" + sk.opt.source) if err := sk.setup(); err != nil { diff --git a/internal/tailer/tailer_single.go b/internal/tailer/tailer_single.go index 2b0b94b94c..552d350d3e 100644 --- a/internal/tailer/tailer_single.go +++ b/internal/tailer/tailer_single.go @@ -11,7 +11,6 @@ import ( "fmt" "io" "os" - "path/filepath" "time" "github.com/GuanceCloud/cliutils/logger" @@ -38,10 +37,10 @@ const ( type Single struct { opt *option - file *os.File - inode string - recordKey string - filepath, filename string + file *os.File + inode string + recordKey string + filepath string decoder *encoding.Decoder mult *multiline.Multiline @@ -58,7 +57,7 @@ type Single struct { log *logger.Logger } -func NewTailerSingle(filename string, opts ...Option) (*Single, error) { +func NewTailerSingle(filepath string, opts ...Option) (*Single, error) { _ = logtail.InitDefault() c := defaultOption() @@ -68,10 +67,9 @@ func NewTailerSingle(filename string, opts ...Option) (*Single, error) { t := &Single{ opt: c, - filepath: filename, - filename: filepath.Base(filename), + filepath: filepath, } - t.tags = t.buildTags(t.opt.globalTags) + t.buildTags(t.opt.extraTags) t.log = logger.SLogger("logging/" + t.opt.source) if err := t.setup(); err != nil { @@ -152,7 +150,7 @@ func (t *Single) seekOffset() error { t.log.Infof("position %d larger than the file size %d, may be truncated", offset, size) } else { t.offset, err = t.file.Seek(offset, io.SeekStart) - t.log.Infof("set position %d for filename %s", offset, t.filepath) + t.log.Infof("set position %d for file %s", offset, t.filepath) return err } } @@ -161,7 +159,7 @@ func (t *Single) seekOffset() error { // use fromBeginning if t.opt.fromBeginning { t.offset, err = t.file.Seek(0, io.SeekStart) - t.log.Infof("set start position for filename %s", t.filepath) + t.log.Infof("set start position for file %s", t.filepath) return err } @@ -170,7 +168,7 @@ func (t *Single) seekOffset() error { size := stat.Size() if size < t.opt.fileFromBeginningThresholdSize { t.offset, err = t.file.Seek(0, io.SeekStart) - t.log.Infof("set start position for filename %s, because file size %d < %d", + t.log.Infof("set start position for file %s, because file size %d < %d", t.filepath, size, t.opt.fileFromBeginningThresholdSize) return err } @@ -178,7 +176,7 @@ func (t *Single) seekOffset() error { // use tail t.offset, err = t.file.Seek(0, io.SeekEnd) - t.log.Infof("set end position for filename %s", t.filepath) + t.log.Infof("set end position for file %s", t.filepath) return err } @@ -274,7 +272,7 @@ func (t *Single) forwardMessage() { if err := t.collectOnce(); err != nil { if !errors.Is(err, reader.ErrReadEmpty) { - t.log.Warnf("failed to read data from file %s, error: %s", t.filename, err) + t.log.Warnf("failed to read data from file %s, error: %s", t.filepath, err) } t.wait() continue @@ -403,18 +401,19 @@ func (t *Single) feedToRemote(pending [][]byte) { for _, text := range pending { t.readLines++ fields := map[string]interface{}{ - "log_read_lines": t.readLines, - "log_read_offset": t.offset, - "log_read_time": t.readTime.UnixNano(), - "log_file_inode": t.inode, - "message_length": len(text), "filepath": t.filepath, + "log_read_lines": t.readLines, pipeline.FieldStatus: pipeline.DefaultStatus, } + if t.opt.enableDebugFields { + fields["log_read_offset"] = t.offset + fields["log_read_time"] = t.readTime.UnixNano() + fields["log_file_inode"] = t.inode + } - err := t.opt.forwardFunc(t.filename, string(text), fields) + err := t.opt.forwardFunc(t.filepath, string(text), fields) if err != nil { - t.log.Warnf("failed to forward text from file %s, error: %s", t.filename, err) + t.log.Warnf("failed to forward text from file %s, error: %s", t.filepath, err) } } } @@ -427,14 +426,17 @@ func (t *Single) feedToIO(pending [][]byte) { t.readLines++ fields := map[string]interface{}{ + "filepath": t.filepath, "log_read_lines": t.readLines, - "log_read_offset": t.offset, - "log_read_time": t.readTime.UnixNano(), - "log_file_inode": t.inode, - "message_length": len(cnt), pipeline.FieldMessage: string(cnt), pipeline.FieldStatus: pipeline.DefaultStatus, } + if t.opt.enableDebugFields { + fields["log_read_offset"] = t.offset + fields["log_read_time"] = t.readTime.UnixNano() + fields["log_file_inode"] = t.inode + } + opts := append(point.DefaultLoggingOptions(), point.WithTime(timeNow.Add(time.Duration(i)*time.Microsecond))) pt := point.NewPointV2( @@ -470,20 +472,11 @@ func (t *Single) resetFlushScore() { t.flushScore = 0 } -func (t *Single) buildTags(globalTags map[string]string) map[string]string { - tags := make(map[string]string) - for k, v := range globalTags { - tags[k] = v - } - - if _, ok := tags["filepath"]; !ok { - tags["filepath"] = t.filepath - } - - if _, ok := tags["filename"]; !ok { - tags["filename"] = t.filename +func (t *Single) buildTags(extraTags map[string]string) { + t.tags = make(map[string]string) + for k, v := range extraTags { + t.tags[k] = v } - return tags } func (t *Single) decode(text []byte) ([]byte, error) {