1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-01-16 02:47:20 +02:00

Generate internal/transform in otlploggrpc (#5553)

Part of #5056

It abstracts the `transform` package from `otlploghttp` and makes it a
shared template. Then, it generates the abstracted `transform` package
into `otlploggrpc`.

For full usage of this transform package, check
https://github.com/open-telemetry/opentelemetry-go/pull/5522
This commit is contained in:
Sam Xie 2024-06-28 01:15:35 -07:00 committed by GitHub
parent f3a2d96022
commit 82fe9aa1e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 2009 additions and 3 deletions

View File

@ -6,7 +6,10 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel v1.27.0
go.opentelemetry.io/otel/log v0.3.0
go.opentelemetry.io/otel/sdk v1.27.0
go.opentelemetry.io/otel/sdk/log v0.3.0
go.opentelemetry.io/otel/trace v1.27.0
go.opentelemetry.io/proto/otlp v1.3.1
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d
google.golang.org/grpc v1.64.0
@ -22,10 +25,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
go.opentelemetry.io/otel/log v0.3.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect

View File

@ -5,3 +5,8 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlp
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry.go.tmpl "--data={}" --out=retry/retry.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry_test.go.tmpl "--data={}" --out=retry/retry_test.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/attr_test.go.tmpl "--data={}" --out=transform/attr_test.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log.go.tmpl "--data={}" --out=transform/log.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl "--data={}" --out=transform/log_attr_test.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_test.go.tmpl "--data={}" --out=transform/log_test.go

View File

@ -0,0 +1,186 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/attr_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package transform
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
)
var (
attrBool = attribute.Bool("bool", true)
attrBoolSlice = attribute.BoolSlice("bool slice", []bool{true, false})
attrInt = attribute.Int("int", 1)
attrIntSlice = attribute.IntSlice("int slice", []int{-1, 1})
attrInt64 = attribute.Int64("int64", 1)
attrInt64Slice = attribute.Int64Slice("int64 slice", []int64{-1, 1})
attrFloat64 = attribute.Float64("float64", 1)
attrFloat64Slice = attribute.Float64Slice("float64 slice", []float64{-1, 1})
attrString = attribute.String("string", "o")
attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"})
attrInvalid = attribute.KeyValue{
Key: attribute.Key("invalid"),
Value: attribute.Value{},
}
valBoolTrue = &cpb.AnyValue{Value: &cpb.AnyValue_BoolValue{BoolValue: true}}
valBoolFalse = &cpb.AnyValue{Value: &cpb.AnyValue_BoolValue{BoolValue: false}}
valBoolSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valBoolTrue, valBoolFalse},
},
}}
valIntOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: 1}}
valIntNOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: -1}}
valIntSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valIntNOne, valIntOne},
},
}}
valDblOne = &cpb.AnyValue{Value: &cpb.AnyValue_DoubleValue{DoubleValue: 1}}
valDblNOne = &cpb.AnyValue{Value: &cpb.AnyValue_DoubleValue{DoubleValue: -1}}
valDblSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valDblNOne, valDblOne},
},
}}
valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}}
valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}}
valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valStrO, valStrN},
},
}}
kvBool = &cpb.KeyValue{Key: "bool", Value: valBoolTrue}
kvBoolSlice = &cpb.KeyValue{Key: "bool slice", Value: valBoolSlice}
kvInt = &cpb.KeyValue{Key: "int", Value: valIntOne}
kvIntSlice = &cpb.KeyValue{Key: "int slice", Value: valIntSlice}
kvInt64 = &cpb.KeyValue{Key: "int64", Value: valIntOne}
kvInt64Slice = &cpb.KeyValue{Key: "int64 slice", Value: valIntSlice}
kvFloat64 = &cpb.KeyValue{Key: "float64", Value: valDblOne}
kvFloat64Slice = &cpb.KeyValue{Key: "float64 slice", Value: valDblSlice}
kvString = &cpb.KeyValue{Key: "string", Value: valStrO}
kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice}
kvInvalid = &cpb.KeyValue{
Key: "invalid",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "INVALID"},
},
}
)
func TestAttrTransforms(t *testing.T) {
type attrTest struct {
name string
in []attribute.KeyValue
want []*cpb.KeyValue
}
for _, test := range []attrTest{
{"nil", nil, nil},
{"empty", []attribute.KeyValue{}, nil},
{
"invalid",
[]attribute.KeyValue{attrInvalid},
[]*cpb.KeyValue{kvInvalid},
},
{
"bool",
[]attribute.KeyValue{attrBool},
[]*cpb.KeyValue{kvBool},
},
{
"bool slice",
[]attribute.KeyValue{attrBoolSlice},
[]*cpb.KeyValue{kvBoolSlice},
},
{
"int",
[]attribute.KeyValue{attrInt},
[]*cpb.KeyValue{kvInt},
},
{
"int slice",
[]attribute.KeyValue{attrIntSlice},
[]*cpb.KeyValue{kvIntSlice},
},
{
"int64",
[]attribute.KeyValue{attrInt64},
[]*cpb.KeyValue{kvInt64},
},
{
"int64 slice",
[]attribute.KeyValue{attrInt64Slice},
[]*cpb.KeyValue{kvInt64Slice},
},
{
"float64",
[]attribute.KeyValue{attrFloat64},
[]*cpb.KeyValue{kvFloat64},
},
{
"float64 slice",
[]attribute.KeyValue{attrFloat64Slice},
[]*cpb.KeyValue{kvFloat64Slice},
},
{
"string",
[]attribute.KeyValue{attrString},
[]*cpb.KeyValue{kvString},
},
{
"string slice",
[]attribute.KeyValue{attrStringSlice},
[]*cpb.KeyValue{kvStringSlice},
},
{
"all",
[]attribute.KeyValue{
attrBool,
attrBoolSlice,
attrInt,
attrIntSlice,
attrInt64,
attrInt64Slice,
attrFloat64,
attrFloat64Slice,
attrString,
attrStringSlice,
attrInvalid,
},
[]*cpb.KeyValue{
kvBool,
kvBoolSlice,
kvInt,
kvIntSlice,
kvInt64,
kvInt64Slice,
kvFloat64,
kvFloat64Slice,
kvString,
kvStringSlice,
kvInvalid,
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Run("Attrs", func(t *testing.T) {
assert.ElementsMatch(t, test.want, Attrs(test.in))
})
t.Run("AttrIter", func(t *testing.T) {
s := attribute.NewSet(test.in...)
assert.ElementsMatch(t, test.want, AttrIter(s.Iter()))
})
})
}
}

View File

@ -0,0 +1,411 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package transform provides transformation functionality from the
// sdk/log data-types into OTLP data-types.
package transform // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform"
import (
"sync"
"time"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
lpb "go.opentelemetry.io/proto/otlp/logs/v1"
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
"go.opentelemetry.io/otel/attribute"
api "go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/log"
)
// ResourceLogs returns an slice of OTLP ResourceLogs generated from records.
func ResourceLogs(records []log.Record) []*lpb.ResourceLogs {
if len(records) == 0 {
return nil
}
resMap := resourceLogsMapPool.Get().(map[attribute.Distinct]*lpb.ResourceLogs)
defer func() {
clear(resMap)
resourceLogsMapPool.Put(resMap)
}()
resourceLogsMap(&resMap, records)
out := make([]*lpb.ResourceLogs, 0, len(resMap))
for _, rl := range resMap {
out = append(out, rl)
}
return out
}
var resourceLogsMapPool = sync.Pool{
New: func() any {
return make(map[attribute.Distinct]*lpb.ResourceLogs)
},
}
func resourceLogsMap(dst *map[attribute.Distinct]*lpb.ResourceLogs, records []log.Record) {
for _, r := range records {
res := r.Resource()
rl, ok := (*dst)[res.Equivalent()]
if !ok {
rl = new(lpb.ResourceLogs)
if res.Len() > 0 {
rl.Resource = &rpb.Resource{
Attributes: AttrIter(res.Iter()),
}
}
rl.SchemaUrl = res.SchemaURL()
(*dst)[res.Equivalent()] = rl
}
rl.ScopeLogs = ScopeLogs(records)
}
}
// ScopeLogs returns a slice of OTLP ScopeLogs generated from recoreds.
func ScopeLogs(records []log.Record) []*lpb.ScopeLogs {
scopeMap := scopeLogsMapPool.Get().(map[instrumentation.Scope]*lpb.ScopeLogs)
defer func() {
clear(scopeMap)
scopeLogsMapPool.Put(scopeMap)
}()
scopeLogsMap(&scopeMap, records)
out := make([]*lpb.ScopeLogs, 0, len(scopeMap))
for _, sl := range scopeMap {
out = append(out, sl)
}
return out
}
var scopeLogsMapPool = sync.Pool{
New: func() any {
return make(map[instrumentation.Scope]*lpb.ScopeLogs)
},
}
func scopeLogsMap(dst *map[instrumentation.Scope]*lpb.ScopeLogs, records []log.Record) {
for _, r := range records {
scope := r.InstrumentationScope()
sl, ok := (*dst)[scope]
if !ok {
sl = new(lpb.ScopeLogs)
var emptyScope instrumentation.Scope
if scope != emptyScope {
sl.Scope = &cpb.InstrumentationScope{
Name: scope.Name,
Version: scope.Version,
}
sl.SchemaUrl = scope.SchemaURL
}
(*dst)[scope] = sl
}
sl.LogRecords = append(sl.LogRecords, LogRecord(r))
}
}
// LogRecord returns an OTLP LogRecord generated from record.
func LogRecord(record log.Record) *lpb.LogRecord {
r := &lpb.LogRecord{
TimeUnixNano: timeUnixNano(record.Timestamp()),
ObservedTimeUnixNano: timeUnixNano(record.ObservedTimestamp()),
SeverityNumber: SeverityNumber(record.Severity()),
SeverityText: record.SeverityText(),
Body: LogAttrValue(record.Body()),
Attributes: make([]*cpb.KeyValue, 0, record.AttributesLen()),
Flags: uint32(record.TraceFlags()),
// TODO: DroppedAttributesCount: /* ... */,
}
record.WalkAttributes(func(kv api.KeyValue) bool {
r.Attributes = append(r.Attributes, LogAttr(kv))
return true
})
if tID := record.TraceID(); tID.IsValid() {
r.TraceId = tID[:]
}
if sID := record.SpanID(); sID.IsValid() {
r.SpanId = sID[:]
}
return r
}
// timeUnixNano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC as uint64. The result is undefined if the Unix
// time in nanoseconds cannot be represented by an int64 (a date before the
// year 1678 or after 2262). timeUnixNano on the zero Time returns 0. The
// result does not depend on the location associated with t.
func timeUnixNano(t time.Time) uint64 {
if t.IsZero() {
return 0
}
return uint64(t.UnixNano())
}
// AttrIter transforms an [attribute.Iterator] into OTLP key-values.
func AttrIter(iter attribute.Iterator) []*cpb.KeyValue {
l := iter.Len()
if l == 0 {
return nil
}
out := make([]*cpb.KeyValue, 0, l)
for iter.Next() {
out = append(out, Attr(iter.Attribute()))
}
return out
}
// Attrs transforms a slice of [attribute.KeyValue] into OTLP key-values.
func Attrs(attrs []attribute.KeyValue) []*cpb.KeyValue {
if len(attrs) == 0 {
return nil
}
out := make([]*cpb.KeyValue, 0, len(attrs))
for _, kv := range attrs {
out = append(out, Attr(kv))
}
return out
}
// Attr transforms an [attribute.KeyValue] into an OTLP key-value.
func Attr(kv attribute.KeyValue) *cpb.KeyValue {
return &cpb.KeyValue{Key: string(kv.Key), Value: AttrValue(kv.Value)}
}
// AttrValue transforms an [attribute.Value] into an OTLP AnyValue.
func AttrValue(v attribute.Value) *cpb.AnyValue {
av := new(cpb.AnyValue)
switch v.Type() {
case attribute.BOOL:
av.Value = &cpb.AnyValue_BoolValue{
BoolValue: v.AsBool(),
}
case attribute.BOOLSLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: boolSliceValues(v.AsBoolSlice()),
},
}
case attribute.INT64:
av.Value = &cpb.AnyValue_IntValue{
IntValue: v.AsInt64(),
}
case attribute.INT64SLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: int64SliceValues(v.AsInt64Slice()),
},
}
case attribute.FLOAT64:
av.Value = &cpb.AnyValue_DoubleValue{
DoubleValue: v.AsFloat64(),
}
case attribute.FLOAT64SLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: float64SliceValues(v.AsFloat64Slice()),
},
}
case attribute.STRING:
av.Value = &cpb.AnyValue_StringValue{
StringValue: v.AsString(),
}
case attribute.STRINGSLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: stringSliceValues(v.AsStringSlice()),
},
}
default:
av.Value = &cpb.AnyValue_StringValue{
StringValue: "INVALID",
}
}
return av
}
func boolSliceValues(vals []bool) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_BoolValue{
BoolValue: v,
},
}
}
return converted
}
func int64SliceValues(vals []int64) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_IntValue{
IntValue: v,
},
}
}
return converted
}
func float64SliceValues(vals []float64) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_DoubleValue{
DoubleValue: v,
},
}
}
return converted
}
func stringSliceValues(vals []string) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{
StringValue: v,
},
}
}
return converted
}
// Attrs transforms a slice of [api.KeyValue] into OTLP key-values.
func LogAttrs(attrs []api.KeyValue) []*cpb.KeyValue {
if len(attrs) == 0 {
return nil
}
out := make([]*cpb.KeyValue, 0, len(attrs))
for _, kv := range attrs {
out = append(out, LogAttr(kv))
}
return out
}
// LogAttr transforms an [api.KeyValue] into an OTLP key-value.
func LogAttr(attr api.KeyValue) *cpb.KeyValue {
return &cpb.KeyValue{
Key: attr.Key,
Value: LogAttrValue(attr.Value),
}
}
// LogAttrValues transforms a slice of [api.Value] into an OTLP []AnyValue.
func LogAttrValues(vals []api.Value) []*cpb.AnyValue {
if len(vals) == 0 {
return nil
}
out := make([]*cpb.AnyValue, 0, len(vals))
for _, v := range vals {
out = append(out, LogAttrValue(v))
}
return out
}
// LogAttrValue transforms an [api.Value] into an OTLP AnyValue.
func LogAttrValue(v api.Value) *cpb.AnyValue {
av := new(cpb.AnyValue)
switch v.Kind() {
case api.KindBool:
av.Value = &cpb.AnyValue_BoolValue{
BoolValue: v.AsBool(),
}
case api.KindInt64:
av.Value = &cpb.AnyValue_IntValue{
IntValue: v.AsInt64(),
}
case api.KindFloat64:
av.Value = &cpb.AnyValue_DoubleValue{
DoubleValue: v.AsFloat64(),
}
case api.KindString:
av.Value = &cpb.AnyValue_StringValue{
StringValue: v.AsString(),
}
case api.KindBytes:
av.Value = &cpb.AnyValue_BytesValue{
BytesValue: v.AsBytes(),
}
case api.KindSlice:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: LogAttrValues(v.AsSlice()),
},
}
case api.KindMap:
av.Value = &cpb.AnyValue_KvlistValue{
KvlistValue: &cpb.KeyValueList{
Values: LogAttrs(v.AsMap()),
},
}
default:
av.Value = &cpb.AnyValue_StringValue{
StringValue: "INVALID",
}
}
return av
}
// SeverityNumber transforms a [log.Severity] into an OTLP SeverityNumber.
func SeverityNumber(s api.Severity) lpb.SeverityNumber {
switch s {
case api.SeverityTrace:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE
case api.SeverityTrace2:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE2
case api.SeverityTrace3:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE3
case api.SeverityTrace4:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE4
case api.SeverityDebug:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG
case api.SeverityDebug2:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG2
case api.SeverityDebug3:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG3
case api.SeverityDebug4:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG4
case api.SeverityInfo:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO
case api.SeverityInfo2:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO2
case api.SeverityInfo3:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO3
case api.SeverityInfo4:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO4
case api.SeverityWarn:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN
case api.SeverityWarn2:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN2
case api.SeverityWarn3:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN3
case api.SeverityWarn4:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN4
case api.SeverityError:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR
case api.SeverityError2:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR2
case api.SeverityError3:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR3
case api.SeverityError4:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR4
case api.SeverityFatal:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL
case api.SeverityFatal2:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL2
case api.SeverityFatal3:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL3
case api.SeverityFatal4:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL4
}
return lpb.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED
}

View File

@ -0,0 +1,149 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package transform
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/log"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
)
var (
logAttrBool = log.Bool("bool", true)
logAttrInt = log.Int("int", 1)
logAttrInt64 = log.Int64("int64", 1)
logAttrFloat64 = log.Float64("float64", 1)
logAttrString = log.String("string", "o")
logAttrBytes = log.Bytes("bytes", []byte("test"))
logAttrSlice = log.Slice("slice", log.BoolValue(true))
logAttrMap = log.Map("map", logAttrString)
logAttrEmpty = log.Empty("")
kvBytes = &cpb.KeyValue{
Key: "bytes",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_BytesValue{
BytesValue: []byte("test"),
},
},
}
kvSlice = &cpb.KeyValue{
Key: "slice",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valBoolTrue},
},
},
},
}
kvMap = &cpb.KeyValue{
Key: "map",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_KvlistValue{
KvlistValue: &cpb.KeyValueList{
Values: []*cpb.KeyValue{kvString},
},
},
},
}
kvEmpty = &cpb.KeyValue{
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "INVALID"},
},
}
)
func TestLogAttrs(t *testing.T) {
type logAttrTest struct {
name string
in []log.KeyValue
want []*cpb.KeyValue
}
for _, test := range []logAttrTest{
{"nil", nil, nil},
{"len(0)", []log.KeyValue{}, nil},
{
"empty",
[]log.KeyValue{logAttrEmpty},
[]*cpb.KeyValue{kvEmpty},
},
{
"bool",
[]log.KeyValue{logAttrBool},
[]*cpb.KeyValue{kvBool},
},
{
"int",
[]log.KeyValue{logAttrInt},
[]*cpb.KeyValue{kvInt},
},
{
"int64",
[]log.KeyValue{logAttrInt64},
[]*cpb.KeyValue{kvInt64},
},
{
"float64",
[]log.KeyValue{logAttrFloat64},
[]*cpb.KeyValue{kvFloat64},
},
{
"string",
[]log.KeyValue{logAttrString},
[]*cpb.KeyValue{kvString},
},
{
"bytes",
[]log.KeyValue{logAttrBytes},
[]*cpb.KeyValue{kvBytes},
},
{
"slice",
[]log.KeyValue{logAttrSlice},
[]*cpb.KeyValue{kvSlice},
},
{
"map",
[]log.KeyValue{logAttrMap},
[]*cpb.KeyValue{kvMap},
},
{
"all",
[]log.KeyValue{
logAttrBool,
logAttrInt,
logAttrInt64,
logAttrFloat64,
logAttrString,
logAttrBytes,
logAttrSlice,
logAttrMap,
logAttrEmpty,
},
[]*cpb.KeyValue{
kvBool,
kvInt,
kvInt64,
kvFloat64,
kvString,
kvBytes,
kvSlice,
kvMap,
kvEmpty,
},
},
} {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.want, LogAttrs(test.in))
})
}
}

View File

@ -0,0 +1,246 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package transform
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
lpb "go.opentelemetry.io/proto/otlp/logs/v1"
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
api "go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/log/logtest"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
)
var (
// Sat Jan 01 2000 00:00:00 GMT+0000.
ts = time.Date(2000, time.January, 0o1, 0, 0, 0, 0, time.FixedZone("GMT", 0))
obs = ts.Add(30 * time.Second)
alice = api.String("user", "alice")
bob = api.String("user", "bob")
pbAlice = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "alice"},
}}
pbBob = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "bob"},
}}
sevA = api.SeverityInfo
sevB = api.SeverityError
pbSevA = lpb.SeverityNumber_SEVERITY_NUMBER_INFO
pbSevB = lpb.SeverityNumber_SEVERITY_NUMBER_ERROR
bodyA = api.StringValue("a")
bodyB = api.StringValue("b")
pbBodyA = &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{
StringValue: "a",
},
}
pbBodyB = &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{
StringValue: "b",
},
}
spanIDA = []byte{0, 0, 0, 0, 0, 0, 0, 1}
spanIDB = []byte{0, 0, 0, 0, 0, 0, 0, 2}
traceIDA = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
traceIDB = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
flagsA = byte(1)
flagsB = byte(0)
scope = instrumentation.Scope{
Name: "test/code/path",
Version: "v0.1.0",
SchemaURL: semconv.SchemaURL,
}
pbScope = &cpb.InstrumentationScope{
Name: "test/code/path",
Version: "v0.1.0",
}
res = resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("test server"),
semconv.ServiceVersion("v0.1.0"),
)
pbRes = &rpb.Resource{
Attributes: []*cpb.KeyValue{
{
Key: "service.name",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "test server"},
},
},
{
Key: "service.version",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "v0.1.0"},
},
},
},
}
records = func() []log.Record {
var out []log.Record
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevA,
SeverityText: "A",
Body: bodyA,
Attributes: []api.KeyValue{alice},
TraceID: trace.TraceID(traceIDA),
SpanID: trace.SpanID(spanIDA),
TraceFlags: trace.TraceFlags(flagsA),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevA,
SeverityText: "A",
Body: bodyA,
Attributes: []api.KeyValue{bob},
TraceID: trace.TraceID(traceIDA),
SpanID: trace.SpanID(spanIDA),
TraceFlags: trace.TraceFlags(flagsA),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevB,
SeverityText: "B",
Body: bodyB,
Attributes: []api.KeyValue{alice},
TraceID: trace.TraceID(traceIDB),
SpanID: trace.SpanID(spanIDB),
TraceFlags: trace.TraceFlags(flagsB),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevB,
SeverityText: "B",
Body: bodyB,
Attributes: []api.KeyValue{bob},
TraceID: trace.TraceID(traceIDB),
SpanID: trace.SpanID(spanIDB),
TraceFlags: trace.TraceFlags(flagsB),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
return out
}()
pbLogRecords = []*lpb.LogRecord{
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevA,
SeverityText: "A",
Body: pbBodyA,
Attributes: []*cpb.KeyValue{pbAlice},
Flags: uint32(flagsA),
TraceId: traceIDA,
SpanId: spanIDA,
},
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevA,
SeverityText: "A",
Body: pbBodyA,
Attributes: []*cpb.KeyValue{pbBob},
Flags: uint32(flagsA),
TraceId: traceIDA,
SpanId: spanIDA,
},
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevB,
SeverityText: "B",
Body: pbBodyB,
Attributes: []*cpb.KeyValue{pbAlice},
Flags: uint32(flagsB),
TraceId: traceIDB,
SpanId: spanIDB,
},
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevB,
SeverityText: "B",
Body: pbBodyB,
Attributes: []*cpb.KeyValue{pbBob},
Flags: uint32(flagsB),
TraceId: traceIDB,
SpanId: spanIDB,
},
}
pbScopeLogs = &lpb.ScopeLogs{
Scope: pbScope,
SchemaUrl: semconv.SchemaURL,
LogRecords: pbLogRecords,
}
pbResourceLogs = &lpb.ResourceLogs{
Resource: pbRes,
SchemaUrl: semconv.SchemaURL,
ScopeLogs: []*lpb.ScopeLogs{pbScopeLogs},
}
)
func TestResourceLogs(t *testing.T) {
want := []*lpb.ResourceLogs{pbResourceLogs}
assert.Equal(t, want, ResourceLogs(records))
}
func TestSeverityNumber(t *testing.T) {
for i := 0; i <= int(api.SeverityFatal4); i++ {
want := lpb.SeverityNumber(i)
want += lpb.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED
assert.Equal(t, want, SeverityNumber(api.Severity(i)))
}
}
func BenchmarkResourceLogs(b *testing.B) {
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
var out []*lpb.ResourceLogs
for pb.Next() {
out = ResourceLogs(records)
}
_ = out
})
}

View File

@ -5,3 +5,8 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlp
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry.go.tmpl "--data={}" --out=retry/retry.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry_test.go.tmpl "--data={}" --out=retry/retry_test.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/attr_test.go.tmpl "--data={}" --out=transform/attr_test.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log.go.tmpl "--data={}" --out=transform/log.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl "--data={}" --out=transform/log_attr_test.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_test.go.tmpl "--data={}" --out=transform/log_test.go

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/attr_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

View File

@ -0,0 +1,186 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/attr_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package transform
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
)
var (
attrBool = attribute.Bool("bool", true)
attrBoolSlice = attribute.BoolSlice("bool slice", []bool{true, false})
attrInt = attribute.Int("int", 1)
attrIntSlice = attribute.IntSlice("int slice", []int{-1, 1})
attrInt64 = attribute.Int64("int64", 1)
attrInt64Slice = attribute.Int64Slice("int64 slice", []int64{-1, 1})
attrFloat64 = attribute.Float64("float64", 1)
attrFloat64Slice = attribute.Float64Slice("float64 slice", []float64{-1, 1})
attrString = attribute.String("string", "o")
attrStringSlice = attribute.StringSlice("string slice", []string{"o", "n"})
attrInvalid = attribute.KeyValue{
Key: attribute.Key("invalid"),
Value: attribute.Value{},
}
valBoolTrue = &cpb.AnyValue{Value: &cpb.AnyValue_BoolValue{BoolValue: true}}
valBoolFalse = &cpb.AnyValue{Value: &cpb.AnyValue_BoolValue{BoolValue: false}}
valBoolSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valBoolTrue, valBoolFalse},
},
}}
valIntOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: 1}}
valIntNOne = &cpb.AnyValue{Value: &cpb.AnyValue_IntValue{IntValue: -1}}
valIntSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valIntNOne, valIntOne},
},
}}
valDblOne = &cpb.AnyValue{Value: &cpb.AnyValue_DoubleValue{DoubleValue: 1}}
valDblNOne = &cpb.AnyValue{Value: &cpb.AnyValue_DoubleValue{DoubleValue: -1}}
valDblSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valDblNOne, valDblOne},
},
}}
valStrO = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "o"}}
valStrN = &cpb.AnyValue{Value: &cpb.AnyValue_StringValue{StringValue: "n"}}
valStrSlice = &cpb.AnyValue{Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valStrO, valStrN},
},
}}
kvBool = &cpb.KeyValue{Key: "bool", Value: valBoolTrue}
kvBoolSlice = &cpb.KeyValue{Key: "bool slice", Value: valBoolSlice}
kvInt = &cpb.KeyValue{Key: "int", Value: valIntOne}
kvIntSlice = &cpb.KeyValue{Key: "int slice", Value: valIntSlice}
kvInt64 = &cpb.KeyValue{Key: "int64", Value: valIntOne}
kvInt64Slice = &cpb.KeyValue{Key: "int64 slice", Value: valIntSlice}
kvFloat64 = &cpb.KeyValue{Key: "float64", Value: valDblOne}
kvFloat64Slice = &cpb.KeyValue{Key: "float64 slice", Value: valDblSlice}
kvString = &cpb.KeyValue{Key: "string", Value: valStrO}
kvStringSlice = &cpb.KeyValue{Key: "string slice", Value: valStrSlice}
kvInvalid = &cpb.KeyValue{
Key: "invalid",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "INVALID"},
},
}
)
func TestAttrTransforms(t *testing.T) {
type attrTest struct {
name string
in []attribute.KeyValue
want []*cpb.KeyValue
}
for _, test := range []attrTest{
{"nil", nil, nil},
{"empty", []attribute.KeyValue{}, nil},
{
"invalid",
[]attribute.KeyValue{attrInvalid},
[]*cpb.KeyValue{kvInvalid},
},
{
"bool",
[]attribute.KeyValue{attrBool},
[]*cpb.KeyValue{kvBool},
},
{
"bool slice",
[]attribute.KeyValue{attrBoolSlice},
[]*cpb.KeyValue{kvBoolSlice},
},
{
"int",
[]attribute.KeyValue{attrInt},
[]*cpb.KeyValue{kvInt},
},
{
"int slice",
[]attribute.KeyValue{attrIntSlice},
[]*cpb.KeyValue{kvIntSlice},
},
{
"int64",
[]attribute.KeyValue{attrInt64},
[]*cpb.KeyValue{kvInt64},
},
{
"int64 slice",
[]attribute.KeyValue{attrInt64Slice},
[]*cpb.KeyValue{kvInt64Slice},
},
{
"float64",
[]attribute.KeyValue{attrFloat64},
[]*cpb.KeyValue{kvFloat64},
},
{
"float64 slice",
[]attribute.KeyValue{attrFloat64Slice},
[]*cpb.KeyValue{kvFloat64Slice},
},
{
"string",
[]attribute.KeyValue{attrString},
[]*cpb.KeyValue{kvString},
},
{
"string slice",
[]attribute.KeyValue{attrStringSlice},
[]*cpb.KeyValue{kvStringSlice},
},
{
"all",
[]attribute.KeyValue{
attrBool,
attrBoolSlice,
attrInt,
attrIntSlice,
attrInt64,
attrInt64Slice,
attrFloat64,
attrFloat64Slice,
attrString,
attrStringSlice,
attrInvalid,
},
[]*cpb.KeyValue{
kvBool,
kvBoolSlice,
kvInt,
kvIntSlice,
kvInt64,
kvInt64Slice,
kvFloat64,
kvFloat64Slice,
kvString,
kvStringSlice,
kvInvalid,
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Run("Attrs", func(t *testing.T) {
assert.ElementsMatch(t, test.want, Attrs(test.in))
})
t.Run("AttrIter", func(t *testing.T) {
s := attribute.NewSet(test.in...)
assert.ElementsMatch(t, test.want, AttrIter(s.Iter()))
})
})
}
}

View File

@ -0,0 +1,411 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package transform provides transformation functionality from the
// sdk/log data-types into OTLP data-types.
package transform // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/transform"
import (
"sync"
"time"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
lpb "go.opentelemetry.io/proto/otlp/logs/v1"
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
"go.opentelemetry.io/otel/attribute"
api "go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/log"
)
// ResourceLogs returns an slice of OTLP ResourceLogs generated from records.
func ResourceLogs(records []log.Record) []*lpb.ResourceLogs {
if len(records) == 0 {
return nil
}
resMap := resourceLogsMapPool.Get().(map[attribute.Distinct]*lpb.ResourceLogs)
defer func() {
clear(resMap)
resourceLogsMapPool.Put(resMap)
}()
resourceLogsMap(&resMap, records)
out := make([]*lpb.ResourceLogs, 0, len(resMap))
for _, rl := range resMap {
out = append(out, rl)
}
return out
}
var resourceLogsMapPool = sync.Pool{
New: func() any {
return make(map[attribute.Distinct]*lpb.ResourceLogs)
},
}
func resourceLogsMap(dst *map[attribute.Distinct]*lpb.ResourceLogs, records []log.Record) {
for _, r := range records {
res := r.Resource()
rl, ok := (*dst)[res.Equivalent()]
if !ok {
rl = new(lpb.ResourceLogs)
if res.Len() > 0 {
rl.Resource = &rpb.Resource{
Attributes: AttrIter(res.Iter()),
}
}
rl.SchemaUrl = res.SchemaURL()
(*dst)[res.Equivalent()] = rl
}
rl.ScopeLogs = ScopeLogs(records)
}
}
// ScopeLogs returns a slice of OTLP ScopeLogs generated from recoreds.
func ScopeLogs(records []log.Record) []*lpb.ScopeLogs {
scopeMap := scopeLogsMapPool.Get().(map[instrumentation.Scope]*lpb.ScopeLogs)
defer func() {
clear(scopeMap)
scopeLogsMapPool.Put(scopeMap)
}()
scopeLogsMap(&scopeMap, records)
out := make([]*lpb.ScopeLogs, 0, len(scopeMap))
for _, sl := range scopeMap {
out = append(out, sl)
}
return out
}
var scopeLogsMapPool = sync.Pool{
New: func() any {
return make(map[instrumentation.Scope]*lpb.ScopeLogs)
},
}
func scopeLogsMap(dst *map[instrumentation.Scope]*lpb.ScopeLogs, records []log.Record) {
for _, r := range records {
scope := r.InstrumentationScope()
sl, ok := (*dst)[scope]
if !ok {
sl = new(lpb.ScopeLogs)
var emptyScope instrumentation.Scope
if scope != emptyScope {
sl.Scope = &cpb.InstrumentationScope{
Name: scope.Name,
Version: scope.Version,
}
sl.SchemaUrl = scope.SchemaURL
}
(*dst)[scope] = sl
}
sl.LogRecords = append(sl.LogRecords, LogRecord(r))
}
}
// LogRecord returns an OTLP LogRecord generated from record.
func LogRecord(record log.Record) *lpb.LogRecord {
r := &lpb.LogRecord{
TimeUnixNano: timeUnixNano(record.Timestamp()),
ObservedTimeUnixNano: timeUnixNano(record.ObservedTimestamp()),
SeverityNumber: SeverityNumber(record.Severity()),
SeverityText: record.SeverityText(),
Body: LogAttrValue(record.Body()),
Attributes: make([]*cpb.KeyValue, 0, record.AttributesLen()),
Flags: uint32(record.TraceFlags()),
// TODO: DroppedAttributesCount: /* ... */,
}
record.WalkAttributes(func(kv api.KeyValue) bool {
r.Attributes = append(r.Attributes, LogAttr(kv))
return true
})
if tID := record.TraceID(); tID.IsValid() {
r.TraceId = tID[:]
}
if sID := record.SpanID(); sID.IsValid() {
r.SpanId = sID[:]
}
return r
}
// timeUnixNano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC as uint64. The result is undefined if the Unix
// time in nanoseconds cannot be represented by an int64 (a date before the
// year 1678 or after 2262). timeUnixNano on the zero Time returns 0. The
// result does not depend on the location associated with t.
func timeUnixNano(t time.Time) uint64 {
if t.IsZero() {
return 0
}
return uint64(t.UnixNano())
}
// AttrIter transforms an [attribute.Iterator] into OTLP key-values.
func AttrIter(iter attribute.Iterator) []*cpb.KeyValue {
l := iter.Len()
if l == 0 {
return nil
}
out := make([]*cpb.KeyValue, 0, l)
for iter.Next() {
out = append(out, Attr(iter.Attribute()))
}
return out
}
// Attrs transforms a slice of [attribute.KeyValue] into OTLP key-values.
func Attrs(attrs []attribute.KeyValue) []*cpb.KeyValue {
if len(attrs) == 0 {
return nil
}
out := make([]*cpb.KeyValue, 0, len(attrs))
for _, kv := range attrs {
out = append(out, Attr(kv))
}
return out
}
// Attr transforms an [attribute.KeyValue] into an OTLP key-value.
func Attr(kv attribute.KeyValue) *cpb.KeyValue {
return &cpb.KeyValue{Key: string(kv.Key), Value: AttrValue(kv.Value)}
}
// AttrValue transforms an [attribute.Value] into an OTLP AnyValue.
func AttrValue(v attribute.Value) *cpb.AnyValue {
av := new(cpb.AnyValue)
switch v.Type() {
case attribute.BOOL:
av.Value = &cpb.AnyValue_BoolValue{
BoolValue: v.AsBool(),
}
case attribute.BOOLSLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: boolSliceValues(v.AsBoolSlice()),
},
}
case attribute.INT64:
av.Value = &cpb.AnyValue_IntValue{
IntValue: v.AsInt64(),
}
case attribute.INT64SLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: int64SliceValues(v.AsInt64Slice()),
},
}
case attribute.FLOAT64:
av.Value = &cpb.AnyValue_DoubleValue{
DoubleValue: v.AsFloat64(),
}
case attribute.FLOAT64SLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: float64SliceValues(v.AsFloat64Slice()),
},
}
case attribute.STRING:
av.Value = &cpb.AnyValue_StringValue{
StringValue: v.AsString(),
}
case attribute.STRINGSLICE:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: stringSliceValues(v.AsStringSlice()),
},
}
default:
av.Value = &cpb.AnyValue_StringValue{
StringValue: "INVALID",
}
}
return av
}
func boolSliceValues(vals []bool) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_BoolValue{
BoolValue: v,
},
}
}
return converted
}
func int64SliceValues(vals []int64) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_IntValue{
IntValue: v,
},
}
}
return converted
}
func float64SliceValues(vals []float64) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_DoubleValue{
DoubleValue: v,
},
}
}
return converted
}
func stringSliceValues(vals []string) []*cpb.AnyValue {
converted := make([]*cpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{
StringValue: v,
},
}
}
return converted
}
// Attrs transforms a slice of [api.KeyValue] into OTLP key-values.
func LogAttrs(attrs []api.KeyValue) []*cpb.KeyValue {
if len(attrs) == 0 {
return nil
}
out := make([]*cpb.KeyValue, 0, len(attrs))
for _, kv := range attrs {
out = append(out, LogAttr(kv))
}
return out
}
// LogAttr transforms an [api.KeyValue] into an OTLP key-value.
func LogAttr(attr api.KeyValue) *cpb.KeyValue {
return &cpb.KeyValue{
Key: attr.Key,
Value: LogAttrValue(attr.Value),
}
}
// LogAttrValues transforms a slice of [api.Value] into an OTLP []AnyValue.
func LogAttrValues(vals []api.Value) []*cpb.AnyValue {
if len(vals) == 0 {
return nil
}
out := make([]*cpb.AnyValue, 0, len(vals))
for _, v := range vals {
out = append(out, LogAttrValue(v))
}
return out
}
// LogAttrValue transforms an [api.Value] into an OTLP AnyValue.
func LogAttrValue(v api.Value) *cpb.AnyValue {
av := new(cpb.AnyValue)
switch v.Kind() {
case api.KindBool:
av.Value = &cpb.AnyValue_BoolValue{
BoolValue: v.AsBool(),
}
case api.KindInt64:
av.Value = &cpb.AnyValue_IntValue{
IntValue: v.AsInt64(),
}
case api.KindFloat64:
av.Value = &cpb.AnyValue_DoubleValue{
DoubleValue: v.AsFloat64(),
}
case api.KindString:
av.Value = &cpb.AnyValue_StringValue{
StringValue: v.AsString(),
}
case api.KindBytes:
av.Value = &cpb.AnyValue_BytesValue{
BytesValue: v.AsBytes(),
}
case api.KindSlice:
av.Value = &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: LogAttrValues(v.AsSlice()),
},
}
case api.KindMap:
av.Value = &cpb.AnyValue_KvlistValue{
KvlistValue: &cpb.KeyValueList{
Values: LogAttrs(v.AsMap()),
},
}
default:
av.Value = &cpb.AnyValue_StringValue{
StringValue: "INVALID",
}
}
return av
}
// SeverityNumber transforms a [log.Severity] into an OTLP SeverityNumber.
func SeverityNumber(s api.Severity) lpb.SeverityNumber {
switch s {
case api.SeverityTrace:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE
case api.SeverityTrace2:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE2
case api.SeverityTrace3:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE3
case api.SeverityTrace4:
return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE4
case api.SeverityDebug:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG
case api.SeverityDebug2:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG2
case api.SeverityDebug3:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG3
case api.SeverityDebug4:
return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG4
case api.SeverityInfo:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO
case api.SeverityInfo2:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO2
case api.SeverityInfo3:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO3
case api.SeverityInfo4:
return lpb.SeverityNumber_SEVERITY_NUMBER_INFO4
case api.SeverityWarn:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN
case api.SeverityWarn2:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN2
case api.SeverityWarn3:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN3
case api.SeverityWarn4:
return lpb.SeverityNumber_SEVERITY_NUMBER_WARN4
case api.SeverityError:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR
case api.SeverityError2:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR2
case api.SeverityError3:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR3
case api.SeverityError4:
return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR4
case api.SeverityFatal:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL
case api.SeverityFatal2:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL2
case api.SeverityFatal3:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL3
case api.SeverityFatal4:
return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL4
}
return lpb.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED
}

View File

@ -0,0 +1,149 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package transform
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/log"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
)
var (
logAttrBool = log.Bool("bool", true)
logAttrInt = log.Int("int", 1)
logAttrInt64 = log.Int64("int64", 1)
logAttrFloat64 = log.Float64("float64", 1)
logAttrString = log.String("string", "o")
logAttrBytes = log.Bytes("bytes", []byte("test"))
logAttrSlice = log.Slice("slice", log.BoolValue(true))
logAttrMap = log.Map("map", logAttrString)
logAttrEmpty = log.Empty("")
kvBytes = &cpb.KeyValue{
Key: "bytes",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_BytesValue{
BytesValue: []byte("test"),
},
},
}
kvSlice = &cpb.KeyValue{
Key: "slice",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_ArrayValue{
ArrayValue: &cpb.ArrayValue{
Values: []*cpb.AnyValue{valBoolTrue},
},
},
},
}
kvMap = &cpb.KeyValue{
Key: "map",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_KvlistValue{
KvlistValue: &cpb.KeyValueList{
Values: []*cpb.KeyValue{kvString},
},
},
},
}
kvEmpty = &cpb.KeyValue{
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "INVALID"},
},
}
)
func TestLogAttrs(t *testing.T) {
type logAttrTest struct {
name string
in []log.KeyValue
want []*cpb.KeyValue
}
for _, test := range []logAttrTest{
{"nil", nil, nil},
{"len(0)", []log.KeyValue{}, nil},
{
"empty",
[]log.KeyValue{logAttrEmpty},
[]*cpb.KeyValue{kvEmpty},
},
{
"bool",
[]log.KeyValue{logAttrBool},
[]*cpb.KeyValue{kvBool},
},
{
"int",
[]log.KeyValue{logAttrInt},
[]*cpb.KeyValue{kvInt},
},
{
"int64",
[]log.KeyValue{logAttrInt64},
[]*cpb.KeyValue{kvInt64},
},
{
"float64",
[]log.KeyValue{logAttrFloat64},
[]*cpb.KeyValue{kvFloat64},
},
{
"string",
[]log.KeyValue{logAttrString},
[]*cpb.KeyValue{kvString},
},
{
"bytes",
[]log.KeyValue{logAttrBytes},
[]*cpb.KeyValue{kvBytes},
},
{
"slice",
[]log.KeyValue{logAttrSlice},
[]*cpb.KeyValue{kvSlice},
},
{
"map",
[]log.KeyValue{logAttrMap},
[]*cpb.KeyValue{kvMap},
},
{
"all",
[]log.KeyValue{
logAttrBool,
logAttrInt,
logAttrInt64,
logAttrFloat64,
logAttrString,
logAttrBytes,
logAttrSlice,
logAttrMap,
logAttrEmpty,
},
[]*cpb.KeyValue{
kvBool,
kvInt,
kvInt64,
kvFloat64,
kvString,
kvBytes,
kvSlice,
kvMap,
kvEmpty,
},
},
} {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.want, LogAttrs(test.in))
})
}
}

View File

@ -0,0 +1,246 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/otlp/otlplog/transform/log_test.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package transform
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
lpb "go.opentelemetry.io/proto/otlp/logs/v1"
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
api "go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/log/logtest"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
)
var (
// Sat Jan 01 2000 00:00:00 GMT+0000.
ts = time.Date(2000, time.January, 0o1, 0, 0, 0, 0, time.FixedZone("GMT", 0))
obs = ts.Add(30 * time.Second)
alice = api.String("user", "alice")
bob = api.String("user", "bob")
pbAlice = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "alice"},
}}
pbBob = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "bob"},
}}
sevA = api.SeverityInfo
sevB = api.SeverityError
pbSevA = lpb.SeverityNumber_SEVERITY_NUMBER_INFO
pbSevB = lpb.SeverityNumber_SEVERITY_NUMBER_ERROR
bodyA = api.StringValue("a")
bodyB = api.StringValue("b")
pbBodyA = &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{
StringValue: "a",
},
}
pbBodyB = &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{
StringValue: "b",
},
}
spanIDA = []byte{0, 0, 0, 0, 0, 0, 0, 1}
spanIDB = []byte{0, 0, 0, 0, 0, 0, 0, 2}
traceIDA = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
traceIDB = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
flagsA = byte(1)
flagsB = byte(0)
scope = instrumentation.Scope{
Name: "test/code/path",
Version: "v0.1.0",
SchemaURL: semconv.SchemaURL,
}
pbScope = &cpb.InstrumentationScope{
Name: "test/code/path",
Version: "v0.1.0",
}
res = resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("test server"),
semconv.ServiceVersion("v0.1.0"),
)
pbRes = &rpb.Resource{
Attributes: []*cpb.KeyValue{
{
Key: "service.name",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "test server"},
},
},
{
Key: "service.version",
Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "v0.1.0"},
},
},
},
}
records = func() []log.Record {
var out []log.Record
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevA,
SeverityText: "A",
Body: bodyA,
Attributes: []api.KeyValue{alice},
TraceID: trace.TraceID(traceIDA),
SpanID: trace.SpanID(spanIDA),
TraceFlags: trace.TraceFlags(flagsA),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevA,
SeverityText: "A",
Body: bodyA,
Attributes: []api.KeyValue{bob},
TraceID: trace.TraceID(traceIDA),
SpanID: trace.SpanID(spanIDA),
TraceFlags: trace.TraceFlags(flagsA),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevB,
SeverityText: "B",
Body: bodyB,
Attributes: []api.KeyValue{alice},
TraceID: trace.TraceID(traceIDB),
SpanID: trace.SpanID(spanIDB),
TraceFlags: trace.TraceFlags(flagsB),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
out = append(out, logtest.RecordFactory{
Timestamp: ts,
ObservedTimestamp: obs,
Severity: sevB,
SeverityText: "B",
Body: bodyB,
Attributes: []api.KeyValue{bob},
TraceID: trace.TraceID(traceIDB),
SpanID: trace.SpanID(spanIDB),
TraceFlags: trace.TraceFlags(flagsB),
InstrumentationScope: &scope,
Resource: res,
}.NewRecord())
return out
}()
pbLogRecords = []*lpb.LogRecord{
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevA,
SeverityText: "A",
Body: pbBodyA,
Attributes: []*cpb.KeyValue{pbAlice},
Flags: uint32(flagsA),
TraceId: traceIDA,
SpanId: spanIDA,
},
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevA,
SeverityText: "A",
Body: pbBodyA,
Attributes: []*cpb.KeyValue{pbBob},
Flags: uint32(flagsA),
TraceId: traceIDA,
SpanId: spanIDA,
},
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevB,
SeverityText: "B",
Body: pbBodyB,
Attributes: []*cpb.KeyValue{pbAlice},
Flags: uint32(flagsB),
TraceId: traceIDB,
SpanId: spanIDB,
},
{
TimeUnixNano: uint64(ts.UnixNano()),
ObservedTimeUnixNano: uint64(obs.UnixNano()),
SeverityNumber: pbSevB,
SeverityText: "B",
Body: pbBodyB,
Attributes: []*cpb.KeyValue{pbBob},
Flags: uint32(flagsB),
TraceId: traceIDB,
SpanId: spanIDB,
},
}
pbScopeLogs = &lpb.ScopeLogs{
Scope: pbScope,
SchemaUrl: semconv.SchemaURL,
LogRecords: pbLogRecords,
}
pbResourceLogs = &lpb.ResourceLogs{
Resource: pbRes,
SchemaUrl: semconv.SchemaURL,
ScopeLogs: []*lpb.ScopeLogs{pbScopeLogs},
}
)
func TestResourceLogs(t *testing.T) {
want := []*lpb.ResourceLogs{pbResourceLogs}
assert.Equal(t, want, ResourceLogs(records))
}
func TestSeverityNumber(t *testing.T) {
for i := 0; i <= int(api.SeverityFatal4); i++ {
want := lpb.SeverityNumber(i)
want += lpb.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED
assert.Equal(t, want, SeverityNumber(api.Severity(i)))
}
}
func BenchmarkResourceLogs(b *testing.B) {
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
var out []*lpb.ResourceLogs
for pb.Next() {
out = ResourceLogs(records)
}
_ = out
})
}