// otellog 提供 git.blauwelle.com/go/crate/log 的 opentelemetry 处理功能 package logotel import ( "fmt" "strconv" "git.blauwelle.com/go/crate/log/logsdk" "git.blauwelle.com/go/crate/synchelper" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" ) // New 创建 log/opentelemetry 处理器 func New(opts ...Option) *Processor { cfg := newConfig(opts...) return &Processor{ bufferPool: cfg.bufferPool, } } var _ logsdk.EntryProcessor = &Processor{} // Processor 用于把日志和 opentelemetry 对接 type Processor struct { bufferPool synchelper.BytesBufferPool } func (processor *Processor) Process(entry logsdk.ReadonlyEntry) { span := trace.SpanFromContext(entry.Context) if !span.IsRecording() { return } attrs := make([]attribute.KeyValue, 0, len(entry.Fields)+6) attrs = append(attrs, attribute.String("log.severity", entry.Level.String())) attrs = append(attrs, attribute.String("log.message", entry.Message)) if entry.Caller.IsValid() { attrs = append(attrs, semconv.CodeFunctionKey.String(entry.Caller.Function)) attrs = append(attrs, semconv.CodeFilepathKey.String(entry.Caller.File)) attrs = append(attrs, semconv.CodeLineNumberKey.Int(entry.Caller.Line)) } if len(entry.Stack) > 0 { buf := processor.bufferPool.Get() for _, frame := range entry.Stack { buf.WriteString(frame.Function) buf.WriteByte('\n') buf.WriteByte('\t') buf.WriteString(frame.File) buf.WriteByte(':') buf.WriteString(strconv.Itoa(frame.Line)) buf.WriteByte('\n') } processor.bufferPool.Put(buf) attrs = append(attrs, attribute.String("zz.stack", buf.String())) } for _, field := range entry.Fields { attrs = append(attrs, fieldToKV(field)) } span.AddEvent("log", trace.WithTimestamp(entry.Time), trace.WithAttributes(attrs...)) if entry.Level <= logsdk.LevelError { span.SetStatus(codes.Error, entry.Message) } } func fieldToKV(field logsdk.Field) attribute.KeyValue { switch value := field.Value.(type) { case nil: return attribute.String(field.Key, "") case string: return attribute.String(field.Key, value) case int: return attribute.Int(field.Key, value) case int64: return attribute.Int64(field.Key, value) case float64: return attribute.Float64(field.Key, value) case bool: return attribute.Bool(field.Key, value) case error: return attribute.String(field.Key, value.Error()) case fmt.Stringer: return attribute.String(field.Key, value.String()) } return attribute.String(field.Key, fmt.Sprint(field.Value)) }