Skip to content

Provide OOTB mapping (with OTLP) for incoming Opentelemetry logs to outgoing Opentelemetry logs #22696

@someStrangerFromTheAbyss

Description

A note for the community

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Problem

Currently, if you setup a Vector.dev to receive logs with the Opentelemetry Source then send them with Opentelemetry Sink whitout any mapping or changes to these logs, your logs does not match the Opentelemetry format. The way i found out about it was when trying to use the Loki Otlp endpoint. None of the attributes/Ressource_attributes were not detected since the logs are not in a RessourceLogs array.

You have to do a remap of these logs for it to corresponds to the Opentelemetry format.

Currently, the best way i got around this problem is to have a vrl remap with the best of my opentelemetry/vector knowledge. This is what i came up with right now:

attributes_array = []
resource_attributes_array = []
# for each: https://vector.dev/docs/reference/vrl/examples/#for_each
for_each(object!(.attributes)) -> |key,value|{
    insert_key = {}
    insert_key.key = key
    insert_key.value = {}
    # The attribute value is either:
    # A primitive type: string, boolean, double precision floating point (IEEE 754-1985) or signed 64 bit integer.
    # An array of primitive type values. The array MUST be homogeneous, i.e., it MUST NOT contain values of different types.
    # Example of type: https://github.com/open-telemetry/opentelemetry-proto/blob/d7770822d70c7bd47a6891fc9faacc66fc4af3d3/examples/logs.json#L9
    if is_string(value) {
        insert_key.value.stringValue = value
    } else if is_boolean(value) {
        insert_key.value.boolValue = value
    } else if is_integer(value){
        insert_key.value.intValue = value
    } else if is_float(value){
        insert_key.value.doubleValue = value
    } else {
        # in such a case, we try to transform to string then, insert value has string. If cannot be string, simply put empty.
        insert_key.value.stringValue = string(value) ?? ""
    }
    attributes_array = push(attributes_array,insert_key)
}
for_each(object!(.resources)) -> |key,value|{
    insert_key = {}
    insert_key.key = key
    insert_key.value = {}
    # The attribute value is either:
    # A primitive type: string, boolean, double precision floating point (IEEE 754-1985) or signed 64 bit integer.
    # An array of primitive type values. The array MUST be homogeneous, i.e., it MUST NOT contain values of different types.
    # Example of type: https://github.com/open-telemetry/opentelemetry-proto/blob/d7770822d70c7bd47a6891fc9faacc66fc4af3d3/examples/logs.json#L9
    if is_string(value) {
        insert_key.value.stringValue = value
    } else if is_boolean(value) {
        insert_key.value.boolValue = value
    } else if is_integer(value){
        insert_key.value.intValue = value
    } else if is_float(value){
        insert_key.value.doubleValue = value
    } else {
        # in such a case, we try to transform to string then, insert value has string. If cannot be string, simply put empty.
        insert_key.value.stringValue = string(value) ?? ""
    }
    resource_attributes_array = push(resource_attributes_array,insert_key)
}
#map_values(object!(.attributes)) -> |value| { upcase!(value) }
.resourceLogs = [{
  "resource": {
    "attributes": resource_attributes_array
  },
  "scopeLogs": [{
    "scope": object(.scope) ?? {},
    "logRecords": [{
      "timeUnixNano": to_unix_timestamp!(.timestamp, unit: "nanoseconds"),
      "body": { "stringValue": .message },
      "severityText": .severity_text,
      "severityNumber": .severity_number,
      "attributes": attributes_array,
      "droppedAttributesCount": .dropped_attributes_count,
      "traceId": string(.traceId) ?? "",
      "spanId": string(.spanId) ?? ""
    }]
  }]
}]
del(.severity_number)
del(.severity_text)
del(.resources)
del(.attributes)
del(.message)
del(.timestamp)
del(.dropped_attributes_count)
del(.observed_timestamp)
del(.source_type)
del(.scope)

Any suggestion is appreciated.

Configuration

sources:
  open_telemetry:
    grpc:
      address: 0.0.0.0:4320
    http:
      address: 0.0.0.0:4321
    type: opentelemetry
transforms:
  remap_opentelemetry:
    type: remap
    inputs: [open_telemetry.logs]
    file: "/home/charles/vector/otel_remap.vrl"
sinks:
  debug_console:
    type: console
    encoding:
      codec: json
    inputs: 
    - remap_opentelemetry

Version

0.45.0

Debug Output


Example Data

Logs sent by the opentelemetry collector after being received by telemetrygen:

{
    "resourceLogs": [
        {
            "resource": {
                "attributes": [
                    {
                        "key": "service.name",
                        "value": {
                            "stringValue": "test"
                        }
                    },
                    {
                        "key": "k8s.cluster.name",
                        "value": {
                            "stringValue": "logs-dev-ne1-z02"
                        }
                    }
                ]
            },
            "scopeLogs": [
                {
                    "scope": {},
                    "logRecords": [
                        {
                            "timeUnixNano": "1742408939363760620",
                            "severityNumber": 9,
                            "severityText": "Info",
                            "body": {
                                "stringValue": "the message"
                            },
                            "attributes": [
                                {
                                    "key": "app",
                                    "value": {
                                        "stringValue": "server"
                                    }
                                },
                                {
                                    "key": "component",
                                    "value": {
                                        "stringValue": "logslocal"
                                    }
                                }
                            ],
                            "droppedAttributesCount": 1,
                            "traceId": "",
                            "spanId": ""
                        }
                    ]
                }
            ]
        }
    ]
}

How its parsed by vector:

{
    "attributes": {
        "app": "server",
        "component": "logslocal"
    },
    "dropped_attributes_count": 1,
    "message": "the message",
    "observed_timestamp": "2025-03-19T14:29:39.120971960Z",
    "resources": {
        "k8s.cluster.name": "logs-dev-ne1-z02",
        "service.name": "test"
    },
    "severity_number": 9,
    "severity_text": "Info",
    "source_type": "opentelemetry",
    "timestamp": "2025-03-19T14:29:38.903143790Z"
}

The timestamp are different because these are different samples, but the result would be the same.

Additional Context

Here is my otelcollector config:

processors:
  attributes:
    actions:
      - key: component
        value: "logslocal"
        action: upsert
  resource:
    attributes:
    - key: service.name
      value: "test"
      action: upsert
    - key: k8s.cluster.name
      value: "logs-dev-ne1-z02"
      action: upsert
receivers:
  otlp:
    protocols:
      grpc:
       endpoint: 0.0.0.0:4317
exporters:
  otlphttp:
    endpoint: http://localhost:4321
  debug:
    verbosity: detailed
    sampling_initial: 5
    sampling_thereafter: 200
  file:
    path: ./foo
service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [attributes,resource]
      exporters: [otlphttp,debug,file]

I then use telemetrygen to generate logs in otel format.

References

I also saw that there is a bug for the Sink opentelemetry, #22188

Ill try to find a way to use the reduce to combine into one request

Metadata

Metadata

Assignees

No one assigned

    Labels

    sink: opentelemetryAnything `opentelemetry` sink relatedtype: enhancementA value-adding code change that enhances its existing functionality.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions