package pb import ( "fmt" "strings" "time" "github.com/VictoriaMetrics/easyproto" ) // ExportLogsServiceRequest represents the corresponding OTEL protobuf message type ExportLogsServiceRequest struct { ResourceLogs []ResourceLogs } // MarshalProtobuf marshals r to protobuf message, appends it to dst and returns the result. func (r *ExportLogsServiceRequest) MarshalProtobuf(dst []byte) []byte { m := mp.Get() r.marshalProtobuf(m.MessageMarshaler()) dst = m.Marshal(dst) mp.Put(m) return dst } func (r *ExportLogsServiceRequest) marshalProtobuf(mm *easyproto.MessageMarshaler) { for _, rm := range r.ResourceLogs { rm.marshalProtobuf(mm.AppendMessage(1)) } } // UnmarshalProtobuf unmarshals r from protobuf message at src. func (r *ExportLogsServiceRequest) UnmarshalProtobuf(src []byte) (err error) { // message ExportLogsServiceRequest { // repeated ResourceLogs resource_metrics = 1; // } var fc easyproto.FieldContext for len(src) > 0 { src, err = fc.NextField(src) if err != nil { return fmt.Errorf("cannot read next field in ExportLogsServiceRequest: %w", err) } switch fc.FieldNum { case 1: data, ok := fc.MessageData() if !ok { return fmt.Errorf("cannot read ResourceLogs data") } var rl ResourceLogs if err := rl.unmarshalProtobuf(data); err != nil { return fmt.Errorf("cannot unmarshal ResourceLogs: %w", err) } r.ResourceLogs = append(r.ResourceLogs, rl) } } return nil } // ResourceLogs represents the corresponding OTEL protobuf message type ResourceLogs struct { Resource Resource `json:"resource"` ScopeLogs []ScopeLogs `json:"scopeLogs"` } func (rl *ResourceLogs) marshalProtobuf(mm *easyproto.MessageMarshaler) { rl.Resource.marshalProtobuf(mm.AppendMessage(1)) for _, sm := range rl.ScopeLogs { sm.marshalProtobuf(mm.AppendMessage(2)) } } func (rl *ResourceLogs) unmarshalProtobuf(src []byte) (err error) { // message ResourceLogs { // Resource resource = 1; // repeated ScopeLogs scope_logs = 2; // } var fc easyproto.FieldContext for len(src) > 0 { src, err = fc.NextField(src) if err != nil { return fmt.Errorf("cannot read next field in ResourceLogs: %w", err) } switch fc.FieldNum { case 1: data, ok := fc.MessageData() if !ok { return fmt.Errorf("cannot read Resource data") } if err := rl.Resource.unmarshalProtobuf(data); err != nil { return fmt.Errorf("cannot umarshal Resource: %w", err) } case 2: data, ok := fc.MessageData() if !ok { return fmt.Errorf("cannot read ScopeLogs data") } var sl ScopeLogs if err := sl.unmarshalProtobuf(data); err != nil { return fmt.Errorf("cannot unmarshal ScopeLogs: %w", err) } rl.ScopeLogs = append(rl.ScopeLogs, sl) } } return nil } // ScopeLogs represents the corresponding OTEL protobuf message type ScopeLogs struct { LogRecords []LogRecord } func (sl *ScopeLogs) marshalProtobuf(mm *easyproto.MessageMarshaler) { for _, m := range sl.LogRecords { m.marshalProtobuf(mm.AppendMessage(2)) } } func (sl *ScopeLogs) unmarshalProtobuf(src []byte) (err error) { // message ScopeLogs { // repeated LogRecord log_records = 2; // } var fc easyproto.FieldContext for len(src) > 0 { src, err = fc.NextField(src) if err != nil { return fmt.Errorf("cannot read next field in ScopeLogs: %w", err) } switch fc.FieldNum { case 2: data, ok := fc.MessageData() if !ok { return fmt.Errorf("cannot read LogRecord data") } var lr LogRecord if err := lr.unmarshalProtobuf(data); err != nil { return fmt.Errorf("cannot unmarshal LogRecord: %w", err) } sl.LogRecords = append(sl.LogRecords, lr) } } return nil } // LogRecord represents the corresponding OTEL protobuf message // https://github.com/open-telemetry/oteps/blob/main/text/logs/0097-log-data-model.md type LogRecord struct { // time_unix_nano is the time when the event occurred. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // Value of 0 indicates unknown or missing timestamp. TimeUnixNano uint64 // Time when the event was observed by the collection system. // For events that originate in OpenTelemetry (e.g. using OpenTelemetry Logging SDK) // this timestamp is typically set at the generation time and is equal to Timestamp. // For events originating externally and collected by OpenTelemetry (e.g. using // Collector) this is the time when OpenTelemetry's code observed the event measured // by the clock of the OpenTelemetry code. This field MUST be set once the event is // observed by OpenTelemetry. // // For converting OpenTelemetry log data to formats that support only one timestamp or // when receiving OpenTelemetry log data by recipients that support only one timestamp // internally the following logic is recommended: // - Use time_unix_nano if it is present, otherwise use observed_time_unix_nano. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // Value of 0 indicates unknown or missing timestamp. ObservedTimeUnixNano uint64 // Numerical value of the severity, normalized to values described in Log Data Model. SeverityNumber int32 SeverityText string Body AnyValue Attributes []*KeyValue } func (lr *LogRecord) marshalProtobuf(mm *easyproto.MessageMarshaler) { mm.AppendFixed64(1, lr.TimeUnixNano) mm.AppendInt32(2, lr.SeverityNumber) mm.AppendString(3, lr.SeverityText) lr.Body.marshalProtobuf(mm.AppendMessage(5)) for _, a := range lr.Attributes { a.marshalProtobuf(mm.AppendMessage(6)) } mm.AppendFixed64(11, lr.ObservedTimeUnixNano) } func (lr *LogRecord) unmarshalProtobuf(src []byte) (err error) { // message LogRecord { // fixed64 time_unix_nano = 1; // fixed64 observed_time_unix_nano = 11; // SeverityNumber severity_number = 2; // string severity_text = 3; // AnyValue body = 5; // repeated KeyValue attributes = 6; // } var fc easyproto.FieldContext for len(src) > 0 { src, err = fc.NextField(src) if err != nil { return fmt.Errorf("cannot read next field in LogRecord: %w", err) } switch fc.FieldNum { case 1: ts, ok := fc.Fixed64() if !ok { return fmt.Errorf("cannot read log record timestamp") } lr.TimeUnixNano = ts case 11: ts, ok := fc.Fixed64() if !ok { return fmt.Errorf("cannot read log record observed timestamp") } lr.ObservedTimeUnixNano = ts case 2: severityNumber, ok := fc.Int32() if !ok { return fmt.Errorf("cannot read severity number") } lr.SeverityNumber = severityNumber case 3: severityText, ok := fc.String() if !ok { return fmt.Errorf("cannot read severity string") } lr.SeverityText = strings.Clone(severityText) case 5: data, ok := fc.MessageData() if !ok { return fmt.Errorf("cannot read Body") } if err := lr.Body.unmarshalProtobuf(data); err != nil { return fmt.Errorf("cannot unmarshal Body: %w", err) } case 6: data, ok := fc.MessageData() if !ok { return fmt.Errorf("cannot read attributes data") } lr.Attributes = append(lr.Attributes, &KeyValue{}) a := lr.Attributes[len(lr.Attributes)-1] if err := a.unmarshalProtobuf(data); err != nil { return fmt.Errorf("cannot unmarshal Attribute: %w", err) } } } return nil } // FormatSeverity returns normalized severity for log record func (lr *LogRecord) FormatSeverity() string { if lr.SeverityText != "" { return lr.SeverityText } if lr.SeverityNumber < 0 || lr.SeverityNumber >= int32(len(logSeverities)) { return logSeverities[0] } return logSeverities[lr.SeverityNumber] } // ExtractTimestampNano returns timestamp for log record func (lr *LogRecord) ExtractTimestampNano() int64 { switch { case lr.TimeUnixNano > 0: return int64(lr.TimeUnixNano) case lr.ObservedTimeUnixNano > 0: return int64(lr.ObservedTimeUnixNano) default: return time.Now().UnixNano() } } // https://github.com/open-telemetry/opentelemetry-collector/blob/cd1f7623fe67240e32e74735488c3db111fad47b/pdata/plog/severity_number.go#L41 var logSeverities = []string{ "Unspecified", "Trace", "Trace2", "Trace3", "Trace4", "Debug", "Debug2", "Debug3", "Debug4", "Info", "Info2", "Info3", "Info4", "Error", "Error2", "Error3", "Error4", "Fatal", "Fatal2", "Fatal3", "Fatal4", }