Skip to content

otlploghttp: global OTLP endpoint with trailing slash produces double-slash path #8863

Description

@RedZapdos123

Description:

On latest main, otlploghttp mishandles the global OTEL_EXPORTER_OTLP_ENDPOINT when the base URL ends with a trailing slash

If the endpoint is set to a base URL like http://host/prefix/, log export requests are sent to /prefix//v1/logs instead of /prefix/v1/logs

Steps to Reproduce:

  1. Check out main at 524fc3c25
  2. From PowerShell in the repository root, run:
    $repo = ((Get-Location).Path -replace '\\', '/')
    $root = Join-Path $env:TEMP 'otel-otlploghttp-trailing-slash-repro'
    Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
    New-Item -ItemType Directory -Force -Path $root | Out-Null
    
    @"
    module example.com/otlploghttprepro
    
    go 1.25.0
    
    require (
        go.opentelemetry.io/otel v0.0.0
        go.opentelemetry.io/otel/sdk/log v0.0.0
        go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.0.0
    )
    
    replace go.opentelemetry.io/otel => $repo
    replace go.opentelemetry.io/otel/sdk/log => $repo/sdk/log
    replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => $repo/exporters/otlp/otlplog/otlploghttp
    "@ | Set-Content -Path (Join-Path $root 'go.mod')
    
    @"
    package main
    
    import (
        "context"
        "fmt"
        "net/http"
        "net/http/httptest"
        "os"
        "time"
    
        "go.opentelemetry.io/otel/attribute"
        "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
        sdklog "go.opentelemetry.io/otel/sdk/log"
    )
    
    func main() {
        var gotPath string
        srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            gotPath = r.URL.Path
            w.WriteHeader(http.StatusOK)
            _, _ = w.Write([]byte("{}"))
        }))
        defer srv.Close()
    
        if err := os.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", srv.URL+"/prefix/"); err != nil {
            panic(err)
        }
        defer os.Unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT")
    
        exp, err := otlploghttp.New(context.Background(), otlploghttp.WithTimeout(time.Second))
        if err != nil {
            panic(err)
        }
    
        var rec sdklog.Record
        rec.SetBody(attribute.StringValue("x"))
    
        if err := exp.Export(context.Background(), []sdklog.Record{rec}); err != nil {
            panic(err)
        }
    
        fmt.Println(gotPath)
    }
    "@ | Set-Content -Path (Join-Path $root 'main.go')
    
    Push-Location $root
    go mod tidy
    go run .
    Pop-Location

Actual Behavior:

The repro prints:

/prefix//v1/logs

So the exporter sends logs to a path with a duplicated slash

Expected Behavior:

The exporter should normalize the global base endpoint path and send logs to:

/prefix/v1/logs

A trailing slash in the base OTEL_EXPORTER_OTLP_ENDPOINT should not produce a duplicated slash in the final OTLP logs path

Notes:

  • exporters/otlp/otlplog/otlploghttp/config.go currently returns u.Path + "/v1/logs" in convPath
  • If u.Path already ends with /, that produces /prefix//v1/logs
  • The trace and metric OTLP HTTP exporters normalize the global endpoint base path instead of duplicating the slash
  • This is separate from otlploghttp.WithEndpointURL does not default to "/v1/logs" #8537, which discussed WithEndpointURL(...) semantics for explicit in-code endpoint URLs
  • This bug affects only the global OTEL_EXPORTER_OTLP_ENDPOINT base URL path handling

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions